diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 5470a357cf..43023b67fb 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -381,10 +381,12 @@ class BaseExpression: def __hash__(self): path, args, kwargs = self.deconstruct() - h = hash(path) ^ hash(args) - for kwarg in kwargs.items(): - h ^= hash(kwarg) - return h + kwargs = kwargs.copy() + output_field = type(kwargs.pop('output_field', None)) + return hash((path, output_field) + args + tuple([ + (key, tuple(value)) if isinstance(value, list) else (key, value) + for key, value in kwargs.items() + ])) class Expression(BaseExpression, Combinable): diff --git a/docs/releases/2.0.3.txt b/docs/releases/2.0.3.txt index c0c9ebb782..348af75bc0 100644 --- a/docs/releases/2.0.3.txt +++ b/docs/releases/2.0.3.txt @@ -21,3 +21,6 @@ Bugfixes * Made ``Q.deconstruct()`` deterministic with multiple keyword arguments (:ticket:`29125`). You may need to modify ``Q``'s in existing migrations, or accept an autogenerated migration. + +* Fixed a regression where a ``When()`` expression with a list argument crashes + (:ticket:`29166`). diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py index 090607e8b8..256be935ef 100644 --- a/tests/expressions_case/tests.py +++ b/tests/expressions_case/tests.py @@ -1293,3 +1293,21 @@ class CaseDocumentationExamples(TestCase): [('Jack Black', 'P')], transform=attrgetter('name', 'account_type') ) + + def test_hash(self): + expression_1 = Case( + When(account_type__in=[Client.REGULAR, Client.GOLD], then=1), + default=2, + output_field=models.IntegerField(), + ) + expression_2 = Case( + When(account_type__in=(Client.REGULAR, Client.GOLD), then=1), + default=2, + output_field=models.IntegerField(), + ) + expression_3 = Case(When(account_type__in=[Client.REGULAR, Client.GOLD], then=1), default=2) + expression_4 = Case(When(account_type__in=[Client.PLATINUM, Client.GOLD], then=2), default=1) + self.assertEqual(hash(expression_1), hash(expression_2)) + self.assertNotEqual(hash(expression_2), hash(expression_3)) + self.assertNotEqual(hash(expression_1), hash(expression_4)) + self.assertNotEqual(hash(expression_3), hash(expression_4))