Django ModelForm unique_together authentication

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 the form to add models that look like this:

 class SolutionForm(forms.ModelForm): class Meta: model = Solution exclude = ['problem'] 

My problem is that SolutionForm does not check the Solution unique_together and thus returns an IntegrityError when trying to save the form. I know that I could use validate_unique to manually verify this, but I was wondering if there is a way to catch this in the validation form and automatically return the form error.

Thank.

+54
django validation modelform
Jan 26 '10 at 17:01
source share
8 answers

I managed to fix this without changing the view, 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 the problem property to the form before executing is_valid .

+17
Jan 28
source share

I solved the same problem by overriding the validate_unique() method of 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 always make sure that an attribute not provided on the form is still available, for example. instance=Solution(problem=some_problem) in the initializer.

+35
Sep 21 '10 at 6:40
source share

As Felix says, ModelForms must check the unique_together constraint in their validation.

However, in your case, you actually exclude one element of this restriction from your form. I guess this is your problem - how will the form check the constraint if half of it is not even on the form?

+22
Jan 26 '10 at 20:19
source share

@sttwister's solution is correct, 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 return the object in case of duplication, but check if it exists in the database, while maintaining a little performance.

+7
May 5 '15 at 10:03
source share

With Yarmo's answer, the following looks good to me (in Django 1.3), but maybe I broke some kind of corner case (there are a lot of tickets surrounding _get_validation_exclusions ):

 class SolutionForm(forms.ModelForm): class Meta: model = Solution exclude = ['problem'] def _get_validation_exclusions(self): exclude = super(SolutionForm, self)._get_validation_exclusions() exclude.remove('problem') return exclude 

I'm not sure, but this seems like Django's mistake to me ... but I will have to look back at previously reported issues.




Edit: I spoke too soon. Perhaps what I wrote above will work in some situations, but not in mine; I ended up using Jarmo's answer directly.

+1
Aug 05 2018-11-11T00:
source share

You will need to do something like this:

 def your_view(request): if request.method == 'GET': form = SolutionForm() elif request.method == 'POST': problem = ... # logic to find the problem instance solution = Solution(problem=problem) # or solution.problem = problem form = SolutionForm(request.POST, instance=solution) # the form will validate because the problem has been provided on solution instance if form.is_valid(): solution = form.save() # redirect or return other response # show the form 
0
Jan 27 '10 at 9:54 on
source share

If you want the error message to be associated with the name field (and appear next to it):

 def clean(self): cleaned_data = super().clean() name_field = 'name' name = cleaned_data.get(name_field) if name: if Solution.objects.filter(name=name, problem=self.problem).exists(): cleaned_data.pop(name_field) # is also done by add_error self.add_error(name_field, _('There is already a solution with this name.')) return cleaned_data 
0
Jan 15 '16 at 9:45
source share

My solution is based on Django 2.1

Leave SolutionForm alone, you have save () method in Solution

 class Solution(models.Model): ... def save(self, *args, **kwargs): self.clean() return super(Solution, self).save(*args, **kwargs) def clean(): # have your custom model field checks here # They can raise Validation Error # Now this is the key to enforcing unique constraint self.validate_unique() 

The full_clean () call in save () does not work, because the ValidationError error is not being processed

0
Apr 26 '19 at 20:57
source share



All Articles