views:

710

answers:

6

Hi,

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.

+2  A: 

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?

Daniel Roseman
Indeed that was the problem. So I guess that I can't get an error on the form without also including the problem field and that I'll have to manually check for this case.
sttwister
A: 

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
douglaz
The form still doesn't validate the `unique_together` constraint, probably because problem is mentioned in the `exclude` property, even though it has a valid instance
sttwister
+1  A: 

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:
            pass
        else:
            raise ValidationError('Solution with this Name already exists for this problem')
    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.

sttwister
+1  A: 

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.

Jarmo Jaakkola
A: 

I found Jarmo's solution great. Thanks, just what i was looking for: simple, DRY, and no need of redefining any validate_unique-messages.

andzep
A: 

Maybe it's too late to join this discussion. But I think Felix's info maybe out of date for version 1.2. I notice that by default, form's clean methon doesn't check unique_together in 1.2.

Georgie Porgie