mirror of https://github.com/django/django.git
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:
parent
dd45d5223b
commit
76c3e310dd
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Reference in New Issue