#35550: UniqueConstraint with condition seems not checked in BaseInlineFormSet
---------------------------------------+------------------------
               Reporter:  Sergio Livi  |          Owner:  nobody
                   Type:  Bug          |         Status:  new
              Component:  Forms        |        Version:  5.0
               Severity:  Normal       |       Keywords:
           Triage Stage:  Unreviewed   |      Has patch:  0
    Needs documentation:  0            |    Needs tests:  0
Patch needs improvement:  0            |  Easy pickings:  0
                  UI/UX:  0            |
---------------------------------------+------------------------
 Hello,
 I spotted a corner case for which we have an `IntegrityError` instead of a
 `ValidationError`.

 I've put together a [https://github.com/serl/django-fieldset-unique-bug
 demo project with all the details], the most important follows.

 I have a model like so:

 {{{
 class Product(models.Model):
     design = models.ForeignKey(...)
     type = models.CharField(...)
     size = models.CharField(null=True, ...)
     ...

     class Meta:
         constraints = [
             models.UniqueConstraint(
                 fields=["design", "type"],
                 condition=models.Q(size__isnull=True),
                 name="unique_design_type",
             ),
             ...
         ]
 }}}

 I'm using the standard forms in the admin site.

 If I try to violate that `UniqueConstraint` from `ProductAdmin`, I
 correctly get a validation error. I materialized this check in a test case
 `TestFormValidation.test_validation_unique_design_type`.

 If I try to violate it from the formset in `ProductInlineAdmin` in
 `DesignAdmin`, I get a 500 error. The test case
 `TestFormSetValidation.test_validation_unique_design_type` shows the
 expected and actual behavior:

 {{{
 def test_validation_unique_design_type(self):
     ...

     formset = FormSet(
         data={
             f"{prefix}-INITIAL_FORMS": "0",
             f"{prefix}-TOTAL_FORMS": "2",
             f"{prefix}-MAX_NUM_FORMS": "1000",
             f"{prefix}-0-type": "Mug",
             f"{prefix}-0-price": "2",
             f"{prefix}-1-type": "Mug",
             f"{prefix}-1-price": "2",
         },
         instance=self.design,
     )

     # EXPECTED BEHAVIOR
     self.assertFalse(formset.is_valid())
     with self.assertRaisesMessage(ValueError, "didn't validate"):
         formset.save()

     # ACTUAL BEHAVIOR
     self.assertTrue(formset.is_valid())
     with self.assertRaisesMessage(IntegrityError, "UNIQUE constraint
 failed"):
         formset.save()
 }}}

 ''Note that in the demo project I also added a second `UniqueConstraint`
 with three fields and no condition, and that works like a charm (test
 cases there to prove). That second constraint actually show the pattern
 that I use in the main project, which is to have an unique constraint on
 three fields, one of which is nullable - that is `nulls_distinct=False`
 before updating to Django 5 and Postgres 15. But I digress.''

 As a workaround for may day job I implemented a custom `clean` in my
 inline formsets.

 If you agree on the buggy nature of this behavior, I'd like to participate
 with a PR. Let me know your thoughts.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35550>
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 on the web visit 
https://groups.google.com/d/msgid/django-updates/010701903f6c1f67-66314d54-f2bc-4822-828e-a676022cbf3c-000000%40eu-central-1.amazonses.com.

Reply via email to