summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2025-02-19 03:04:16 -0500
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-08-04 09:42:32 +0200
commitb3bb7230e1225861b5c1f08931f2d82c2b04133a (patch)
tree0832739f5f549254b63f1c4fbeb42040e241de2b /tests
parente5ccb69bc3da407ab2b0477c0cc5db27c7207225 (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.py19
-rw-r--r--tests/queries/test_q.py64
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):