Opened 16 months ago
Closed 16 months ago
#35550 closed Bug (duplicate)
UniqueConstraint with condition seems not checked in BaseInlineFormSet
| Reported by: | Serl | Owned by: | nobody |
|---|---|---|---|
| Component: | Forms | Version: | 5.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Hello,
I spotted a corner case for which we have an IntegrityError instead of a ValidationError.
I've put together a 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.
Thanks for the ticket.
UniqueConstraintwithexpressionsand partial unique constraints are ignored on purpose, see #33335.Duplicate of #23964.