Opened 6 months ago

Closed 6 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.

Change History (1)

comment:1 by Mariusz Felisiak, 6 months ago

Resolution: duplicate
Status: newclosed

Thanks for the ticket. UniqueConstraint with expressions and partial unique constraints are ignored on purpose, see #33335.

Duplicate of #23964.

Note: See TracTickets for help on using tickets.
Back to Top