Adding Custom Django Model Validation

I think you should use this: https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects

Just define clean() method in your model like this: (example from the docs link)

def clean(self):
    from django.core.exceptions import ValidationError
    # Don't allow draft entries to have a pub_date.
    if self.status == 'draft' and self.pub_date is not None:
        raise ValidationError('Draft entries may not have a publication date.')
    # Set the pub_date for published items if it hasn't been set already.
    if self.status == 'published' and self.pub_date is None:
        self.pub_date = datetime.datetime.now()

I think this can help you, We can create multiple validators like this use in models.

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.db import models

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(
            _('%(value)s is not an even number'),
            params={'value': value},
        )

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

I would override the validate_unique method on the model. To make sure you ignore the current object when validating, you can use the following:

from django.db.models import Model, DateTimeField
from django.core.validators import NON_FIELD_ERRORS, ValidationError

class MyModel(Model):
    start_date = DateTimeField()
    end_date = DateTimeField()

    def validate_unique(self, *args, **kwargs):
        super(MyModel, self).validate_unique(*args, **kwargs)

        qs = self.__class__._default_manager.filter(
            start_date__lt=self.end_date,
            end_date__gt=self.start_date
        )

        if not self._state.adding and self.pk is not None:
            qs = qs.exclude(pk=self.pk)

        if qs.exists():
            raise ValidationError({
                NON_FIELD_ERRORS: ['overlapping date range',],
            })

ModelForm will automatically call this for you through a full_clean(), which you can use manually too.

PPR has a nice discussion of a simple, correct range overlap condition.


The basic pattern I've found useful is to put all my custom validation in clean() and then simply call full_clean() (which calls clean() and a few other methods) from inside save(), e.g.:

class BaseModel(models.Model):
    
    def clean(self, *args, **kwargs):
        # add custom validation here
        super().clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

This isn't done by default, as explained here, because it interferes with certain features, but those aren't a problem for my application.