Adding Custom Django Model Validation
Solution 1:
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.
Solution 2:
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.
Solution 3:
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()
Solution 4:
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])