[4.2.x] Fixed #34291 -- Fixed Meta.constraints validation crash on UniqueConstraint with ordered expressions.

Thanks Dan F for the report.

Bug in 667105877e.
Backport of 2b1242abb3 from main
This commit is contained in:
Mariusz Felisiak 2023-01-26 09:31:40 +01:00
parent d43fbdf6f1
commit 719a14badc
3 changed files with 32 additions and 6 deletions

View File

@ -2,7 +2,7 @@ from enum import Enum
from django.core.exceptions import FieldError, ValidationError
from django.db import connections
from django.db.models.expressions import Exists, ExpressionList, F
from django.db.models.expressions import Exists, ExpressionList, F, OrderBy
from django.db.models.indexes import IndexExpression
from django.db.models.lookups import Exact
from django.db.models.query_utils import Q
@ -338,10 +338,12 @@ class UniqueConstraint(BaseConstraint):
meta=model._meta, exclude=exclude
).items()
}
expressions = [
Exact(expr, expr.replace_expressions(replacements))
for expr in self.expressions
]
expressions = []
for expr in self.expressions:
# Ignore ordering.
if isinstance(expr, OrderBy):
expr = expr.expression
expressions.append(Exact(expr, expr.replace_expressions(replacements)))
queryset = queryset.filter(*expressions)
model_class_pk = instance._get_pk_val(model._meta)
if not instance._state.adding and model_class_pk is not None:

View File

@ -10,4 +10,5 @@ in 4.1.5.
Bugfixes
========
* ...
* Fixed a bug in Django 4.1 that caused a crash of model validation on
``UniqueConstraint`` with ordered expressions (:ticket:`34291`).

View File

@ -672,6 +672,29 @@ class UniqueConstraintTests(TestCase):
exclude={"name"},
)
def test_validate_ordered_expression(self):
constraint = models.UniqueConstraint(
Lower("name").desc(), name="name_lower_uniq_desc"
)
msg = "Constraint “name_lower_uniq_desc” is violated."
with self.assertRaisesMessage(ValidationError, msg):
constraint.validate(
UniqueConstraintProduct,
UniqueConstraintProduct(name=self.p1.name.upper()),
)
constraint.validate(
UniqueConstraintProduct,
UniqueConstraintProduct(name="another-name"),
)
# Existing instances have their existing row excluded.
constraint.validate(UniqueConstraintProduct, self.p1)
# Unique field is excluded.
constraint.validate(
UniqueConstraintProduct,
UniqueConstraintProduct(name=self.p1.name.upper()),
exclude={"name"},
)
def test_validate_expression_condition(self):
constraint = models.UniqueConstraint(
Lower("name"),