From 4c675523bd3e45906bf6736444c65b066e25208c Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 5 Oct 2020 09:13:14 -0400 Subject: [PATCH] Refs #29838, Refs #28507 -- Made make_hashable() ignore key order. --- django/core/exceptions.py | 5 ++--- django/utils/hashable.py | 7 ++++++- tests/utils_tests/test_hashable.py | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/django/core/exceptions.py b/django/core/exceptions.py index 5780ffdb4a..673d004d57 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -196,15 +196,14 @@ class ValidationError(Exception): return hash(self) == hash(other) def __hash__(self): - # Ignore params and messages ordering. if hasattr(self, 'message'): return hash(( self.message, self.code, - tuple(sorted(make_hashable(self.params))) if self.params else None, + make_hashable(self.params), )) if hasattr(self, 'error_dict'): - return hash(tuple(sorted(make_hashable(self.error_dict)))) + return hash(make_hashable(self.error_dict)) return hash(tuple(sorted(self.error_list, key=operator.attrgetter('message')))) diff --git a/django/utils/hashable.py b/django/utils/hashable.py index 0bef5b003d..7d137ccc2f 100644 --- a/django/utils/hashable.py +++ b/django/utils/hashable.py @@ -2,10 +2,15 @@ from django.utils.itercompat import is_iterable def make_hashable(value): + """ + Attempt to make value hashable or raise a TypeError if it fails. + + The returned value should generate the same hash for equal values. + """ if isinstance(value, dict): return tuple([ (key, make_hashable(nested_value)) - for key, nested_value in value.items() + for key, nested_value in sorted(value.items()) ]) # Try hash to avoid converting a hashable iterable (e.g. string, frozenset) # to a tuple. diff --git a/tests/utils_tests/test_hashable.py b/tests/utils_tests/test_hashable.py index b4db3ef7d7..d267b112cc 100644 --- a/tests/utils_tests/test_hashable.py +++ b/tests/utils_tests/test_hashable.py @@ -10,7 +10,8 @@ class TestHashable(SimpleTestCase): ({}, ()), ({'a'}, ('a',)), (frozenset({'a'}), {'a'}), - ({'a': 1}, (('a', 1),)), + ({'a': 1, 'b': 2}, (('a', 1), ('b', 2))), + ({'b': 2, 'a': 1}, (('a', 1), ('b', 2))), (('a', ['b', 1]), ('a', ('b', 1))), (('a', {'b': 1}), ('a', (('b', 1),))), )