I following up with my question Creating new objects under specific parameters . I was trying to find out how to write functions into my django models to create default values on fields. I know how to do that, but now would like to take it a step further by adding parameters to the functions and using previous fields as the parameters. So far I have tried the following and each have given their own error messages when I try and migrate them.
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num(score): # Want the score parameter to be the score field
new_num = score
return new_num
last_score = models.IntegerField(default=num(score))
Error: int() argument must be a string, a bytes-like object or a number, not 'IntegerField'
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num(self):
new_num = self.score
return new_num
last_score = models.IntegerField(default=num(self))
NameError: name 'self' is not defined
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num():
new_num = self.score
return new_num
last_score = models.IntegerField(default=num)
NameError: name 'self' is not defined
Does anybody know how to do this, or know where there is documentation on this?
Django's model field default callable can't actually be an instance method. Due to how this callable is used in migrations it must be serializable for migrations, e.g. in the migrations you'll see a reference to the path of the function, such as default="my_app.models.my_callable_default".
The way I've seen other solve this is to override the models save method to check those fields and set the default value there.
class NewTest(models.Model):
score = models.IntegerField(default=0)
last_score = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if not self.pk: # Check for create
self.last_score = self.score
else:
# Get the existing score from the database, because this instance has been modified
score_from_db = NewTest.objects.values("score").get(pk=self.pk)["score"]
self.last_score = self.score
return super().save(*args, **kwargs) # Continue with save as normal
This solution overrides the save method of your model to assign another fields value to the last_score field during creation, and if it's an update it will pull the existing object from the database to get the last score (I'm not sure if this is what you are after). The if not self.pk is used to check if the current save is being called for a new object (create), or an existing object (update), because the database assigns the primary key during creation.
Related
I am trying to create create a M2M value on self within the same model. I can update the name field fine. However, I keep getting the TypeError when I update the M2M (supertag) field.
models.py
class Tag(models.Model):
name = models.CharField("Name", max_length=5000, blank=True)
supertag = models.ManyToManyField('self', blank=True)
serializers.py
supe = tag.all()
print(supe)
# returns [<Tag: XYZ>, <Tag: 123>]
for y in supe:
# import pdb; pdb.set_trace()
tag = Tag.objects.update(supertag__pk=y.pk)
tag.save()
error:
TypeError: 'supertag__pk' is an invalid keyword argument for this function
I also tried just tag = Tag.objects.update(supertag=supe) which gave the same error
supe is a queryset, it doesn't have a pk attribute.
Also, you are using same name for different variables. tag has already been assigned.
supe = tag.all()
When assigning tag to a new object would affect the working of the for loop, which is based on the former tag variable.
tag = Tag.objects.get_or_create(supertag__pk=supe.pk)
You can't do this.
EDIT
The function get_or_create actually returns tuple, an object and a boolean flag.
The boolean flag specifies whether the object was created or not.
So, the logic you were implementing was wrong. As we discussed,
You could do something like this,
for x in supe:
if x.taglevel == 1:
for value in supe:
x.tag.add(value)
x.save()
else:
#your next logic
print("No level 1")
So, I have a model called ScheduleItem
class ScheduleItem(models.Model):
agreement = FK
location = FK
start = models.DateTimeField()
end = models.DateTimeField()
totalHours = DecimalField
def get_total_hours(self):
start = timedelta(hours=self.start.hour, minutes=self.start.minute)
end = timedelta(hours=self.end.hour, minutes=self.end.minute)
td = (end-start).seconds
totalHours=Decimal(td/Decimal(60)/Decimal(60))
return totalHours
def save(self,*args,**kwargs):
if self.pk == None:
super(ScheduleItem,self).save(self,*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(self,*args,**kwargs)
This throws PRIMARY key errors. I get duplicate entries with the second super(ScheduleItem,self). I cannot for the life of me figure out how to check for pk to access the datetime value and then save again within the save override method. I've tried moving things around, I've tried saving within the get_total_hours() function, with nothing but trouble.
I just want the object to be committed to the db so I can get the datetime objects and then calculate the total hours.
I'd rather not convert to datetime within the save function.
Does anyone have any tip or can anyone tell me where I'm going wrong?
You should not pass self to save(). You're calling super().save() as a bound method on an instance, so self is implicitly passed as the first argument. Change it to this:
def save(self,*args,**kwargs):
if self.pk is None:
super(ScheduleItem,self).save(*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(*args,**kwargs)
You get this weird behaviour because the first positional argument is force_insert, and the model instance evaluates to True. The second call to super().save() tries to force an insert with the same pk you previously saved.
Edit : These different types were just because of the django method:
request.POST.get("attribute")
which from Json data, returns me unicode.
The solution was to parse these values at the beginning
I have got a big problem, and i don't understand where it comes from.
On my Score model to save scores for a game, I need to compare values from the current score and the old one before saving. My error is that the types of my field are different whereas my object types are identical.
Maybe some code could explain :
class Score(models.Model):
map = models.ForeignKey(Map, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
score = models.FloatField()
class Meta:
unique_together = ('map', 'user')
def save(self, *args, **kwargs):
try:
oldScore = Score.objects.get(map=self.map, user=self.user)
except ObjectDoesNotExist:
oldScore = None
if oldScore is not None:
if oldScore.score < self.score:
print >> sys.stderr, type(oldScore), type(self)
print >> sys.stderr, type(oldScore.score), type(self.score)
oldScore.delete()
else:
return False
super(Score, self).save(*args, **kwargs)
return True
def __unicode__(self):
return str(self.map) + ' - ' + self.user.username + " : " + str(self.score)
and how i create the score and save it :
score = Score(map=map, user=user, score=score)
saved = score.save()
The result of debug prints :
<class 'main.models.score.Score'> <class 'main.models.score.Score'>
<type 'float'> <type 'unicode'>
I would like to compare my old with new score, but I can't because of these different types. I know i could do some type conversions, but I'd like to know why this is happening, maybe I have failed on something stupid :s
ps: I'm under python 2.7 and Django 1.9.2
Thanks for helping me :)
That is some magic done by the model's metaclass. See, the model fields are defined as a Field class (or its child, eg. FloatField). But when you want to work with the instance of model, you dont want to have a FloatField in the .score property, you want to have the actual value there, right? And that is done by the ModelBase.__metaclass__ when the model's instance is created.
Now when you are saving the value, it's completely ok, that the type of score is unicode - let's say you received the data via form, and all the data you receive are unicode. The value is converted (and validated) when saving. Django looks what kind of data is expected (float), and it tries to convert the value. If that wont work, it will raise an exception. Otherwise the converted value will be stored.
so what you want to do with your save method is this:
def save(self, *args, **kwargs):
if self.pk: # the model has non-empty primary key, so it's in the db already
oldScore = Score.objects.get(self.pk)
if oldScore.score > float(self.score):
# old score was higher, dont do anything
return False
super(Score, self).save(*args, **kwargs)
return True
I am trying to create a custom cleaning method which look in the db if the value of one specific data exists already and if yes raises an error.
I'm using a model form of a class (subsystem) who is inheriting from an other class (project).
I want to check if the sybsystem already exists or not when i try to add a new one in a form.
I get project name in my view function.
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def clean(self,project_name):
cleaned_data = super(SubsytemForm, self).clean(self,project_name)
form_subsystem_name = cleaned_data.get("subsystem_name")
Subsystem.objects.filter(project__project_name=project_name)
subsystem_objects=Subsystem.objects.filter(project__project_name=project_name)
nb_subsystem = subsystem_objects.count()
for i in range (nb_subsystem):
if (subsystem_objects[i].subsystem_name==form_subsystem_name):
msg = u"Subsystem already existing"
self._errors["subsystem_name"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["subsystem_name"]
return cleaned_data
My view function :
def addform(request,project_name):
if form.is_valid():
form=form.save(commit=False)
form.project_id=Project.objects.get(project_name=project_name).id
form.clean(form,project_name)
form.save()
This is not working and i don't know how to do.
I have the error : clean() takes exactly 2 arguments (1 given)
My model :
class Project(models.Model):
project_name = models.CharField("Project name", max_length=20)
Class Subsystem(models.Model):
subsystem_name = models.Charfield("Subsystem name", max_length=20)
projects = models.ForeignKey(Project)
There are quite a few things wrong with this code.
Firstly, you're not supposed to call clean explicitly. Django does it for you automatically when you call form.is_valid(). And because it's done automatically, you can't pass extra arguments. You need to pass the argument in when you instantiate the form, and keep it as an instance variable which your clean code can reference.
Secondly, the code is actually only validating a single field. So it should be done in a specific clean_fieldname method - ie clean_subsystem_name. That avoids the need for mucking about with _errors and deleting the unwanted data at the end.
Thirdly, if you ever find yourself getting a count of something, iterating through a range, then using that index to point back into the original list, you're doing it wrong. In Python, you should always iterate through the actual thing - in this case, the queryset - that you're interested in. However, in this case that is irrelevant anyway as you should query for the actual name directly in the database and check if it exists, rather than iterating through checking for matches.
So, putting it all together:
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def __init__(self, *args, **kwargs):
self.project_name = kwargs.pop('project_name', None)
super(SubsystemForm, self).__init__(*args, **kwargs)
def clean_subsystem_name(self):
form_subsystem_name = self.cleaned_data.get("subsystem_name")
existing = Subsystem.objects.filter(
project__project_name=self.project_name,
subsytem_name=form_subsystem_name
).exists()
if existing:
raise forms.ValidationError(u"Subsystem already existing")
return form_subsystem_name
When you do form=form.save(commit=False) you store a Subsystem instance in the variable form but the clean method is defined in SubsystemForm. Isn't it?
Models.py
#.....
class FilterSave(models.Model):
def __unicode__(self):
return "%s" % (self.name)
name = models.CharField(max_length=50)
customer_type = models.CharField(max_length=20)
tag = models.CharField(max_length=10)
In my views.py
#......
def save_filter(self, filter_name, keyword_filter):
dict_json = eval(json.dumps(keyword_filter))
filter_saves = FilterSave.objects.all()
# Avoid record that already exist
filter_save = FilterSave(name=filter_name, customer_type=dict_json["customer_type"], tag=dict_json["tag"])
# I am trying ....
for fs in filter_saves:
if fs.name != filter_name:#just check for a field name
filter_save.save()
else:
print 'already exist '
More I tried
# Avoid name collision(just for name field not for a Record)
filter_save = FilterSave(name=filter_name, customer_type=dict_json["customer_type"], tag=dict_json["tag"])
exists = FilterSave.objects.filter(name=filter_name)
if not exists:
filter_save.save()
My problems here .I want to save a RECORD if it's not exist in table.Anybody Could help me what I am trying here?
thanks
Django has a convenience method get_or_create(defaults=None, **kwargs)
get_or_create
A convenience method for looking up an object with the given kwargs (may be empty if your model has defaults for all fields), creating one if necessary.
Returns a tuple of (object, created), where object is the retrieved or created object and created is a boolean specifying whether a new object was created.
Is that what you mean?