Skip to content

Commit 2c81d66

Browse files
committed
get_error_detail: use error_list to get code(s)
1 parent a7cd1db commit 2c81d66

File tree

2 files changed

+117
-5
lines changed

2 files changed

+117
-5
lines changed

rest_framework/fields.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,16 +227,39 @@ def __init__(self, value, display_text, disabled=False):
227227
yield Option(value='n/a', display_text=cutoff_text, disabled=True)
228228

229229

230+
def _get_error_detail_dict(error_dict, default_code):
231+
return {
232+
k: [
233+
ErrorDetail(error.message % (error.params or ()),
234+
code=error.code if error.code else default_code)
235+
for error in errors
236+
] for k, errors in error_dict.items()
237+
}
238+
239+
230240
def get_error_detail(exc_info):
231241
"""
232242
Given a Django ValidationError, return a list of ErrorDetail,
233243
with the `code` populated.
234244
"""
235245
code = getattr(exc_info, 'code', None) or 'invalid'
236-
return [
237-
ErrorDetail(msg, code=code)
238-
for msg in exc_info.messages
239-
]
246+
247+
try:
248+
error_dict = exc_info.error_dict
249+
except AttributeError:
250+
errors = []
251+
for error in exc_info.error_list:
252+
try:
253+
error_dict = error.error_dict
254+
except AttributeError:
255+
detail = ErrorDetail(error.message % (error.params or ()),
256+
code=error.code if error.code else code)
257+
else:
258+
detail = _get_error_detail_dict(error, code)
259+
errors.append(detail)
260+
return errors
261+
else:
262+
return _get_error_detail_dict(error_dict, code)
240263

241264

242265
class CreateOnlyDefault(object):

tests/test_fields.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
from decimal import ROUND_DOWN, ROUND_UP, Decimal
77

88
import pytest
9+
from django.core.exceptions import ValidationError as DjangoValidationError
910
from django.http import QueryDict
1011
from django.test import TestCase, override_settings
1112
from django.utils import six
1213
from django.utils.timezone import activate, deactivate, override, utc
1314

1415
import rest_framework
15-
from rest_framework import compat, serializers
16+
from rest_framework import compat, exceptions, serializers
1617
from rest_framework.fields import DjangoImageField, is_simple_callable
1718

1819
try:
@@ -2183,3 +2184,91 @@ class ExampleSerializer(serializers.Serializer):
21832184
"'ExampleSerializer', because it is the same as the default "
21842185
"method name. Remove the `method_name` argument."
21852186
)
2187+
2188+
2189+
class TestValidationErrorCode:
2190+
@pytest.mark.parametrize('use_list', (False, True))
2191+
def test_validationerror_code_with_msg(self, use_list):
2192+
2193+
class ExampleSerializer(serializers.Serializer):
2194+
password = serializers.CharField()
2195+
2196+
def validate_password(self, obj):
2197+
err = DjangoValidationError('exc_msg', code='exc_code')
2198+
if use_list:
2199+
err = DjangoValidationError([err])
2200+
raise err
2201+
2202+
serializer = ExampleSerializer(data={'password': 123})
2203+
serializer.is_valid()
2204+
assert serializer.errors == {'password': ['exc_msg']}
2205+
assert serializer.errors['password'][0].code == 'exc_code'
2206+
2207+
@pytest.mark.parametrize('code', (None, 'exc_code',))
2208+
@pytest.mark.parametrize('use_list', (False, True))
2209+
def test_validationerror_code_with_dict(self, use_list, code):
2210+
2211+
class ExampleSerializer(serializers.Serializer):
2212+
2213+
def validate(self, obj):
2214+
if code is None:
2215+
err = DjangoValidationError({
2216+
'email': 'email error',
2217+
})
2218+
else:
2219+
err = DjangoValidationError({
2220+
'email': DjangoValidationError(
2221+
'email error',
2222+
code=code),
2223+
})
2224+
if use_list:
2225+
err = DjangoValidationError([err])
2226+
raise err
2227+
2228+
serializer = ExampleSerializer(data={})
2229+
serializer.is_valid()
2230+
expected_code = code if code else 'invalid'
2231+
if use_list:
2232+
assert serializer.errors == {
2233+
'non_field_errors': [
2234+
exceptions.ErrorDetail(
2235+
string='email error',
2236+
code=expected_code
2237+
)
2238+
]
2239+
}
2240+
else:
2241+
assert serializer.errors == {
2242+
'email': ['email error'],
2243+
}
2244+
assert serializer.errors['email'][0].code == expected_code
2245+
2246+
@pytest.mark.parametrize('code', (None, 'exc_code',))
2247+
def test_validationerror_code_with_dict_list_same_code(self, code):
2248+
2249+
class ExampleSerializer(serializers.Serializer):
2250+
2251+
def validate(self, obj):
2252+
if code is None:
2253+
raise DjangoValidationError({'email': ['email error 1',
2254+
'email error 2']})
2255+
raise DjangoValidationError({'email': [
2256+
DjangoValidationError('email error 1', code=code),
2257+
DjangoValidationError('email error 2', code=code),
2258+
]})
2259+
2260+
serializer = ExampleSerializer(data={})
2261+
serializer.is_valid()
2262+
expected_code = code if code else 'invalid'
2263+
assert serializer.errors == {
2264+
'email': [
2265+
exceptions.ErrorDetail(
2266+
string='email error 1',
2267+
code=expected_code
2268+
),
2269+
exceptions.ErrorDetail(
2270+
string='email error 2',
2271+
code=expected_code
2272+
),
2273+
]
2274+
}

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