Django's ModelForm unique_together validation

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.


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.


With the help of Jarmo's answer, the following seems to work nicely for me (in Django 1.3), but it's possible I've broken some 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 a Django bug to me... but I'd have to look around the previously-reported issues.


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