Skip to content

Commit

Permalink
Fixed #34744 -- Prevented recreation of migration for constraints wit…
Browse files Browse the repository at this point in the history
…h a dict_keys.

Co-authored-by: Mariusz Felisiak <[email protected]>
  • Loading branch information
shangxiao and felixxm committed Aug 23, 2023
1 parent dd45d52 commit 76c3e31
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
23 changes: 23 additions & 0 deletions django/db/models/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
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")

Expand Down Expand Up @@ -151,6 +153,27 @@ def deconstruct(self):
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:
"""
Expand Down
37 changes: 37 additions & 0 deletions tests/migrations/test_autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,43 @@ def test_add_constraints_with_new_model(self):
["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",
Expand Down
38 changes: 38 additions & 0 deletions tests/queries/test_q.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,44 @@ def test_reconstruct_and(self):
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])
Expand Down

0 comments on commit 76c3e31

Please sign in to comment.