Skip to content

Custom Validation with clean()

Right now, an animal can have two approved applications. Our rule needs a query: is another application for this animal already approved?

A rule about the whole row, not one field, goes in the model's clean() method. models.Model's clean() does nothing until we override it, and the admin calls it before it saves.

The clean() Method

Import ValidationError at the top of animals/models.py:

animals/models.py
from datetime import date, timedelta

from django.core.exceptions import ValidationError
from django.db import models

Then add clean() to AdoptionApplication, below save():

animals/models.py
    def clean(self):
        # Only an approval needs checking, and only once an animal is chosen.
        if self.status != self.Status.APPROVED or self.animal_id is None:
            return
        others = self.animal.applications.filter(status=self.Status.APPROVED)
        if others.exclude(pk=self.pk).exists():
            raise ValidationError(
                "Another application for this animal is already approved."
            )
  • self.animal.applications is the animal's applications, through the related_name.
  • filter() keeps the approved ones.
  • exclude(pk=self.pk) leaves out the application being saved. pk is short for primary key, the row's id here.
  • exists() returns True if any row matches.

A ValidationError raised with a message and no field name is an error about the whole application. The admin shows it with the form, and nothing is saved.

See It in the Admin

Pepper has an approved application from the last page. Open Pepper, set the other application to Approved, and click Save.

The admin shows "Please correct the error below." with our message on that row, and nothing is saved.

Two approvals saved at the same moment could both pass this check, and Django's documentation shows how a conditional UniqueConstraint makes the database refuse the second.