diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 0c96ecdd5..829f11382 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -11,49 +11,50 @@ from rest_framework import status class APIException(Exception): """ Base class for REST framework exceptions. - Subclasses should provide `.status_code` and `.detail` properties. + Subclasses should provide `.status_code` and `.data` properties. + + The `.data` is a dictionary that usually contains just one + field: "detail". However, some exception classes may override + it. """ - pass + + def __init__(self, detail=None): + self.data = {'detail': detail or self.default_detail} class ParseError(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = 'Malformed request.' - def __init__(self, detail=None): - self.detail = detail or self.default_detail + +class DeserializeError(APIException): + status_code = status.HTTP_400_BAD_REQUEST + + def __init__(self, errors): + self.data = dict(errors) class AuthenticationFailed(APIException): status_code = status.HTTP_401_UNAUTHORIZED default_detail = 'Incorrect authentication credentials.' - def __init__(self, detail=None): - self.detail = detail or self.default_detail - class NotAuthenticated(APIException): status_code = status.HTTP_401_UNAUTHORIZED default_detail = 'Authentication credentials were not provided.' - def __init__(self, detail=None): - self.detail = detail or self.default_detail - class PermissionDenied(APIException): status_code = status.HTTP_403_FORBIDDEN default_detail = 'You do not have permission to perform this action.' - def __init__(self, detail=None): - self.detail = detail or self.default_detail - class MethodNotAllowed(APIException): status_code = status.HTTP_405_METHOD_NOT_ALLOWED default_detail = "Method '%s' not allowed." def __init__(self, method, detail=None): - self.detail = (detail or self.default_detail) % method + self.data = {'detail': (detail or self.default_detail) % method} class NotAcceptable(APIException): @@ -61,7 +62,9 @@ class NotAcceptable(APIException): default_detail = "Could not satisfy the request's Accept header" def __init__(self, detail=None, available_renderers=None): - self.detail = detail or self.default_detail + super(NotAcceptable, self).__init__(detail) + # TODO: self.available_renderers not used anywhere + # across the code self.available_renderers = available_renderers @@ -70,7 +73,7 @@ class UnsupportedMediaType(APIException): default_detail = "Unsupported media type '%s' in request." def __init__(self, media_type, detail=None): - self.detail = (detail or self.default_detail) % media_type + self.data = {'detail': (detail or self.default_detail) % media_type} class Throttled(APIException): @@ -83,9 +86,10 @@ class Throttled(APIException): self.wait = wait and math.ceil(wait) or None if wait is not None: format = detail or self.default_detail + self.extra_detail - self.detail = format % (self.wait, self.wait != 1 and 's' or '') + self.data = {'detail': + format % (self.wait, self.wait != 1 and 's' or '')} else: - self.detail = detail or self.default_detail + self.data = {'detail': detail or self.default_detail} class ConfigurationError(Exception): diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index f11def6d4..b190e5728 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -7,7 +7,7 @@ which allows mixin classes to be composed in interesting ways. from __future__ import unicode_literals from django.http import Http404 -from rest_framework import status +from rest_framework import status, exceptions from rest_framework.response import Response from rest_framework.request import clone_request import warnings @@ -55,7 +55,7 @@ class CreateModelMixin(object): return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + raise exceptions.DeserializeError(serializer.errors) def get_success_headers(self, data): try: @@ -132,7 +132,7 @@ class UpdateModelMixin(object): self.post_save(self.object, created=created) return Response(serializer.data, status=success_status_code) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + raise exceptions.DeserializeError(serializer.errors) def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True diff --git a/rest_framework/views.py b/rest_framework/views.py index e1b6705b6..193d4fb27 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -283,8 +283,7 @@ class APIView(View): exc.status_code = status.HTTP_403_FORBIDDEN if isinstance(exc, exceptions.APIException): - return Response({'detail': exc.detail}, - status=exc.status_code, + return Response(exc.data, status=exc.status_code, exception=True) elif isinstance(exc, Http404): return Response({'detail': 'Not found'},