Refs #29838, Refs #28507 -- Made make_hashable() ignore key order.

This commit is contained in:
Simon Charette 2020-10-05 09:13:14 -04:00 committed by Mariusz Felisiak
parent 981a3426cf
commit 4c675523bd
3 changed files with 10 additions and 5 deletions

View File

@ -196,15 +196,14 @@ class ValidationError(Exception):
return hash(self) == hash(other) return hash(self) == hash(other)
def __hash__(self): def __hash__(self):
# Ignore params and messages ordering.
if hasattr(self, 'message'): if hasattr(self, 'message'):
return hash(( return hash((
self.message, self.message,
self.code, self.code,
tuple(sorted(make_hashable(self.params))) if self.params else None, make_hashable(self.params),
)) ))
if hasattr(self, 'error_dict'): 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')))) return hash(tuple(sorted(self.error_list, key=operator.attrgetter('message'))))

View File

@ -2,10 +2,15 @@ from django.utils.itercompat import is_iterable
def make_hashable(value): 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): if isinstance(value, dict):
return tuple([ return tuple([
(key, make_hashable(nested_value)) (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) # Try hash to avoid converting a hashable iterable (e.g. string, frozenset)
# to a tuple. # to a tuple.

View File

@ -10,7 +10,8 @@ class TestHashable(SimpleTestCase):
({}, ()), ({}, ()),
({'a'}, ('a',)), ({'a'}, ('a',)),
(frozenset({'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))),
(('a', {'b': 1}), ('a', (('b', 1),))), (('a', {'b': 1}), ('a', (('b', 1),))),
) )