mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-08 14:24:48 +03:00
WIP - Integrate 2 tuples solution for error codes
This is still a wip, the code is uggly and will need to be refactored and reviewed a lot!
This commit is contained in:
parent
ba21a1e551
commit
12e4b8f3b1
|
@ -58,22 +58,74 @@ class APIException(Exception):
|
|||
return self.detail
|
||||
|
||||
|
||||
def build_error_from_django_validation_error(exc_info):
|
||||
code = getattr(exc_info, 'code', None) or 'invalid'
|
||||
return [
|
||||
(msg, code)
|
||||
for msg in exc_info.messages
|
||||
]
|
||||
|
||||
# The recommended style for using `ValidationError` is to keep it namespaced
|
||||
# under `serializers`, in order to minimize potential confusion with Django's
|
||||
# built in `ValidationError`. For example:
|
||||
#
|
||||
# from rest_framework import serializers
|
||||
# raise serializers.ValidationError('Value was invalid')
|
||||
|
||||
|
||||
class ValidationError(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
code = None
|
||||
|
||||
def __init__(self, detail, code=None):
|
||||
# For validation errors the 'detail' key is always required.
|
||||
# The details should always be coerced to a list if not already.
|
||||
if not isinstance(detail, dict) and not isinstance(detail, list):
|
||||
detail = [detail]
|
||||
self.detail = _force_text_recursive(detail)
|
||||
self.code = code
|
||||
|
||||
if code:
|
||||
self.full_details = [(detail, code)]
|
||||
else:
|
||||
self.full_details = detail
|
||||
|
||||
if isinstance(self.full_details, tuple):
|
||||
self.detail, self.code = self.full_details
|
||||
self.detail = [self.detail]
|
||||
|
||||
elif isinstance(self.full_details, list):
|
||||
if isinstance(self.full_details, ReturnList):
|
||||
self.detail = ReturnList(serializer=self.full_details.serializer)
|
||||
else:
|
||||
self.detail = []
|
||||
for error in self.full_details:
|
||||
if isinstance(error, tuple):
|
||||
message, code = error
|
||||
self.detail.append(message)
|
||||
elif isinstance(error, dict):
|
||||
self.detail = self.full_details
|
||||
break
|
||||
|
||||
elif isinstance(self.full_details, dict):
|
||||
if isinstance(self.full_details, ReturnDict):
|
||||
self.detail = ReturnDict(serializer=self.full_details.serializer)
|
||||
else:
|
||||
self.detail = {}
|
||||
|
||||
for field_name, errors in self.full_details.items():
|
||||
self.detail[field_name] = []
|
||||
if isinstance(errors, tuple):
|
||||
message, code = errors
|
||||
self.detail[field_name].append(message)
|
||||
elif isinstance(errors, list):
|
||||
for error in errors:
|
||||
if isinstance(error, tuple):
|
||||
message, code = error
|
||||
else:
|
||||
message = error
|
||||
if message:
|
||||
self.detail[field_name].append(message)
|
||||
else:
|
||||
self.detail = [self.full_details]
|
||||
|
||||
self.detail = _force_text_recursive(self.detail)
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.detail)
|
||||
|
|
|
@ -31,7 +31,9 @@ from rest_framework.compat import (
|
|||
MinValueValidator, duration_string, parse_duration, unicode_repr,
|
||||
unicode_to_repr
|
||||
)
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.exceptions import (
|
||||
ValidationError, build_error_from_django_validation_error
|
||||
)
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import html, humanize_datetime, representation
|
||||
|
||||
|
@ -501,9 +503,9 @@ class Field(object):
|
|||
# attempting to accumulate a list of errors.
|
||||
if isinstance(exc.detail, dict):
|
||||
raise
|
||||
errors.extend(exc.detail)
|
||||
errors.append((exc.detail, exc.code))
|
||||
except DjangoValidationError as exc:
|
||||
errors.extend(exc.messages)
|
||||
errors.extend(build_error_from_django_validation_error(exc))
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework.compat import DurationField as ModelDurationField
|
||||
from rest_framework.compat import JSONField as ModelJSONField
|
||||
from rest_framework.compat import postgres_fields, unicode_to_repr
|
||||
from rest_framework.exceptions import build_error_from_django_validation_error
|
||||
from rest_framework.utils import model_meta
|
||||
from rest_framework.utils.field_mapping import (
|
||||
ClassLookupDict, get_field_kwargs, get_nested_relation_kwargs,
|
||||
|
@ -213,12 +214,12 @@ class BaseSerializer(Field):
|
|||
self._validated_data = self.run_validation(self.initial_data)
|
||||
except ValidationError as exc:
|
||||
self._validated_data = {}
|
||||
self._errors = exc.detail
|
||||
self._errors = exc.full_details
|
||||
else:
|
||||
self._errors = {}
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
raise ValidationError(self._errors)
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
|
@ -248,7 +249,36 @@ class BaseSerializer(Field):
|
|||
if not hasattr(self, '_errors'):
|
||||
msg = 'You must call `.is_valid()` before accessing `.errors`.'
|
||||
raise AssertionError(msg)
|
||||
return self._errors
|
||||
|
||||
if isinstance(self._errors, dict):
|
||||
errors = ReturnDict(serializer=self)
|
||||
for key, value in self._errors.items():
|
||||
if isinstance(value, dict):
|
||||
errors[key] = {}
|
||||
for key_, value_ in value.items():
|
||||
message, code = value_[0]
|
||||
errors[key][key_] = [message]
|
||||
elif isinstance(value, list):
|
||||
if isinstance(value[0], tuple):
|
||||
message, code = value[0]
|
||||
else:
|
||||
message = value[0]
|
||||
if isinstance(message, list):
|
||||
errors[key] = message
|
||||
else:
|
||||
errors[key] = [message]
|
||||
elif isinstance(value, tuple):
|
||||
message, code = value
|
||||
errors[key] = [message]
|
||||
else:
|
||||
errors[key] = [value]
|
||||
elif isinstance(self._errors, list):
|
||||
errors = ReturnList(self._errors, serializer=self)
|
||||
else:
|
||||
# This shouldn't ever happen.
|
||||
errors = self._errors
|
||||
|
||||
return errors
|
||||
|
||||
@property
|
||||
def validated_data(self):
|
||||
|
@ -299,24 +329,25 @@ def get_validation_error_detail(exc):
|
|||
# inside your codebase, but we handle Django's validation
|
||||
# exception class as well for simpler compat.
|
||||
# Eg. Calling Model.clean() explicitly inside Serializer.validate()
|
||||
error = build_error_from_django_validation_error(exc)
|
||||
return {
|
||||
api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
|
||||
api_settings.NON_FIELD_ERRORS_KEY: error
|
||||
}
|
||||
elif isinstance(exc.detail, dict):
|
||||
elif isinstance(exc.full_details, dict):
|
||||
# If errors may be a dict we use the standard {key: list of values}.
|
||||
# Here we ensure that all the values are *lists* of errors.
|
||||
return {
|
||||
key: value if isinstance(value, list) else [value]
|
||||
for key, value in exc.detail.items()
|
||||
for key, value in exc.full_details.items()
|
||||
}
|
||||
elif isinstance(exc.detail, list):
|
||||
elif isinstance(exc.full_details, list):
|
||||
# Errors raised as a list are non-field errors.
|
||||
return {
|
||||
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
|
||||
api_settings.NON_FIELD_ERRORS_KEY: exc.full_details
|
||||
}
|
||||
# Errors raised as a string are non-field errors.
|
||||
return {
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [exc.full_details]
|
||||
}
|
||||
|
||||
|
||||
|
@ -422,12 +453,13 @@ class Serializer(BaseSerializer):
|
|||
message = self.error_messages['invalid'].format(
|
||||
datatype=type(data).__name__
|
||||
)
|
||||
code = 'invalid'
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
|
||||
})
|
||||
|
||||
ret = OrderedDict()
|
||||
errors = OrderedDict()
|
||||
ret = ReturnDict(serializer=self)
|
||||
errors = ReturnDict(serializer=self)
|
||||
fields = self._writable_fields
|
||||
|
||||
for field in fields:
|
||||
|
@ -438,9 +470,10 @@ class Serializer(BaseSerializer):
|
|||
if validate_method is not None:
|
||||
validated_value = validate_method(validated_value)
|
||||
except ValidationError as exc:
|
||||
errors[field.field_name] = exc.detail
|
||||
errors[field.field_name] = exc.full_details
|
||||
except DjangoValidationError as exc:
|
||||
errors[field.field_name] = list(exc.messages)
|
||||
error = build_error_from_django_validation_error(exc)
|
||||
errors[field.field_name] = error
|
||||
except SkipField:
|
||||
pass
|
||||
else:
|
||||
|
@ -575,14 +608,16 @@ class ListSerializer(BaseSerializer):
|
|||
message = self.error_messages['not_a_list'].format(
|
||||
input_type=type(data).__name__
|
||||
)
|
||||
code = 'not_a_list'
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
|
||||
})
|
||||
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
message = self.error_messages['empty']
|
||||
code = 'empty_not_allowed'
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [(message, code)]
|
||||
})
|
||||
|
||||
ret = []
|
||||
|
|
|
@ -100,8 +100,9 @@ class UniqueTogetherValidator(object):
|
|||
if self.instance is not None:
|
||||
return
|
||||
|
||||
code = 'required'
|
||||
missing = {
|
||||
field_name: self.missing_message
|
||||
field_name: [(self.missing_message, code)]
|
||||
for field_name in self.fields
|
||||
if field_name not in attrs
|
||||
}
|
||||
|
@ -186,8 +187,9 @@ class BaseUniqueForValidator(object):
|
|||
The `UniqueFor<Range>Validator` classes always force an implied
|
||||
'required' state on the fields they are applied to.
|
||||
"""
|
||||
code = 'required'
|
||||
missing = {
|
||||
field_name: self.missing_message
|
||||
field_name: [(self.missing_message, code)]
|
||||
for field_name in [self.field, self.date_field]
|
||||
if field_name not in attrs
|
||||
}
|
||||
|
@ -213,9 +215,8 @@ class BaseUniqueForValidator(object):
|
|||
queryset = self.exclude_current_instance(attrs, queryset)
|
||||
if queryset.exists():
|
||||
message = self.message.format(date_field=self.date_field)
|
||||
raise ValidationError({
|
||||
self.field: message,
|
||||
})
|
||||
code = 'unique'
|
||||
raise ValidationError({self.field: [(message, code)]})
|
||||
|
||||
def __repr__(self):
|
||||
return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % (
|
||||
|
|
|
@ -31,12 +31,12 @@ class TestValidationErrorWithCode(TestCase):
|
|||
|
||||
def exception_handler(exc, request):
|
||||
return_errors = {}
|
||||
for field_name, errors in exc.detail.items():
|
||||
for field_name, errors in exc.full_details.items():
|
||||
return_errors[field_name] = []
|
||||
for error in errors:
|
||||
for message, code in errors:
|
||||
return_errors[field_name].append({
|
||||
'code': error.code,
|
||||
'message': error
|
||||
'code': code,
|
||||
'message': message
|
||||
})
|
||||
|
||||
return Response(return_errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
|
Loading…
Reference in New Issue
Block a user