[Django] #36668: ModelForm validation skips UniqueConstraint if its condition refers to a field not present on the form

8 views
Skip to first unread message

Django

unread,
Oct 16, 2025, 11:41:29 AM10/16/25
to django-...@googlegroups.com
#36668: ModelForm validation skips UniqueConstraint if its condition refers to a
field not present on the form
-------------------------------------+-------------------------------------
Reporter: Baptiste Mispelon | Type: Bug
Status: new | Component: Database
| layer (models, ORM)
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage:
modelform,constraint,uniqueconstraint| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
,,(sorry for this mouthful of a title 😁),,

For starters, I'm not sure if this is a bug or a feature request but it
feels like a bug to me so I went with that. Similarly, I'm unsure if this
is a `ModelForm` issue or a `UniqueConstraint` one and went with the
latter.

Now for the actual issue, consider this code (see attached patch for an
actual testcase [1] ):

{{{#!python
from django import forms
from django.db import models


class Page(models.Model):
pass


class Revision(models.Model):
page = models.ForeignKey(Page, on_delete=models.CASCADE)
status = models.IntegerField(default=1)

class Meta:
constraints = [
models.UniqueConstraint(
name="unique_page_status_1",
fields=["page"],
condition=models.Q(status=1),
)
]


class RevisionForm(forms.ModelForm):
class Meta:
model = Revision
fields = ["page"]


page = Page.objects.create()
page.revision_set.create()

form = RevisionForm(data={"page": page.pk})
# I would explain the form to be invalid because the revision that would
be
# created by saving the form would fail against the constraint.
# But that's not what happens:
assert not form.is_valid() # fails
}}}
(I've tested as far back as 4.1 and they all exhibit the same issue.)

I've investigated a little and here's what I think is happening: the
`ModelForm` ends up calling `UniqueConstraint.validate()` and passing it
an `exclude` list that contains `status` (because `status` is a field on
the model, but not on the form). In turn, the logic in
`UniqueConstraint.validate()` notices that `status` is present in its
`condition`, and so decides to skip that validation.

I think that last step where `UniqueConstraint.validate()` skips fields
used in the constraint's `condition` if they're listed in `exclude` is a
bug. I think this behavior makes sense for `CheckConstraint`, but not for
`UniqueConstraint`. In my mind `UniqueConstraint.validate()` should only
check `self.fields` and ignore `self.condition`.



[1] Once the patch is applied with `git apply ticket-xxx.diff` you can run
it with `uv run --with-editable=. --with-
requirements=tests/requirements/py3.txt python tests/runtests.py aaaaaa`
--
Ticket URL: <https://code.djangoproject.com/ticket/36668>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Oct 16, 2025, 11:42:07 AM10/16/25
to django-...@googlegroups.com
#36668: ModelForm validation skips UniqueConstraint if its condition refers to a
field not present on the form
-------------------------------------+-------------------------------------
Reporter: Baptiste Mispelon | Owner: (none)
Type: Bug | Status: new
Component: Database layer | Version: 5.2
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
modelform,constraint,uniqueconstraint| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Baptiste Mispelon):

* Attachment "ticket-36668-tests.diff" added.

Django

unread,
Oct 16, 2025, 1:12:20 PM10/16/25
to django-...@googlegroups.com
#36668: ModelForm validation skips UniqueConstraint if its condition refers to a
field not present on the form
-------------------------------------+-------------------------------------
Reporter: Baptiste Mispelon | Owner: (none)
Type: Bug | Status: new
Component: Database layer | Version: 5.2
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
modelform,constraint,uniqueconstraint| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Simon Charette):

This was discussed on the forum extensively so
[https://forum.djangoproject.com/t/fields-excluded-from-model-constraint-
validation/37597/4 I'll drop a link here].

The TL;DR is by design as `unique_together` has always worked this way (if
a unique constraint refers to any excluded fields then it's not validated)
so we followed the same approach for `Meta.constraints`.
--
Ticket URL: <https://code.djangoproject.com/ticket/36668#comment:1>

Django

unread,
Oct 16, 2025, 1:45:37 PM10/16/25
to django-...@googlegroups.com
#36668: ModelForm validation skips UniqueConstraint if its condition refers to a
field not present on the form
-------------------------------------+-------------------------------------
Reporter: Baptiste Mispelon | Owner: (none)
Type: Bug | Status: closed
Component: Database layer | Version: 5.2
(models, ORM) |
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage:
modelform,constraint,uniqueconstraint| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* resolution: => invalid
* status: new => closed

Comment:

Happy to look at a documentation improvement here -- the closest thing I
could find was (emphasis added):

> You should only need to call a model’s full_clean() method if you plan
to handle validation errors yourself, **or if you have excluded fields
from the ModelForm that require validation**.
--
Ticket URL: <https://code.djangoproject.com/ticket/36668#comment:2>
Reply all
Reply to author
Forward
0 new messages