summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sanders <shang.xiao.sanders@gmail.com>2023-07-27 17:07:48 +1000
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-08-23 11:42:18 +0200
commit76c3e310dd37a1d77642a8744db636a3a4337af2 (patch)
tree1cb2bd901d479d3a8f199775c2b7dd8df3f8c691
parentdd45d5223b3c5640baefcb591782bbcff873b6bf (diff)
Fixed #34744 -- Prevented recreation of migration for constraints with a dict_keys.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
-rw-r--r--django/db/models/query_utils.py23
-rw-r--r--tests/migrations/test_autodetector.py37
-rw-r--r--tests/queries/test_q.py38
3 files changed, 98 insertions, 0 deletions
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 78148f76b0..fcda30b3a7 100644
--- a/django/db/models/query_utils.py
+++ b/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:
"""
diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py
index 74892bbf3d..4c91659ca8 100644
--- a/tests/migrations/test_autodetector.py
+++ b/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",
diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py
index cdf40292b0..d3bab1f2a0 100644
--- a/tests/queries/test_q.py
+++ b/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])