I have a Django model that looks like this.
class Solution(models.Model):
'''
Represents a solution to a specific problem.
'''
name = models.CharField(max_length=50)
problem = models.ForeignKey(Problem)
description = models.TextField(blank=True)
date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ("name", "problem")
I use a form for adding models that looks like this:
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']
My problem is that the SolutionForm
does not validate Solution
's unique_together
constraint and thus, it returns an IntegrityError
when trying to save the form. I know that I could use validate_unique
to manually check for this but I was wondering if there's any way to catch this in the form validation and return a form error automatically.
Thanks.
The is_valid() method is used to perform validation for each field of the form, it is defined in Django Form class. It returns True if data is valid and place all data into a cleaned_data attribute.
Probably the most common method is to display the error at the top of the form. To create such an error, you can raise a ValidationError from the clean() method. For example: from django import forms from django.
unique_together may be deprecated in the future. This is a list of lists that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate UNIQUE statements are included in the CREATE TABLE statement).
I solved this same problem by overriding the validate_unique()
method of the ModelForm:
def validate_unique(self):
exclude = self._get_validation_exclusions()
exclude.remove('problem') # allow checking against the missing attribute
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError, e:
self._update_errors(e.message_dict)
Now I just always make sure that the attribute not provided on the form is still available, e.g. instance=Solution(problem=some_problem)
on the initializer.
I managed to fix this without modifying the view by adding a clean method to my form:
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']
def clean(self):
cleaned_data = self.cleaned_data
try:
Solution.objects.get(name=cleaned_data['name'], problem=self.problem)
except Solution.DoesNotExist:
pass
else:
raise ValidationError('Solution with this Name already exists for this problem')
# Always return cleaned_data
return cleaned_data
The only thing I need to do now in the view is to add a problem property to the form before executing is_valid
.
As Felix says, ModelForms are supposed to check the unique_together
constraint in their validation.
However, in your case you are actually excluding one element of that constraint from your form. I imagine this is your problem - how is the form going to check the constraint, if half of it is not even on the form?
the solution from @sttwister is right but can be simplified.
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']
def clean(self):
cleaned_data = self.cleaned_data
if Solution.objects.filter(name=cleaned_data['name'],
problem=self.problem).exists():
raise ValidationError(
'Solution with this Name already exists for this problem')
# Always return cleaned_data
return cleaned_data
As a bonus you do not retreive the object in case of duplicate but only check if it exists in the database saving a little bit of performances.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With