mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-08 14:24:48 +03:00
Dont change return type of Serializer.errors
We should keep `Serializer.errors`'s return type unchanged in order to maintain backward compatibility. The error codes will only be propagated to the `exception_handler` or accessible through the `Serializer._errors` private attribute.
This commit is contained in:
parent
8c29efef48
commit
1834760148
|
@ -493,21 +493,6 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
if hasattr(serializer, 'initial_data'):
|
||||
serializer.is_valid()
|
||||
|
||||
# Convert ValidationError to unicode string
|
||||
# This is mainly a hack to monkey patch the errors and make the form renderer happy...
|
||||
errors = OrderedDict()
|
||||
for field_name, values in serializer.errors.items():
|
||||
if isinstance(values, list):
|
||||
errors[field_name] = values
|
||||
continue
|
||||
|
||||
errors[field_name] = []
|
||||
for value in values.detail:
|
||||
for message in value.detail:
|
||||
errors[field_name].append(message)
|
||||
|
||||
serializer._errors = errors
|
||||
|
||||
form_renderer = self.form_renderer_class()
|
||||
return form_renderer.render(
|
||||
serializer.data,
|
||||
|
|
|
@ -219,7 +219,13 @@ class BaseSerializer(Field):
|
|||
self._errors = {}
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
return_errors = None
|
||||
if isinstance(self._errors, list):
|
||||
return_errors = ReturnList(self._errors, serializer=self)
|
||||
elif isinstance(self._errors, dict):
|
||||
return_errors = ReturnDict(self._errors, serializer=self)
|
||||
|
||||
raise ValidationError(return_errors)
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
|
@ -244,12 +250,42 @@ class BaseSerializer(Field):
|
|||
self._data = self.get_initial()
|
||||
return self._data
|
||||
|
||||
def _transform_to_legacy_errors(self, errors_to_transform):
|
||||
# Do not mutate `errors_to_transform` here.
|
||||
errors = ReturnDict(serializer=self)
|
||||
for field_name, values in errors_to_transform.items():
|
||||
if isinstance(values, list):
|
||||
errors[field_name] = values
|
||||
continue
|
||||
|
||||
if isinstance(values.detail, list):
|
||||
errors[field_name] = []
|
||||
for value in values.detail:
|
||||
if isinstance(value, ValidationError):
|
||||
errors[field_name].extend(value.detail)
|
||||
elif isinstance(value, list):
|
||||
errors[field_name].extend(value)
|
||||
else:
|
||||
errors[field_name].append(value)
|
||||
|
||||
elif isinstance(values.detail, dict):
|
||||
errors[field_name] = {}
|
||||
for sub_field_name, value in values.detail.items():
|
||||
errors[field_name][sub_field_name] = []
|
||||
for validation_error in value:
|
||||
errors[field_name][sub_field_name].extend(validation_error.detail)
|
||||
return errors
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
if not hasattr(self, '_errors'):
|
||||
msg = 'You must call `.is_valid()` before accessing `.errors`.'
|
||||
raise AssertionError(msg)
|
||||
return self._errors
|
||||
|
||||
if isinstance(self._errors, list):
|
||||
return map(self._transform_to_legacy_errors, self._errors)
|
||||
else:
|
||||
return self._transform_to_legacy_errors(self._errors)
|
||||
|
||||
@property
|
||||
def validated_data(self):
|
||||
|
|
|
@ -15,7 +15,6 @@ from django.views.generic import View
|
|||
|
||||
from rest_framework import exceptions, status
|
||||
from rest_framework.compat import set_rollback
|
||||
from rest_framework.exceptions import ValidationError, _force_text_recursive
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
|
@ -70,18 +69,8 @@ def exception_handler(exc, context):
|
|||
if getattr(exc, 'wait', None):
|
||||
headers['Retry-After'] = '%d' % exc.wait
|
||||
|
||||
if isinstance(exc.detail, list):
|
||||
data = _force_text_recursive([item.detail if isinstance(item, ValidationError) else item
|
||||
for item in exc.detai])
|
||||
elif isinstance(exc.detail, dict):
|
||||
for field_name, e in exc.detail.items():
|
||||
if hasattr(e, 'detail') and isinstance(e.detail[0], ValidationError):
|
||||
exc.detail[field_name] = e.detail[0].detail
|
||||
elif isinstance(e, ValidationError):
|
||||
exc.detail[field_name] = e.detail
|
||||
else:
|
||||
exc.detail[field_name] = e
|
||||
data = exc.detail
|
||||
if isinstance(exc.detail, (list, dict)):
|
||||
data = exc.detail.serializer.errors
|
||||
else:
|
||||
data = {'detail': exc.detail}
|
||||
|
||||
|
|
|
@ -38,9 +38,12 @@ class TestSimpleBoundField:
|
|||
serializer = ExampleSerializer(data={'text': 'x' * 1000, 'amount': 123})
|
||||
serializer.is_valid()
|
||||
|
||||
# TODO Should we add the _errors with ValidationError to the bound_field.errors to get acces to the error code?
|
||||
assert serializer._errors['text'].detail[0].detail == ['Ensure this field has no more than 100 characters.']
|
||||
assert serializer._errors['text'].detail[0].code == 'max_length'
|
||||
|
||||
assert serializer['text'].value == 'x' * 1000
|
||||
assert serializer['text'].errors.detail[0].detail == ['Ensure this field has no more than 100 characters.']
|
||||
assert serializer['text'].errors.detail[0].code == 'max_length'
|
||||
assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.']
|
||||
assert serializer['text'].name == 'text'
|
||||
assert serializer['amount'].value is 123
|
||||
assert serializer['amount'].errors is None
|
||||
|
|
|
@ -374,7 +374,7 @@ class TestGenericIPAddressFieldValidation(TestCase):
|
|||
|
||||
s = TestSerializer(data={'address': 'not an ip address'})
|
||||
self.assertFalse(s.is_valid())
|
||||
self.assertEquals(1, len(s.errors['address'].detail),
|
||||
self.assertEquals(1, len(s.errors['address']),
|
||||
'Unexpected number of validation errors: '
|
||||
'{0}'.format(s.errors))
|
||||
|
||||
|
|
|
@ -244,8 +244,9 @@ class HyperlinkedForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request})
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail, ['Incorrect type. Expected URL string, received int.'])
|
||||
self.assertEqual(serializer.errors['target'].code, 'incorrect_type')
|
||||
self.assertEqual(serializer.errors, {'target': ['Incorrect type. Expected URL string, received int.']})
|
||||
self.assertEqual(serializer._errors['target'].detail, ['Incorrect type. Expected URL string, received int.'])
|
||||
self.assertEqual(serializer._errors['target'].code, 'incorrect_type')
|
||||
|
||||
def test_reverse_foreign_key_update(self):
|
||||
data = {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']}
|
||||
|
@ -316,8 +317,9 @@ class HyperlinkedForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request})
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer.errors['target'].code, 'null')
|
||||
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
|
||||
self.assertEqual(serializer._errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer._errors['target'].code, 'null')
|
||||
|
||||
|
||||
class HyperlinkedNullableForeignKeyTests(TestCase):
|
||||
|
|
|
@ -235,9 +235,11 @@ class PKForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail,
|
||||
self.assertEqual(serializer.errors,
|
||||
{'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]})
|
||||
self.assertEqual(serializer._errors['target'].detail,
|
||||
['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__])
|
||||
self.assertEqual(serializer.errors['target'].code, 'incorrect_type')
|
||||
self.assertEqual(serializer._errors['target'].code, 'incorrect_type')
|
||||
|
||||
def test_reverse_foreign_key_update(self):
|
||||
data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]}
|
||||
|
@ -308,8 +310,9 @@ class PKForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer.errors['target'].code, 'null')
|
||||
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
|
||||
self.assertEqual(serializer._errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer._errors['target'].code, 'null')
|
||||
|
||||
def test_foreign_key_with_unsaved(self):
|
||||
source = ForeignKeySource(name='source-unsaved')
|
||||
|
|
|
@ -104,8 +104,9 @@ class SlugForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail, ['Object with name=123 does not exist.'])
|
||||
self.assertEqual(serializer.errors['target'].code, 'does_not_exist')
|
||||
self.assertEqual(serializer.errors, {'target': ['Object with name=123 does not exist.']})
|
||||
self.assertEqual(serializer._errors['target'].detail, ['Object with name=123 does not exist.'])
|
||||
self.assertEqual(serializer._errors['target'].code, 'does_not_exist')
|
||||
|
||||
def test_reverse_foreign_key_update(self):
|
||||
data = {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']}
|
||||
|
@ -177,8 +178,9 @@ class SlugForeignKeyTests(TestCase):
|
|||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer.errors['target'].code, 'null')
|
||||
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
|
||||
self.assertEqual(serializer._errors['target'].detail, ['This field may not be null.'])
|
||||
self.assertEqual(serializer._errors['target'].code, 'null')
|
||||
|
||||
|
||||
class SlugNullableForeignKeyTests(TestCase):
|
||||
|
|
|
@ -32,8 +32,9 @@ class TestSerializer:
|
|||
serializer = self.Serializer(data={'char': 'abc'})
|
||||
assert not serializer.is_valid()
|
||||
assert serializer.validated_data == {}
|
||||
assert serializer.errors['integer'].detail == ['This field is required.']
|
||||
assert serializer.errors['integer'].code == 'required'
|
||||
assert serializer.errors == {'integer': ['This field is required.']}
|
||||
assert serializer._errors['integer'].detail == ['This field is required.']
|
||||
assert serializer._errors['integer'].code == 'required'
|
||||
|
||||
def test_partial_validation(self):
|
||||
serializer = self.Serializer(data={'char': 'abc'}, partial=True)
|
||||
|
|
|
@ -71,7 +71,15 @@ class BulkCreateSerializerTests(TestCase):
|
|||
serializer = self.BookSerializer(data=data, many=True)
|
||||
self.assertEqual(serializer.is_valid(), False)
|
||||
|
||||
for idx, error in enumerate(serializer.errors):
|
||||
expected_errors = [
|
||||
{},
|
||||
{},
|
||||
{'id': ['A valid integer is required.']}
|
||||
]
|
||||
|
||||
self.assertEqual(serializer.errors, expected_errors)
|
||||
|
||||
for idx, error in enumerate(serializer._errors):
|
||||
if idx < 2:
|
||||
self.assertEqual(error, {})
|
||||
else:
|
||||
|
|
|
@ -113,8 +113,11 @@ class TestNestedSerializerWithMany:
|
|||
|
||||
assert not serializer.is_valid()
|
||||
|
||||
assert serializer.errors['not_allow_null'].detail == [serializer.error_messages['null']]
|
||||
assert serializer.errors['not_allow_null'].code == 'null'
|
||||
expected_errors = {'not_allow_null': [serializer.error_messages['null']]}
|
||||
assert serializer.errors == expected_errors
|
||||
|
||||
assert serializer._errors['not_allow_null'].detail == [serializer.error_messages['null']]
|
||||
assert serializer._errors['not_allow_null'].code == 'null'
|
||||
|
||||
def test_run_the_field_validation_even_if_the_field_is_null(self):
|
||||
class TestSerializer(self.Serializer):
|
||||
|
@ -165,7 +168,11 @@ class TestNestedSerializerWithMany:
|
|||
|
||||
assert not serializer.is_valid()
|
||||
|
||||
assert serializer.errors['not_allow_empty'].detail['non_field_errors'][0].detail == \
|
||||
expected_errors = {
|
||||
'not_allow_empty': {'non_field_errors': [serializers.ListSerializer.default_error_messages['empty']]}}
|
||||
assert serializer.errors == expected_errors
|
||||
|
||||
assert serializer._errors['not_allow_empty'].detail['non_field_errors'][0].detail == \
|
||||
[serializers.ListSerializer.default_error_messages['empty']]
|
||||
|
||||
assert serializer.errors['not_allow_empty'].detail['non_field_errors'][0].code == 'empty_not_allowed'
|
||||
assert serializer._errors['not_allow_empty'].detail['non_field_errors'][0].code == 'empty_not_allowed'
|
||||
|
|
|
@ -122,9 +122,13 @@ class TestMaxValueValidatorValidation(TestCase):
|
|||
def test_max_value_validation_serializer_fails(self):
|
||||
serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 101})
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(['Ensure this value is less than or equal to 100.'], serializer.errors['number_value'].detail[0].detail)
|
||||
self.assertEqual(None, serializer.errors['number_value'].code)
|
||||
self.assertEqual('max_value', serializer.errors['number_value'].detail[0].code)
|
||||
|
||||
self.assertDictEqual({'number_value': ['Ensure this value is less than or equal to 100.']}, serializer.errors)
|
||||
|
||||
self.assertEqual(['Ensure this value is less than or equal to 100.'],
|
||||
serializer._errors['number_value'].detail[0].detail)
|
||||
self.assertEqual(None, serializer._errors['number_value'].code)
|
||||
self.assertEqual('max_value', serializer._errors['number_value'].detail[0].code)
|
||||
|
||||
def test_max_value_validation_success(self):
|
||||
obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
|
||||
|
|
|
@ -49,9 +49,12 @@ class TestUniquenessValidation(TestCase):
|
|||
serializer = UniquenessSerializer(data=data)
|
||||
|
||||
assert not serializer.is_valid()
|
||||
assert serializer.errors['username'].code is None
|
||||
assert serializer.errors['username'].detail[0].code == 'unique'
|
||||
assert serializer.errors['username'].detail[0].detail == ['UniquenessModel with this username already exists.']
|
||||
|
||||
assert serializer.errors == {'username': ['UniquenessModel with this username already exists.']}
|
||||
|
||||
assert serializer._errors['username'].code is None
|
||||
assert serializer._errors['username'].detail[0].code == 'unique'
|
||||
assert serializer._errors['username'].detail[0].detail == ['UniquenessModel with this username already exists.']
|
||||
|
||||
def test_is_unique(self):
|
||||
data = {'username': 'other'}
|
||||
|
|
Loading…
Reference in New Issue
Block a user