diff options
| author | Simon Charette <charette.s@gmail.com> | 2025-02-19 03:04:16 -0500 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-08-04 09:42:32 +0200 |
| commit | b3bb7230e1225861b5c1f08931f2d82c2b04133a (patch) | |
| tree | 0832739f5f549254b63f1c4fbeb42040e241de2b /tests | |
| parent | e5ccb69bc3da407ab2b0477c0cc5db27c7207225 (diff) | |
[5.2.x] Fixed #34871, #36518 -- Implemented unresolved lookups expression replacement.
This allows the proper resolving of lookups when performing constraint
validation involving Q and Case objects.
Thanks Andrew Roberts for the report and Sarah for the tests and review.
Backport of 079d31e698fa08dd92e2bc4f3fe9b4817a214419 from main.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/constraints/tests.py | 19 | ||||
| -rw-r--r-- | tests/queries/test_q.py | 64 |
2 files changed, 80 insertions, 3 deletions
diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index e67a32b806..96cd1c25ef 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -3,7 +3,7 @@ from unittest import mock from django.core.exceptions import ValidationError from django.db import IntegrityError, connection, models -from django.db.models import F +from django.db.models import Case, F, When from django.db.models.constraints import BaseConstraint, UniqueConstraint from django.db.models.functions import Abs, Lower, Sqrt, Upper from django.db.transaction import atomic @@ -1077,6 +1077,23 @@ class UniqueConstraintTests(TestCase): UniqueConstraintProduct(updated=updated_date + timedelta(days=1)), ) + def test_validate_case_when(self): + UniqueConstraintProduct.objects.create(name="p1") + constraint = models.UniqueConstraint( + Case(When(color__isnull=True, then=F("name"))), + name="name_without_color_uniq", + ) + msg = "Constraint “name_without_color_uniq” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate( + UniqueConstraintProduct, + UniqueConstraintProduct(name="p1"), + ) + constraint.validate( + UniqueConstraintProduct, + UniqueConstraintProduct(name="p1", color="green"), + ) + def test_validate_ordered_expression(self): constraint = models.UniqueConstraint( Lower("name").desc(), name="name_lower_uniq_desc" diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py index f37d7becac..1a62aca061 100644 --- a/tests/queries/test_q.py +++ b/tests/queries/test_q.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.core.exceptions import FieldError from django.db import connection from django.db.models import ( @@ -10,8 +12,13 @@ from django.db.models import ( Value, ) from django.db.models.expressions import NegatedExpression, RawSQL -from django.db.models.functions import Lower -from django.db.models.lookups import Exact, IsNull +from django.db.models.functions import ExtractDay, Lower, TruncDate +from django.db.models.lookups import ( + Exact, + IntegerFieldExact, + IntegerLessThanOrEqual, + IsNull, +) from django.db.models.sql.where import NothingNode from django.test import SimpleTestCase, TestCase @@ -292,6 +299,59 @@ class QTests(SimpleTestCase): expected_base_fields, ) + def test_replace_expressions(self): + replacements = {F("timestamp"): Value(None)} + self.assertEqual( + Q(timestamp__date__day=25).replace_expressions(replacements), + Q(timestamp__date__day=25), + ) + replacements = {F("timestamp"): Value(datetime(2025, 10, 23))} + self.assertEqual( + Q(timestamp__date__day=13).replace_expressions(replacements), + Q( + IntegerFieldExact( + ExtractDay(TruncDate(Value(datetime(2025, 10, 23)))), + 13, + ) + ), + ) + self.assertEqual( + Q(timestamp__date__day__lte=25).replace_expressions(replacements), + Q( + IntegerLessThanOrEqual( + ExtractDay(TruncDate(Value(datetime(2025, 10, 23)))), + 25, + ) + ), + ) + self.assertEqual( + ( + Q(Q(timestamp__date__day__lte=25), timestamp__date__day=13) + ).replace_expressions(replacements), + ( + Q( + Q( + IntegerLessThanOrEqual( + ExtractDay(TruncDate(Value(datetime(2025, 10, 23)))), + 25, + ) + ), + IntegerFieldExact( + ExtractDay(TruncDate(Value(datetime(2025, 10, 23)))), + 13, + ), + ) + ), + ) + self.assertEqual( + Q(timestamp=None).replace_expressions(replacements), + Q(IsNull(Value(datetime(2025, 10, 23)), True)), + ) + self.assertEqual( + Q(timestamp__date__day__invalid=25).replace_expressions(replacements), + Q(timestamp__date__day__invalid=25), + ) + class QCheckTests(TestCase): def test_basic(self): |
