From 719a14badcd226732c9c1172f8183e5f42e54241 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 26 Jan 2023 09:31:40 +0100 Subject: [PATCH] [4.2.x] Fixed #34291 -- Fixed Meta.constraints validation crash on UniqueConstraint with ordered expressions. Thanks Dan F for the report. Bug in 667105877e6723c6985399803a364848891513cc. Backport of 2b1242abb3989f5d74e787b09132d01bcbee5b55 from main --- django/db/models/constraints.py | 12 +++++++----- docs/releases/4.1.6.txt | 3 ++- tests/constraints/tests.py | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 49c7c91de95..c2c7bb06f67 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 a581572a9a5..e97c25aea0b 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 5a498f0d734..223b4b3cd5f 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"),