2
0
Эх сурвалжийг харах

Fixed #34744 -- Prevented recreation of migration for constraints with a dict_keys.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
David Sanders 1 жил өмнө
parent
commit
76c3e310dd

+ 23 - 0
django/db/models/query_utils.py

@@ -14,6 +14,8 @@ from django.core.exceptions import FieldError
 from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections
 from django.db.models.constants import LOOKUP_SEP
 from django.utils import tree
+from django.utils.functional import cached_property
+from django.utils.hashable import make_hashable
 
 logger = logging.getLogger("django.db.models")
 
@@ -151,6 +153,27 @@ class Q(tree.Node):
             kwargs["_negated"] = True
         return path, args, kwargs
 
+    @cached_property
+    def identity(self):
+        path, args, kwargs = self.deconstruct()
+        identity = [path, *kwargs.items()]
+        for child in args:
+            if isinstance(child, tuple):
+                arg, value = child
+                value = make_hashable(value)
+                identity.append((arg, value))
+            else:
+                identity.append(child)
+        return tuple(identity)
+
+    def __eq__(self, other):
+        if not isinstance(other, Q):
+            return NotImplemented
+        return other.identity == self.identity
+
+    def __hash__(self):
+        return hash(self.identity)
+
 
 class DeferredAttribute:
     """

+ 37 - 0
tests/migrations/test_autodetector.py

@@ -2793,6 +2793,43 @@ class AutodetectorTests(BaseAutodetectorTests):
             ["CreateModel", "AddField", "AddConstraint"],
         )
 
+    def test_add_constraints_with_dict_keys(self):
+        book_types = {"F": "Fantasy", "M": "Mystery"}
+        book_with_type = ModelState(
+            "testapp",
+            "Book",
+            [
+                ("id", models.AutoField(primary_key=True)),
+                ("type", models.CharField(max_length=1)),
+            ],
+            {
+                "constraints": [
+                    models.CheckConstraint(
+                        check=models.Q(type__in=book_types.keys()),
+                        name="book_type_check",
+                    ),
+                ],
+            },
+        )
+        book_with_resolved_type = ModelState(
+            "testapp",
+            "Book",
+            [
+                ("id", models.AutoField(primary_key=True)),
+                ("type", models.CharField(max_length=1)),
+            ],
+            {
+                "constraints": [
+                    models.CheckConstraint(
+                        check=models.Q(("type__in", tuple(book_types))),
+                        name="book_type_check",
+                    ),
+                ],
+            },
+        )
+        changes = self.get_changes([book_with_type], [book_with_resolved_type])
+        self.assertEqual(len(changes), 0)
+
     def test_add_index_with_new_model(self):
         book_with_index_title_and_pony = ModelState(
             "otherapp",

+ 38 - 0
tests/queries/test_q.py

@@ -200,6 +200,44 @@ class QTests(SimpleTestCase):
         path, args, kwargs = q.deconstruct()
         self.assertEqual(Q(*args, **kwargs), q)
 
+    def test_equal(self):
+        self.assertEqual(Q(), Q())
+        self.assertEqual(
+            Q(("pk__in", (1, 2))),
+            Q(("pk__in", [1, 2])),
+        )
+        self.assertEqual(
+            Q(("pk__in", (1, 2))),
+            Q(pk__in=[1, 2]),
+        )
+        self.assertEqual(
+            Q(("pk__in", (1, 2))),
+            Q(("pk__in", {1: "first", 2: "second"}.keys())),
+        )
+        self.assertNotEqual(
+            Q(name__iexact=F("other_name")),
+            Q(name=Lower(F("other_name"))),
+        )
+
+    def test_hash(self):
+        self.assertEqual(hash(Q()), hash(Q()))
+        self.assertEqual(
+            hash(Q(("pk__in", (1, 2)))),
+            hash(Q(("pk__in", [1, 2]))),
+        )
+        self.assertEqual(
+            hash(Q(("pk__in", (1, 2)))),
+            hash(Q(pk__in=[1, 2])),
+        )
+        self.assertEqual(
+            hash(Q(("pk__in", (1, 2)))),
+            hash(Q(("pk__in", {1: "first", 2: "second"}.keys()))),
+        )
+        self.assertNotEqual(
+            hash(Q(name__iexact=F("other_name"))),
+            hash(Q(name=Lower(F("other_name")))),
+        )
+
     def test_flatten(self):
         q = Q()
         self.assertEqual(list(q.flatten()), [q])