29

I have a model similar to this one:

class Person(models.Model):
    field1 = models.IntegerField(null=True)
    field2 = models.IntegerField(null=True)

At least one of the fields field1 or field2 must not be null. How I can I verify it in the model?

Florian
  • 2,562
  • 5
  • 25
  • 35
user972014
  • 3,296
  • 6
  • 49
  • 89

3 Answers3

39

Model.clean

One normally writes such tests in Model.clean [Django-doc]:

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

class Person(models.Model):
    field1 = models.IntegerField(null=True)
    field2 = models.IntegerField(null=True)

    def clean(self):
        super().clean()
        if self.field1 is None and self.field2 is None:
            raise ValidationError('Field1 or field2 are both None')

Note that this clean method is not validated by default when you .save() a model. It is typically only called by ModelForms built on top of this model. You can patch the .save() method for example like here to enforce validation when you .save() the model instance, but still there are ways to circumvent this through the ORM.

django-db-constraints (not supported by some databases)

If your database supports it (for example MySQL simply ignores the CHECK constraints), SQL offers a syntax to add extra constraints, and a Django package django-db-constraints [GitHub] provides some tooling to specify such constraints, like:

from django.db import models

class Person(models.Model):
    field1 = models.IntegerField(null=True)
    field2 = models.IntegerField(null=True)

    class Meta:
        db_constraints = {
            'field_null': 'CHECK (field1 IS NOT NULL OR field2 IS NOT NULL)',
        }

Update: Django constraint framework

Since , you can make use of the Django constraint framework [Django-doc]. With this framework, you can specify database constraints that are, given the database supports this, validated at database side. You thus can check if at least one of the two fields is not NULL with a CheckConstraint [Django-doc]:

from django.db import models
from django.db.models import Q

class Person(models.Model):
    field1 = models.IntegerField(null=True)
    field2 = models.IntegerField(null=True)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=Q(field1__isnull=False) | Q(field2__isnull=False),
                name='not_both_null'
            )
        ]
Florian
  • 2,562
  • 5
  • 25
  • 35
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    What can we do if one of the two (field1 or field2) should be defined. Both cannot be null and also both cannot be defined. This is like Exclusive OR (XOR) but `models.Q` does not seem to support this. – Shubham Dhingra Jan 26 '22 at 16:12
  • 5
    @ShubhamDhingra: you can convert that into disjunctive normal form, so `Q(field1__isnull=False, field2=None) | Q(field1=None, field2__isnull=False)` – Willem Van Onsem Jan 26 '22 at 16:13
  • Don't forget to import Q object using `from django.db.models import Q` to avoid *Q is not defined error*. – Joe May 28 '22 at 20:45
5

You can use Model.clean() method:

def clean(self):
    if self.field1 is None and self.field2 is None:
        raise ValidationError(_('field1 or field2 should not be null'))

See https://docs.djangoproject.com/en/2.1/ref/models/instances/#django.db.models.Model.clean

Satevg
  • 1,601
  • 2
  • 16
  • 23
4

Since Django 2.2, you can use the built-in constraints feature in Django. No need for 3rd party code.

Daniel Quinn
  • 6,010
  • 6
  • 38
  • 61