Validating constraints across multiple form fields in Django

After all fields of a Django form have been validated in isolation, the Form().clean(self) method is called to conclude the validation process. This method is meant to house validation logic not associated with one field in particular.

For example, let’s suppose we have an application where our users can order gourmet-level cat & dog food. For some awkward legal reason, though, the amount of cat & dog food items taken together cannot exceed 50 items per order.

Clearly, this requirement cannot be expressed in relation to only one field. Rather, two values have to be taken into account together during form validation. The below code sample illustrates a solution to our example use case:

from django import forms


class PetFoodForm(forms.Form):
    cat_cans = forms.IntegerField(initial=0, min_value=0)
    dog_cans = forms.IntegerField(initial=0, min_value=0)

    def clean(self):
        cleaned_data = super().clean()
        cat_cans = cleaned_data.get("cat_cans")
        dog_cans = cleaned_data.get("dog_cans")

        if cat_cans and dog_cans and (cat_cans + dog_cans > 50):
            raise forms.ValidationError("The number of selected items exceeds 50.")


form1 = PetFoodForm({
    'dog_cans': '15',
    'cat_cans': '15'
})
assert form1.is_valid()

form2 = PetFoodForm({
    'dog_cans': '30',
    'cat_cans': '30'
})
assert not form2.is_valid()