diff --git a/django/core/exceptions.py b/django/core/exceptions.py index 87b173b9b0..78f6d07cf3 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -4,6 +4,7 @@ Global Django exception and warning classes. from functools import reduce import operator +from django.utils import six from django.utils.encoding import force_text @@ -84,10 +85,17 @@ class ValidationError(Exception): list or dictionary can be an actual `list` or `dict` or an instance of ValidationError with its `error_list` or `error_dict` attribute set. """ + + # PY2 can't pickle naive exception: http://bugs.python.org/issue1692335. + super(ValidationError, self).__init__(message, code, params) + if isinstance(message, ValidationError): if hasattr(message, 'error_dict'): message = message.error_dict - elif not hasattr(message, 'message'): + # PY2 has a `message` property which is always there so we can't + # duck-type on it. It was introduced in Python 2.5 and already + # deprecated in Python 2.6. + elif not hasattr(message, 'message' if six.PY3 else 'code'): message = message.error_list else: message, code, params = message.message, message.code, message.params diff --git a/tests/validation/test_picklable.py b/tests/validation/test_picklable.py new file mode 100644 index 0000000000..a8d84dba20 --- /dev/null +++ b/tests/validation/test_picklable.py @@ -0,0 +1,41 @@ +import pickle +from unittest import TestCase + +from django.core.exceptions import ValidationError + + +class PickableValidationErrorTestCase(TestCase): + + def test_validationerror_is_picklable(self): + original = ValidationError('a', code='something') + unpickled = pickle.loads(pickle.dumps(original)) + self.assertIs(unpickled, unpickled.error_list[0]) + self.assertEqual(original.message, unpickled.message) + self.assertEqual(original.code, unpickled.code) + + original = ValidationError('a', code='something') + unpickled = pickle.loads(pickle.dumps(ValidationError(original))) + self.assertIs(unpickled, unpickled.error_list[0]) + self.assertEqual(original.message, unpickled.message) + self.assertEqual(original.code, unpickled.code) + + original = ValidationError(['a', 'b']) + unpickled = pickle.loads(pickle.dumps(original)) + self.assertEqual(original.error_list[0].message, unpickled.error_list[0].message) + self.assertEqual(original.error_list[1].message, unpickled.error_list[1].message) + + original = ValidationError(['a', 'b']) + unpickled = pickle.loads(pickle.dumps(ValidationError(original))) + self.assertEqual(original.error_list[0].message, unpickled.error_list[0].message) + self.assertEqual(original.error_list[1].message, unpickled.error_list[1].message) + + original = ValidationError([ValidationError('a'), ValidationError('b')]) + unpickled = pickle.loads(pickle.dumps(original)) + self.assertIs(unpickled.args[0][0], unpickled.error_list[0]) + self.assertEqual(original.error_list[0].message, unpickled.error_list[0].message) + self.assertEqual(original.error_list[1].message, unpickled.error_list[1].message) + + message_dict = {'field1': ['a', 'b'], 'field2': ['c', 'd']} + original = ValidationError(message_dict) + unpickled = pickle.loads(pickle.dumps(original)) + self.assertEqual(unpickled.message_dict, message_dict)