diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 49c7c91de9..c2c7bb06f6 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -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: diff --git a/docs/releases/4.1.6.txt b/docs/releases/4.1.6.txt index a581572a9a..e97c25aea0 100644 --- a/docs/releases/4.1.6.txt +++ b/docs/releases/4.1.6.txt @@ -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`). diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index 5a498f0d73..223b4b3cd5 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -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"),