aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authtoken/serializers.py8
-rw-r--r--rest_framework/exceptions.py14
-rw-r--r--rest_framework/fields.py21
-rw-r--r--rest_framework/serializers.py60
-rw-r--r--rest_framework/views.py27
5 files changed, 76 insertions, 54 deletions
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index c2c456de..a808d0a3 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -1,7 +1,7 @@
from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
-from rest_framework import serializers
+from rest_framework import exceptions, serializers
class AuthTokenSerializer(serializers.Serializer):
@@ -18,13 +18,13 @@ class AuthTokenSerializer(serializers.Serializer):
if user:
if not user.is_active:
msg = _('User account is disabled.')
- raise serializers.ValidationError(msg)
+ raise exceptions.ValidationFailed(msg)
else:
msg = _('Unable to log in with provided credentials.')
- raise serializers.ValidationError(msg)
+ raise exceptions.ValidationFailed(msg)
else:
msg = _('Must include "username" and "password"')
- raise serializers.ValidationError(msg)
+ raise exceptions.ValidationFailed(msg)
attrs['user'] = user
return attrs
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 06b5e8a2..b7c2d16d 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -24,6 +24,20 @@ class APIException(Exception):
return self.detail
+class ValidationFailed(APIException):
+ status_code = status.HTTP_400_BAD_REQUEST
+
+ def __init__(self, detail):
+ # 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 = detail
+
+ def __str__(self):
+ return str(self.detail)
+
+
class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'Malformed request.'
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 7053acee..b881ad13 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1,7 +1,8 @@
-from django import forms
from django.conf import settings
from django.core import validators
-from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone
from django.utils.datastructures import SortedDict
from django.utils.dateparse import parse_date, parse_datetime, parse_time
@@ -12,6 +13,7 @@ from rest_framework.compat import (
smart_text, EmailValidator, MinValueValidator, MaxValueValidator,
MinLengthValidator, MaxLengthValidator, URLValidator
)
+from rest_framework.exceptions import ValidationFailed
from rest_framework.settings import api_settings
from rest_framework.utils import html, representation, humanize_datetime
import copy
@@ -98,7 +100,7 @@ NOT_READ_ONLY_DEFAULT = 'May not set both `read_only` and `default`'
NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
USE_READONLYFIELD = 'Field(read_only=True) should be ReadOnlyField'
MISSING_ERROR_MESSAGE = (
- 'ValidationError raised by `{class_name}`, but error key `{key}` does '
+ 'ValidationFailed raised by `{class_name}`, but error key `{key}` does '
'not exist in the `error_messages` dictionary.'
)
@@ -263,7 +265,7 @@ class Field(object):
def run_validators(self, value):
"""
Test the given value against all the validators on the field,
- and either raise a `ValidationError` or simply return.
+ and either raise a `ValidationFailed` or simply return.
"""
errors = []
for validator in self.validators:
@@ -271,10 +273,12 @@ class Field(object):
validator.serializer_field = self
try:
validator(value)
- except ValidationError as exc:
+ except ValidationFailed as exc:
+ errors.extend(exc.detail)
+ except DjangoValidationError as exc:
errors.extend(exc.messages)
if errors:
- raise ValidationError(errors)
+ raise ValidationFailed(errors)
def validate(self, value):
pass
@@ -301,7 +305,8 @@ class Field(object):
class_name = self.__class__.__name__
msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
raise AssertionError(msg)
- raise ValidationError(msg.format(**kwargs))
+ message_string = msg.format(**kwargs)
+ raise ValidationFailed(message_string)
@property
def root(self):
@@ -946,7 +951,7 @@ class ImageField(FileField):
}
def __init__(self, *args, **kwargs):
- self._DjangoImageField = kwargs.pop('_DjangoImageField', forms.ImageField)
+ self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
super(ImageField, self).__init__(*args, **kwargs)
def to_internal_value(self, data):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 3bd7b17b..2f683562 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -10,10 +10,11 @@ python primitives.
2. The process of marshalling between python primitives and request and
response content is handled by parsers and renderers.
"""
-from django.core.exceptions import ImproperlyConfigured, ValidationError
+from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six
from django.utils.datastructures import SortedDict
+from rest_framework.exceptions import ValidationFailed
from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
@@ -100,14 +101,14 @@ class BaseSerializer(Field):
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.run_validation(self._initial_data)
- except ValidationError as exc:
+ except ValidationFailed as exc:
self._validated_data = {}
- self._errors = exc.message_dict
+ self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
- raise ValidationError(self._errors)
+ raise ValidationFailed(self._errors)
return not bool(self._errors)
@@ -175,24 +176,34 @@ class BoundField(object):
def __getattr__(self, attr_name):
return getattr(self._field, attr_name)
+ @property
+ def _proxy_class(self):
+ return self._field.__class__
+
+ def __repr__(self):
+ return '<%s value=%s errors=%s>' % (
+ self.__class__.__name__, self.value, self.errors
+ )
+
+
+class NestedBoundField(BoundField):
+ """
+ This BoundField additionally implements __iter__ and __getitem__
+ in order to support nested bound fields. This class is the type of
+ BoundField that is used for serializer fields.
+ """
def __iter__(self):
for field in self.fields.values():
yield self[field.field_name]
def __getitem__(self, key):
- assert hasattr(self, 'fields'), '"%s" is not a nested field. Cannot perform indexing.' % self.name
field = self.fields[key]
value = self.value.get(key) if self.value else None
error = self.errors.get(key) if self.errors else None
+ if isinstance(field, Serializer):
+ return NestedBoundField(field, value, error, prefix=self.name + '.')
return BoundField(field, value, error, prefix=self.name + '.')
- @property
- def _proxy_class(self):
- return self._field.__class__
-
- def __repr__(self):
- return '<%s value=%s errors=%s>' % (self.__class__.__name__, self.value, self.errors)
-
class BindingDict(object):
"""
@@ -308,7 +319,7 @@ class Serializer(BaseSerializer):
return None
if not isinstance(data, dict):
- raise ValidationError({
+ raise ValidationFailed({
api_settings.NON_FIELD_ERRORS_KEY: ['Invalid data']
})
@@ -317,9 +328,9 @@ class Serializer(BaseSerializer):
self.run_validators(value)
value = self.validate(value)
assert value is not None, '.validate() should return the validated data'
- except ValidationError as exc:
- raise ValidationError({
- api_settings.NON_FIELD_ERRORS_KEY: exc.messages
+ except ValidationFailed as exc:
+ raise ValidationFailed({
+ api_settings.NON_FIELD_ERRORS_KEY: exc.detail
})
return value
@@ -338,15 +349,15 @@ class Serializer(BaseSerializer):
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
- except ValidationError as exc:
- errors[field.field_name] = exc.messages
+ except ValidationFailed as exc:
+ errors[field.field_name] = exc.detail
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
- raise ValidationError(errors)
+ raise ValidationFailed(errors)
return ret
@@ -385,6 +396,8 @@ class Serializer(BaseSerializer):
field = self.fields[key]
value = self.data.get(key)
error = self.errors.get(key) if hasattr(self, '_errors') else None
+ if isinstance(field, Serializer):
+ return NestedBoundField(field, value, error)
return BoundField(field, value, error)
@@ -538,9 +551,12 @@ class ModelSerializer(Serializer):
ret = SortedDict()
model = getattr(self.Meta, 'model')
fields = getattr(self.Meta, 'fields', None)
+ exclude = getattr(self.Meta, 'exclude', None)
depth = getattr(self.Meta, 'depth', 0)
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
+ assert not fields and exclude, "Cannot set both 'fields' and 'exclude'."
+
extra_kwargs = self._include_additional_options(extra_kwargs)
# Retrieve metadata about fields & relationships on the model class.
@@ -551,12 +567,6 @@ class ModelSerializer(Serializer):
fields = self._get_default_field_names(declared_fields, info)
exclude = getattr(self.Meta, 'exclude', None)
if exclude is not None:
- warnings.warn(
- "The `Meta.exclude` option is pending deprecation. "
- "Use the explicit `Meta.fields` instead.",
- PendingDeprecationWarning,
- stacklevel=3
- )
for field_name in exclude:
fields.remove(field_name)
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 979229eb..292431c8 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -3,7 +3,7 @@ Provides an APIView class that is the base of all views in REST framework.
"""
from __future__ import unicode_literals
-from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
+from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
@@ -63,27 +63,20 @@ def exception_handler(exc):
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
- return Response({'detail': exc.detail},
- status=exc.status_code,
- headers=headers)
+ if isinstance(exc.detail, (list, dict)):
+ data = exc.detail
+ else:
+ data = {'detail': exc.detail}
- elif isinstance(exc, ValidationError):
- # ValidationErrors may include the non-field key named '__all__'.
- # When returning a response we map this to a key name that can be
- # modified in settings.
- if NON_FIELD_ERRORS in exc.message_dict:
- errors = exc.message_dict.pop(NON_FIELD_ERRORS)
- exc.message_dict[api_settings.NON_FIELD_ERRORS_KEY] = errors
- return Response(exc.message_dict,
- status=status.HTTP_400_BAD_REQUEST)
+ return Response(data, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
- return Response({'detail': 'Not found'},
- status=status.HTTP_404_NOT_FOUND)
+ data = {'detail': 'Not found'}
+ return Response(data, status=status.HTTP_404_NOT_FOUND)
elif isinstance(exc, PermissionDenied):
- return Response({'detail': 'Permission denied'},
- status=status.HTTP_403_FORBIDDEN)
+ data = {'detail': 'Permission denied'}
+ return Response(data, status=status.HTTP_403_FORBIDDEN)
# Note: Unhandled exceptions will raise a 500 error.
return None