How to trigger clean method from model when updating? - django

When I save my form I perform validation of data that is defined in my model
def clean(self):
model = self.__class__
if self.unit and (self.is_active == True) and model.objects.filter(unit=self.unit, is_terminated = False , is_active = True).exclude(id=self.id).count() > 0:
raise ValidationError('Unit has active lease already, Terminate existing one prior to creation of new one or create a not active lease '.format(self.unit))
How can I trigger same clean method during simple update without a need to duplicate clean logic in my update view?(In my view I just perform update without any form)
Unit.objects.filter(pk=term.id).update(is_active=False)

update don't call the save method of your model and so it's not possible for django to raise ValidationError exceptions in this case.
You need to at least call the full_clean method of your model before doing the update.
Maybe like this ?
unit = Unit.objects.get(pk=term.id)
unit.is_active = False
try:
unit.full_clean()
except ValidationError as e:
# Handle the exceptions here
unit.save()
Reference: https://docs.djangoproject.com/en/1.11/ref/models/instances/#validating-objects

Related

Access Django through model extra fields in m2m_changed signal handler

Let's say I have the following Django models, which represents a sorted relationship between a Parent and Child:
class Parent(models.Model):
name = models.CharField(max_length=50)
children = models.ManyToManyField("Child", through="ParentChild")
class Child(models.Model):
name = models.CharField(max_length=50)
class ParentChild(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(fields=["parent", "child"], name="uc_parent_child"),
models.UniqueConstraint(fields=["parent", "sort_number"], name="uc_parent_child"),
]
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
child = models.ForeignKey(Child, on_delete=models.CASCADE)
sort_number = models.IntegerField()
def save(self, *args, **kwargs):
exising_sort_numbers = self.parent.parentchild_set.values_list(
"sort_number", flat=True
)
if self.sort_number in exising_sort_numbers:
raise Exception(f"Duplicate sort number: {self.sort_number}")
super().save(*args, **kwargs)
Now if I create the relationships using the through model, I get the exception for a duplicate sort_number:
ParentChild.objects.create(parent=parent, child=child1, sort_number=0)
ParentChild.objects.create(parent=parent, child=child2, sort_number=0) # raises Exception
However, if I create the relationships using the .add method, I don't get the exception:
parent.children.add(child1, through_defaults={"sort_number": 0})
parent.children.add(child2, through_defaults={"sort_number": 0}) # does NOT raise Exception
I know using the .add method doesn't call the .save method on the through model so I need to use the m2m_change signal to run this logic. But I'm not sure how to get the sort_number within this signal. Here's the code I have for the signal so far:
#receiver(m2m_changed, sender=Parent.children.through)
def validate_something(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == "pre_add":
for pk in pk_set:
child = model.objects.get(pk=pk)
exising_sort_numbers = instance.parentchild_set.values_list(
"sort_number", flat=True
)
# where's sort_number specified in through_defaults ???
Any idea how I can get this value and perform the "pre_add" validation or is this not possible?
You have this constraint - models.UniqueConstraint(fields=["parent", "sort_number"], name="uc_parent_child"), which means that you can't have more than one relation with the same parent and sort_number. There's even an extra check in ParentChild's save method to further enforce this. Makes sense to have an exception thrown when you try to create such a relation.
Also, the constraint name needs to be unique. I tried the code and couldn't make migrations as is.
If you do what you are trying to, you'll get that exception again when saving.
Instead of trying to hack around the constraint, you should either change/remove it or adapt your code to work with it - don't try to create instance which will violate it.
As to your specific question,the instance you get in validate_something is Parent and there's no direct access to the intermediary instance or it's defaults. You also can't query the intermediary instance, because it does not exist yet.
For any googlers that might be looking for a way to handle through model fields, you can't get it in pre_add as #4140tm said, since the record doesn't exist yet. But you can work around it on post_add with some effort:
#receiver(m2m_changed, sender=Parent.children.through)
def validate_something(sender, instance, action, reverse, model, pk_set, **kwargs):
# notice this is not 'pre_add':
if action == "post_add":
# just for clarity:
parent = instance
child_model = model
through_model = sender
# > OP: where's sort_number specified in through_defaults ???
# here it is:
existing_sort_numbers = [pc.sort_number \
for pc in through_model.objects.filter(parent=parent.id) \
if pc.child not in pk_set]
# now just work around (rollback, raise exception, etc):
for pk in pk_set:
added_child = child_model.objects.get(id=pk)
# goes on dirty work...

Why does Django throw a KeyError on this form validation?

Here's the code:
...
class Meta:
model = Card
def clean_video_url(self):
video_url = self.cleaned_data['video_url']
if video_url != '' and len(video_url) != YOUTUBE_VIDEO_URL_LENGTH:
pos = string.find(video_url, YOUTUBE_VIDEO_URL_IDENTIFIER)
identifier_length = len(YOUTUBE_VIDEO_URL_IDENTIFIER)
if pos == -1:
raise forms.ValidationError(_('youtube-url-not-valid'))
video_url = video_url[pos+identifier_length:pos+identifier_length+YOUTUBE_VIDEO_URL_LENGTH]
return video_url
...
def clean(self):
video_url = self.cleaned_data['video_url']
field1 = self.cleaned_data['field1']
if video_url == '' and field1 == '':
raise forms.ValidationError(_('must-fill-video-url-or-front'))
return self.cleaned_data
The most disturbing thing is that it works (submits and persists in the database) in almost all situations. It doesn't work when I write dummy text like 'aeuchah' in the video_url field, but instead it throws:
Exception Type: KeyError
Exception Value:
'video_url'
I re-read my clean_video_url method and went to see what the variables were with a debug tool like pdb.set_trace, but I can't find the problem.
UPDATE: As Marius Grigaitis and Davide R. said, the clean method is called after all the individual field methods are done. clean_video_url raised a ValidationError and returned nothing, so the clean method found nothing to work with and raised a KeyError.
You should always check that key exists in cleaned_data before using it in clean() method. You're not guaranteed that value is present in cleaned_data array if previous validations has not passed.
Documentation: https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
By the time the form’s clean() method is called, all the individual field clean methods will have been run (the previous two sections), so self.cleaned_data will be populated with any data that has survived so far. So you also need to remember to allow for the fact that the fields you are wanting to validate might not have survived the initial individual field checks.

clean() method in model and field validation

I have a problem with a model's clean() method and basic field validation. here's my model and the clean() method.
class Trial(models.Model):
trial_start = DurationField()
movement_start = DurationField()
trial_stop = DurationField()
def clean(self):
from django.core.exceptions import ValidationError
if not (self.movement_start >= self.trial_start):
raise ValidationError('movement start must be >= trial start')
if not (self.trial_stop >= self.movement_start):
raise ValidationError('trial stop must be >= movement start')
if not (self.trial_stop > self.trial_start):
raise ValidationError('trial stop must be > trial start')
My clean() method checks whether certain values are in the correct range. If the user forget to fill out a field, e.g. movement_start, then I get an error:
can't compare datetime.timedelta to NoneType
I'm surprised that I get this error, since the original clean() function should be catching that missing entry (after all movement_start is a required field). So how can I the basic checking for missing values, and my custom check whether values are in certain ranges? Can this be done with model's clean() method, or do I need to use Forms?
EDIT1 to make it more clear: trial_start, movement_start and trial_stop are all required fields. I need to write a clean() method which first checks that all three fields have been filled out, and then, check whether the values are in a certain range.
The following code for example DOES NOT work, since trial_start might be empty. I want to avoid having to check for the existence of each field - django should do that for me.
class TrialForm(ModelForm):
class Meta:
model = Trial
def clean_movement_start(self):
movement_start = self.cleaned_data["movement_start"]
trial_start = self.cleaned_data["trial_start"]
if not (movement_start >= trial_start):
raise forms.ValidationError('movement start must be >= trial start')
return self.cleaned_data["movement_start"]
EDIT2 The reason that I wanted to add this check to the model's clean() method is that objects that are created on the python shell, will automatically be checked for correct values. A form will be fine for views, but I need the value check also for the shell.
I guess that's the way to go:
class TrialForm(ModelForm):
class Meta:
model = Trial
def clean(self):
data = self.cleaned_data
if not ('movement_start' in data.keys() and 'trial_start' in data.keys() and 'trial_stop' in data.keys()):
raise forms.ValidationError("Please fill out missing fields.")
trial_start = data['trial_start']
movement_start = data['movement_start']
trial_stop = data['trial_stop']
if not (movement_start >= trial_start):
raise forms.ValidationError('movement start must be >= trial start')
if not (trial_stop >= movement_start):
raise forms.ValidationError('trial stop must be >= movement start')
if not (trial_stop > trial_start):
raise forms.ValidationError('trial stop must be > trial start')
return data
EDIT the downside of this approach is, that value checking will only work if I create objects through the form. Objects that are created on the python shell won't be checked.
I know this is late, but to answer why this might be happening for people who end up here: There's no "original clean". The clean method is a hook for custom validation, so you are the one who provides its code. Not sure how OP was using the clean hook, but: After defining it you should be calling full_clean() over your models so that Django runs model validation in its entirety. See the details in the docs for the order in which it calls the different validation methods and such https://docs.djangoproject.com/en/1.11/ref/models/instances/#validating-objects (perhaps important to note: full_clean() isn't automatically called by a model's save() which is part of why when you use the shell and save straight away you'll be skipping the validation)
(note ModelForms also have various validation steps and will call a model's full_clean: https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#validation-on-a-modelform )
I'm struggling with a similar issue but with a ForeignKey. In your case, I would just check that the fields are not empty, and I would simplify your Boolean expressions:
class Trial(models.Model):
trial_start = DurationField()
movement_start = DurationField()
trial_stop = DurationField()
def clean(self):
from django.core.exceptions import ValidationError
if self.trial_start:
if self.movement_start and self.movement_start < self.trial_start:
raise ValidationError('movement start must be >= trial start')
if self.trial_stop:
if self.trial_stop <= self.trial_start:
raise ValidationError('trial stop must be > trial start')
if self.movement_start:
if self.trial_stop < self.movement_start:
raise ValidationError('trial stop must be >= movement start')
You can add the following code in your model:
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
With this wherever you create your object (form, view, shell, test) the validation will be called.

Django : Validate data by querying the database in a model form (using custom clean method)

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?

Django using python

1.Kindly tell how can we set the limit for integer fields in django .
2.Kindly tell the codings for the block name (Add Another Choice) should be of limited for the program
class Record(models.Model):
Name = models.CharField(max_length=200,blank=True,null=True,help_text="Employee Name")
Empid = models.CharField(max_length=300,blank=True,help_text="Employee ID")
Salary = models.CharField(max_length=300,blank=True,null=True)
Bonus = models.IntegerField(blank=True,null=True)
class Choice(models.Model):
p=models.ForeignKey(Record)
Month=models.CharField(max_length=200,blank=True,null=True)
Neither can be done at the ORM level; you will need to add database-level constrains using raw SQL.
1 If you just want to check user input use a clean method
ie.
def clean_bonus(self): #thus: clean_FIELDNAME
#check the value and show an error
2 I am not sure what you mean:
a) If you mean you want users to select atleast 1 choice, but limit the maximum use something like this:
class RequireOneFormSet(BaseInlineFormSet):
"""
Require at least one form in the formset to be completed.
"""
def clean(self):
"""Check that at least one form has been completed."""
super(RequireOneFormSet, self).clean()
for error in self.errors:
if error:
return
completed = 0
for cleaned_data in self.cleaned_data:
# form has data and we aren't deleting it.
if cleaned_data and not cleaned_data.get('DELETE', False):
completed += 1
if completed < 1:
raise forms.ValidationError("At least one %s is required." %
self.model._meta.object_name.lower())
b) If you mean you want to have multiple foreign keys use django ContentTypes