Adding Custom Django Model Validation

Each Answer to this Q is separated by one/two green lines.

I have a Django model with a start and end date range. I want to enforce validation so that no two records have overlapping date ranges. What’s the simplest way to implement this so that I don’t have to repeat myself writing this logic?

e.g. I don’t want to re-implement this logic in a Form and a ModelForm and an admin form and the model’s overridden save().

As far as I know, Django doesn’t make it easy to globally enforce these types of criteria.

Googling hasn’t been very helpful, since “model validation” typically refers to validating specific model fields, and not the entire model contents, or relations between fields.

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.

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.

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])


The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .