Fixed #28507 -- Made ValidationError.__eq__() ignore messages and params ordering.
Co-authored-by: caleb logan <clogan202@gmail.com>
This commit is contained in:
parent
16218c2060
commit
95da207bdb
|
@ -1,6 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Global Django exception and warning classes.
|
Global Django exception and warning classes.
|
||||||
"""
|
"""
|
||||||
|
import operator
|
||||||
|
|
||||||
|
from django.utils.hashable import make_hashable
|
||||||
|
|
||||||
|
|
||||||
class FieldDoesNotExist(Exception):
|
class FieldDoesNotExist(Exception):
|
||||||
|
@ -182,6 +185,23 @@ class ValidationError(Exception):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'ValidationError(%s)' % self
|
return 'ValidationError(%s)' % self
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, ValidationError):
|
||||||
|
return NotImplemented
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
if hasattr(self, 'error_dict'):
|
||||||
|
return hash(tuple(sorted(make_hashable(self.error_dict))))
|
||||||
|
return hash(tuple(sorted(self.error_list, key=operator.attrgetter('message'))))
|
||||||
|
|
||||||
|
|
||||||
class EmptyResultSet(Exception):
|
class EmptyResultSet(Exception):
|
||||||
"""A database query predicate is impossible."""
|
"""A database query predicate is impossible."""
|
||||||
|
|
|
@ -341,6 +341,9 @@ Validators
|
||||||
of a raised :exc:`~django.core.exceptions.ValidationError`. This allows
|
of a raised :exc:`~django.core.exceptions.ValidationError`. This allows
|
||||||
custom error messages to use the ``%(value)s`` placeholder.
|
custom error messages to use the ``%(value)s`` placeholder.
|
||||||
|
|
||||||
|
* The :class:`.ValidationError` equality operator now ignores ``messages`` and
|
||||||
|
``params`` ordering.
|
||||||
|
|
||||||
.. _backwards-incompatible-3.2:
|
.. _backwards-incompatible-3.2:
|
||||||
|
|
||||||
Backwards incompatible changes in 3.2
|
Backwards incompatible changes in 3.2
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
@ -14,3 +15,271 @@ class TestValidationError(unittest.TestCase):
|
||||||
message_dict['field2'] = ['E3', 'E4']
|
message_dict['field2'] = ['E3', 'E4']
|
||||||
exception = ValidationError(message_dict)
|
exception = ValidationError(message_dict)
|
||||||
self.assertEqual(sorted(exception.messages), ['E1', 'E2', 'E3', 'E4'])
|
self.assertEqual(sorted(exception.messages), ['E1', 'E2', 'E3', 'E4'])
|
||||||
|
|
||||||
|
def test_eq(self):
|
||||||
|
error1 = ValidationError('message')
|
||||||
|
error2 = ValidationError('message', code='my_code1')
|
||||||
|
error3 = ValidationError('message', code='my_code2')
|
||||||
|
error4 = ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
)
|
||||||
|
error5 = ValidationError({'field1': 'message', 'field2': 'other'})
|
||||||
|
error6 = ValidationError({'field1': 'message'})
|
||||||
|
error7 = ValidationError([
|
||||||
|
ValidationError({'field1': 'field error', 'field2': 'other'}),
|
||||||
|
'message',
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertEqual(error1, ValidationError('message'))
|
||||||
|
self.assertNotEqual(error1, ValidationError('message2'))
|
||||||
|
self.assertNotEqual(error1, error2)
|
||||||
|
self.assertNotEqual(error1, error4)
|
||||||
|
self.assertNotEqual(error1, error5)
|
||||||
|
self.assertNotEqual(error1, error6)
|
||||||
|
self.assertNotEqual(error1, error7)
|
||||||
|
self.assertEqual(error1, mock.ANY)
|
||||||
|
self.assertEqual(error2, ValidationError('message', code='my_code1'))
|
||||||
|
self.assertNotEqual(error2, ValidationError('other', code='my_code1'))
|
||||||
|
self.assertNotEqual(error2, error3)
|
||||||
|
self.assertNotEqual(error2, error4)
|
||||||
|
self.assertNotEqual(error2, error5)
|
||||||
|
self.assertNotEqual(error2, error6)
|
||||||
|
self.assertNotEqual(error2, error7)
|
||||||
|
|
||||||
|
self.assertEqual(error4, ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
))
|
||||||
|
self.assertNotEqual(error4, ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code2',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
))
|
||||||
|
self.assertNotEqual(error4, ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val2'},
|
||||||
|
))
|
||||||
|
self.assertNotEqual(error4, ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val1', 'parm1': 'val2'},
|
||||||
|
))
|
||||||
|
self.assertNotEqual(error4, ValidationError(
|
||||||
|
'error val1 val2',
|
||||||
|
code='my_code1',
|
||||||
|
))
|
||||||
|
# params ordering is ignored.
|
||||||
|
self.assertEqual(error4, ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val2', 'parm1': 'val1'},
|
||||||
|
))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
error5,
|
||||||
|
ValidationError({'field1': 'message', 'field2': 'other'}),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
error5,
|
||||||
|
ValidationError({'field1': 'message', 'field2': 'other2'}),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
error5,
|
||||||
|
ValidationError({'field1': 'message', 'field3': 'other'}),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(error5, error6)
|
||||||
|
# fields ordering is ignored.
|
||||||
|
self.assertEqual(
|
||||||
|
error5,
|
||||||
|
ValidationError({'field2': 'other', 'field1': 'message'}),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(error7, ValidationError(error7.error_list[1:]))
|
||||||
|
self.assertNotEqual(
|
||||||
|
ValidationError(['message']),
|
||||||
|
ValidationError([ValidationError('message', code='my_code')]),
|
||||||
|
)
|
||||||
|
# messages ordering is ignored.
|
||||||
|
self.assertEqual(
|
||||||
|
error7,
|
||||||
|
ValidationError(list(reversed(error7.error_list))),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(error4, ValidationError([error4]))
|
||||||
|
self.assertNotEqual(ValidationError([error4]), error4)
|
||||||
|
self.assertNotEqual(error4, ValidationError({'field1': error4}))
|
||||||
|
self.assertNotEqual(ValidationError({'field1': error4}), error4)
|
||||||
|
|
||||||
|
def test_eq_nested(self):
|
||||||
|
error_dict = {
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
}
|
||||||
|
error = ValidationError(error_dict)
|
||||||
|
self.assertEqual(error, ValidationError(dict(error_dict)))
|
||||||
|
self.assertEqual(error, ValidationError({
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm2': 'val2', 'parm1': 'val1'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
}))
|
||||||
|
self.assertNotEqual(error, ValidationError(
|
||||||
|
{**error_dict, 'field2': 'message'},
|
||||||
|
))
|
||||||
|
self.assertNotEqual(error, ValidationError({
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s val2',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm1': 'val1'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
}))
|
||||||
|
|
||||||
|
def test_hash(self):
|
||||||
|
error1 = ValidationError('message')
|
||||||
|
error2 = ValidationError('message', code='my_code1')
|
||||||
|
error3 = ValidationError('message', code='my_code2')
|
||||||
|
error4 = ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
)
|
||||||
|
error5 = ValidationError({'field1': 'message', 'field2': 'other'})
|
||||||
|
error6 = ValidationError({'field1': 'message'})
|
||||||
|
error7 = ValidationError([
|
||||||
|
ValidationError({'field1': 'field error', 'field2': 'other'}),
|
||||||
|
'message',
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertEqual(hash(error1), hash(ValidationError('message')))
|
||||||
|
self.assertNotEqual(hash(error1), hash(ValidationError('message2')))
|
||||||
|
self.assertNotEqual(hash(error1), hash(error2))
|
||||||
|
self.assertNotEqual(hash(error1), hash(error4))
|
||||||
|
self.assertNotEqual(hash(error1), hash(error5))
|
||||||
|
self.assertNotEqual(hash(error1), hash(error6))
|
||||||
|
self.assertNotEqual(hash(error1), hash(error7))
|
||||||
|
self.assertEqual(
|
||||||
|
hash(error2),
|
||||||
|
hash(ValidationError('message', code='my_code1')),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(error2),
|
||||||
|
hash(ValidationError('other', code='my_code1')),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(hash(error2), hash(error3))
|
||||||
|
self.assertNotEqual(hash(error2), hash(error4))
|
||||||
|
self.assertNotEqual(hash(error2), hash(error5))
|
||||||
|
self.assertNotEqual(hash(error2), hash(error6))
|
||||||
|
self.assertNotEqual(hash(error2), hash(error7))
|
||||||
|
|
||||||
|
self.assertEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
)))
|
||||||
|
self.assertNotEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code2',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
)))
|
||||||
|
self.assertNotEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val2'},
|
||||||
|
)))
|
||||||
|
self.assertNotEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val1', 'parm1': 'val2'},
|
||||||
|
)))
|
||||||
|
self.assertNotEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error val1 val2',
|
||||||
|
code='my_code1',
|
||||||
|
)))
|
||||||
|
# params ordering is ignored.
|
||||||
|
self.assertEqual(hash(error4), hash(ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code1',
|
||||||
|
params={'parm2': 'val2', 'parm1': 'val1'},
|
||||||
|
)))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
hash(error5),
|
||||||
|
hash(ValidationError({'field1': 'message', 'field2': 'other'})),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(error5),
|
||||||
|
hash(ValidationError({'field1': 'message', 'field2': 'other2'})),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(error5),
|
||||||
|
hash(ValidationError({'field1': 'message', 'field3': 'other'})),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(error5, error6)
|
||||||
|
# fields ordering is ignored.
|
||||||
|
self.assertEqual(
|
||||||
|
hash(error5),
|
||||||
|
hash(ValidationError({'field2': 'other', 'field1': 'message'})),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(error7),
|
||||||
|
hash(ValidationError(error7.error_list[1:])),
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(ValidationError(['message'])),
|
||||||
|
hash(ValidationError([ValidationError('message', code='my_code')])),
|
||||||
|
)
|
||||||
|
# messages ordering is ignored.
|
||||||
|
self.assertEqual(
|
||||||
|
hash(error7),
|
||||||
|
hash(ValidationError(list(reversed(error7.error_list)))),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(hash(error4), hash(ValidationError([error4])))
|
||||||
|
self.assertNotEqual(hash(ValidationError([error4])), hash(error4))
|
||||||
|
self.assertNotEqual(
|
||||||
|
hash(error4),
|
||||||
|
hash(ValidationError({'field1': error4})),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_hash_nested(self):
|
||||||
|
error_dict = {
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm2': 'val2', 'parm1': 'val1'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
}
|
||||||
|
error = ValidationError(error_dict)
|
||||||
|
self.assertEqual(hash(error), hash(ValidationError(dict(error_dict))))
|
||||||
|
self.assertEqual(hash(error), hash(ValidationError({
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s %(parm2)s',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm1': 'val1', 'parm2': 'val2'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
})))
|
||||||
|
self.assertNotEqual(hash(error), hash(ValidationError(
|
||||||
|
{**error_dict, 'field2': 'message'},
|
||||||
|
)))
|
||||||
|
self.assertNotEqual(hash(error), hash(ValidationError({
|
||||||
|
'field1': ValidationError(
|
||||||
|
'error %(parm1)s val2',
|
||||||
|
code='my_code',
|
||||||
|
params={'parm1': 'val1'},
|
||||||
|
),
|
||||||
|
'field2': 'other',
|
||||||
|
})))
|
||||||
|
|
Loading…
Reference in New Issue