Skip to content

Commit 44a1aab

Browse files
committed
Deal with ValidationError instantiation
1 parent 12e4b8f commit 44a1aab

File tree

5 files changed

+60
-87
lines changed

5 files changed

+60
-87
lines changed

rest_framework/exceptions.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import unicode_literals
88

99
import math
10+
from collections import namedtuple
1011

1112
from django.utils import six
1213
from django.utils.encoding import force_text
@@ -61,7 +62,7 @@ def __str__(self):
6162
def build_error_from_django_validation_error(exc_info):
6263
code = getattr(exc_info, 'code', None) or 'invalid'
6364
return [
64-
(msg, code)
65+
ErrorDetails(msg, code)
6566
for msg in exc_info.messages
6667
]
6768

@@ -72,60 +73,61 @@ def build_error_from_django_validation_error(exc_info):
7273
# from rest_framework import serializers
7374
# raise serializers.ValidationError('Value was invalid')
7475

76+
ErrorDetails = namedtuple('ErrorDetails', ['message', 'code'])
77+
7578

7679
class ValidationError(APIException):
7780
status_code = status.HTTP_400_BAD_REQUEST
7881
code = None
7982

8083
def __init__(self, detail, code=None):
81-
# For validation errors the 'detail' key is always required.
82-
# The details should always be coerced to a list if not already.
83-
8484
if code:
85-
self.full_details = [(detail, code)]
85+
self.full_details = ErrorDetails(detail, code)
8686
else:
8787
self.full_details = detail
8888

89-
if isinstance(self.full_details, tuple):
90-
self.detail, self.code = self.full_details
91-
self.detail = [self.detail]
89+
if not isinstance(self.full_details, dict) \
90+
and not isinstance(self.full_details, list):
91+
self.full_details = [self.full_details]
92+
self.full_details = _force_text_recursive(self.full_details)
9293

93-
elif isinstance(self.full_details, list):
94+
self.detail = detail
95+
if isinstance(self.full_details, list):
9496
if isinstance(self.full_details, ReturnList):
95-
self.detail = ReturnList(serializer=self.full_details.serializer)
97+
self.detail = ReturnList(
98+
serializer=self.full_details.serializer)
9699
else:
97100
self.detail = []
98-
for error in self.full_details:
99-
if isinstance(error, tuple):
100-
message, code = error
101-
self.detail.append(message)
102-
elif isinstance(error, dict):
103-
self.detail = self.full_details
104-
break
105-
101+
for full_detail in self.full_details:
102+
if isinstance(full_detail, ErrorDetails):
103+
self.detail.append(full_detail.message)
104+
elif isinstance(full_detail, dict):
105+
if not full_detail:
106+
self.detail.append(full_detail)
107+
for key, value in full_detail.items():
108+
if isinstance(value, list):
109+
self.detail.append(
110+
{key: [item.message]
111+
if isinstance(item, ErrorDetails)
112+
else [item] for item in value})
113+
elif isinstance(full_detail, list):
114+
self.detail.extend(full_detail)
115+
else:
116+
self.detail.append(full_detail)
106117
elif isinstance(self.full_details, dict):
107118
if isinstance(self.full_details, ReturnDict):
108-
self.detail = ReturnDict(serializer=self.full_details.serializer)
119+
self.detail = ReturnDict(
120+
serializer=self.full_details.serializer)
109121
else:
110122
self.detail = {}
111-
112-
for field_name, errors in self.full_details.items():
113-
self.detail[field_name] = []
114-
if isinstance(errors, tuple):
115-
message, code = errors
116-
self.detail[field_name].append(message)
117-
elif isinstance(errors, list):
118-
for error in errors:
119-
if isinstance(error, tuple):
120-
message, code = error
121-
else:
122-
message = error
123-
if message:
124-
self.detail[field_name].append(message)
125-
else:
126-
self.detail = [self.full_details]
127-
128-
self.detail = _force_text_recursive(self.detail)
123+
for field_name, full_detail in self.full_details.items():
124+
if isinstance(full_detail, list):
125+
self.detail[field_name] = [
126+
item.message if isinstance(item, ErrorDetails) else item
127+
for item in full_detail
128+
]
129+
else:
130+
self.detail[field_name] = full_detail
129131

130132
def __str__(self):
131133
return six.text_type(self.detail)

rest_framework/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ def run_validators(self, value):
503503
# attempting to accumulate a list of errors.
504504
if isinstance(exc.detail, dict):
505505
raise
506-
errors.append((exc.detail, exc.code))
506+
errors.append(exc.full_details)
507507
except DjangoValidationError as exc:
508508
errors.extend(build_error_from_django_validation_error(exc))
509509
if errors:

rest_framework/serializers.py

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,12 @@ def is_valid(self, raise_exception=False):
214214
self._validated_data = self.run_validation(self.initial_data)
215215
except ValidationError as exc:
216216
self._validated_data = {}
217-
self._errors = exc.full_details
217+
self._errors = exc.detail
218218
else:
219219
self._errors = {}
220220

221221
if self._errors and raise_exception:
222-
raise ValidationError(self._errors)
222+
raise ValidationError(self.errors)
223223

224224
return not bool(self._errors)
225225

@@ -249,36 +249,7 @@ def errors(self):
249249
if not hasattr(self, '_errors'):
250250
msg = 'You must call `.is_valid()` before accessing `.errors`.'
251251
raise AssertionError(msg)
252-
253-
if isinstance(self._errors, dict):
254-
errors = ReturnDict(serializer=self)
255-
for key, value in self._errors.items():
256-
if isinstance(value, dict):
257-
errors[key] = {}
258-
for key_, value_ in value.items():
259-
message, code = value_[0]
260-
errors[key][key_] = [message]
261-
elif isinstance(value, list):
262-
if isinstance(value[0], tuple):
263-
message, code = value[0]
264-
else:
265-
message = value[0]
266-
if isinstance(message, list):
267-
errors[key] = message
268-
else:
269-
errors[key] = [message]
270-
elif isinstance(value, tuple):
271-
message, code = value
272-
errors[key] = [message]
273-
else:
274-
errors[key] = [value]
275-
elif isinstance(self._errors, list):
276-
errors = ReturnList(self._errors, serializer=self)
277-
else:
278-
# This shouldn't ever happen.
279-
errors = self._errors
280-
281-
return errors
252+
return self._errors
282253

283254
@property
284255
def validated_data(self):
@@ -333,21 +304,21 @@ def get_validation_error_detail(exc):
333304
return {
334305
api_settings.NON_FIELD_ERRORS_KEY: error
335306
}
336-
elif isinstance(exc.full_details, dict):
307+
elif isinstance(exc.detail, dict):
337308
# If errors may be a dict we use the standard {key: list of values}.
338309
# Here we ensure that all the values are *lists* of errors.
339310
return {
340311
key: value if isinstance(value, list) else [value]
341-
for key, value in exc.full_details.items()
312+
for key, value in exc.detail.items()
342313
}
343-
elif isinstance(exc.full_details, list):
314+
elif isinstance(exc.detail, list):
344315
# Errors raised as a list are non-field errors.
345316
return {
346-
api_settings.NON_FIELD_ERRORS_KEY: exc.full_details
317+
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
347318
}
348319
# Errors raised as a string are non-field errors.
349320
return {
350-
api_settings.NON_FIELD_ERRORS_KEY: [exc.full_details]
321+
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
351322
}
352323

353324

@@ -455,11 +426,11 @@ def to_internal_value(self, data):
455426
)
456427
code = 'invalid'
457428
raise ValidationError({
458-
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
429+
api_settings.NON_FIELD_ERRORS_KEY: [ErrorDetails(message, code)]
459430
})
460431

461-
ret = ReturnDict(serializer=self)
462-
errors = ReturnDict(serializer=self)
432+
ret = OrderedDict()
433+
errors = OrderedDict()
463434
fields = self._writable_fields
464435

465436
for field in fields:
@@ -470,7 +441,7 @@ def to_internal_value(self, data):
470441
if validate_method is not None:
471442
validated_value = validate_method(validated_value)
472443
except ValidationError as exc:
473-
errors[field.field_name] = exc.full_details
444+
errors[field.field_name] = exc.detail
474445
except DjangoValidationError as exc:
475446
error = build_error_from_django_validation_error(exc)
476447
errors[field.field_name] = error
@@ -610,14 +581,14 @@ def to_internal_value(self, data):
610581
)
611582
code = 'not_a_list'
612583
raise ValidationError({
613-
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
584+
api_settings.NON_FIELD_ERRORS_KEY: [ErrorDetails(message, code)]
614585
})
615586

616587
if not self.allow_empty and len(data) == 0:
617588
message = self.error_messages['empty']
618589
code = 'empty_not_allowed'
619590
raise ValidationError({
620-
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
591+
api_settings.NON_FIELD_ERRORS_KEY: [ErrorDetails(message, code)]
621592
})
622593

623594
ret = []

rest_framework/validators.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from django.utils.translation import ugettext_lazy as _
1212

1313
from rest_framework.compat import unicode_to_repr
14-
from rest_framework.exceptions import ValidationError
14+
from rest_framework.exceptions import ErrorDetails, ValidationError
1515
from rest_framework.utils.representation import smart_repr
1616

1717

@@ -102,7 +102,7 @@ def enforce_required_fields(self, attrs):
102102

103103
code = 'required'
104104
missing = {
105-
field_name: [(self.missing_message, code)]
105+
field_name: ErrorDetails(self.missing_message, code)
106106
for field_name in self.fields
107107
if field_name not in attrs
108108
}
@@ -150,7 +150,7 @@ def __call__(self, attrs):
150150
field_names = ', '.join(self.fields)
151151
message = self.message.format(field_names=field_names)
152152
code = 'unique'
153-
raise ValidationError(message, code=code)
153+
raise ValidationError(ErrorDetails(message, code=code))
154154

155155
def __repr__(self):
156156
return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % (
@@ -189,7 +189,7 @@ def enforce_required_fields(self, attrs):
189189
"""
190190
code = 'required'
191191
missing = {
192-
field_name: [(self.missing_message, code)]
192+
field_name: ErrorDetails(self.missing_message, code)
193193
for field_name in [self.field, self.date_field]
194194
if field_name not in attrs
195195
}
@@ -216,7 +216,7 @@ def __call__(self, attrs):
216216
if queryset.exists():
217217
message = self.message.format(date_field=self.date_field)
218218
code = 'unique'
219-
raise ValidationError({self.field: [(message, code)]})
219+
raise ValidationError({self.field: ErrorDetails(message, code)})
220220

221221
def __repr__(self):
222222
return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % (

tests/test_validation_error.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def setUp(self):
3131

3232
def exception_handler(exc, request):
3333
return_errors = {}
34-
for field_name, errors in exc.full_details.items():
34+
for field_name, errors in exc.detail.items():
3535
return_errors[field_name] = []
3636
for message, code in errors:
3737
return_errors[field_name].append({

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy