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

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
David Sanders 2023-07-27 17:07:48 +10:00 committed by Mariusz Felisiak
parent dd45d5223b
commit 76c3e310dd
3 changed files with 98 additions and 0 deletions

View File

@ -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:
"""

View File

@ -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",

View File

@ -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])