[Django] #33996: Inconsistent behaviour validating constraints between Django and the database

22 views
Skip to first unread message

Django

unread,
Sep 8, 2022, 12:30:08 AM9/8/22
to django-...@googlegroups.com
#33996: Inconsistent behaviour validating constraints between Django and the
database
-------------------------------------+-------------------------------------
Reporter: James | Owner: nobody
Beith |
Type: Bug | Status: new
Component: Database | Version: 4.1
layer (models, ORM) |
Severity: Normal | Keywords:
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
It appears that Django and the database validate check constraints
differently.

Let's say we have this model.

{{{
class Person(models.Model):
age = models.PositiveIntegerField(blank=True, null=True)

class Meta:
constraints = [
models.CheckConstraint(check=Q(age__gte=18), name="age_gte_18")
]
}}}

If we try to create a person in the database it will raise an error if we
violate the constraint.

{{{
>>> Person.objects.create(age=15)
IntegrityError: new row for relation "data_person" violates check
constraint "age_gte_18"
DETAIL: Failing row contains (1, 15).
}}}

And if the age is `None` then the database will not check the constraint,
so the object is saved to the database.

{{{
>>> Person.objects.create(age=None)
<Person: Person object (2)>
}}}

In Django 4.1 we have this new behaviour

> Check, unique, and exclusion constraints defined in the Meta.constraints
option are now checked during model validation.

So if we do this, we do still get the same behaviour as the database.

{{{
>>> person = Person(age=15)
>>> person.full_clean()
ValidationError: {'__all__': ['Constraint "age_gte_18" is violated.']}
}}}

But if the age is `None` we now get the validation error, even though the
database will happily save the object.

{{{
>>> person = Person(age=None)
>>> person.full_clean()
ValidationError: {'__all__': ['Constraint "age_gte_18" is violated.']}
>>> person.save() # successfully saves to the database
}}}

Is this the expected behaviour?

Should Django's validation be taking into account if the field's define
with `null=True` and not raise the validation error (so same behaviour as
the database)?

Alternatively, is it that the constraint needs updating to also support
null so the Django validation passes? e.g.

{{{
models.CheckConstraint(check=Q(age__isnull=True) | Q(age__gte=18),
name="age_gte_18")
}}}

Note: Tested with PostgreSQL, not sure if different databases treat nulls
as not breaking the constraint differently.

--
Ticket URL: <https://code.djangoproject.com/ticket/33996>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Reply all
Reply to author
Forward
0 new messages