#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.

Reply via email to