#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.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/01070199edaed534-33fd3256-a7b0-43e1-8408-8e0cc7a3f71f-000000%40eu-central-1.amazonses.com.