diff options
Diffstat (limited to 'rest_framework/exceptions.py')
| -rw-r--r-- | rest_framework/exceptions.py | 126 | 
1 files changed, 95 insertions, 31 deletions
| diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 425a7214..f954c13e 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -5,84 +5,148 @@ In addition Django's built in 403 and 404 exceptions are handled.  (`django.http.Http404` and `django.core.exceptions.PermissionDenied`)  """  from __future__ import unicode_literals +from django.utils import six +from django.utils.encoding import force_text +from django.utils.translation import ugettext_lazy as _, ungettext  from rest_framework import status +import math + + +def _force_text_recursive(data): +    """ +    Descend into a nested data structure, forcing any +    lazy translation strings into plain text. +    """ +    if isinstance(data, list): +        return [ +            _force_text_recursive(item) for item in data +        ] +    elif isinstance(data, dict): +        return dict([ +            (key, _force_text_recursive(value)) +            for key, value in data.items() +        ]) +    return force_text(data)  class APIException(Exception):      """      Base class for REST framework exceptions. -    Subclasses should provide `.status_code` and `.detail` properties. +    Subclasses should provide `.status_code` and `.default_detail` properties.      """ -    pass +    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR +    default_detail = _('A server error occurred.') +    def __init__(self, detail=None): +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail) -class ParseError(APIException): +    def __str__(self): +        return self.detail + + +# 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 -    default_detail = 'Malformed request.' -    def __init__(self, detail=None): -        self.detail = detail or self.default_detail +    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 = _force_text_recursive(detail) + +    def __str__(self): +        return six.text_type(self.detail) + + +class ParseError(APIException): +    status_code = status.HTTP_400_BAD_REQUEST +    default_detail = _('Malformed request.')  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 +    default_detail = _('Incorrect authentication credentials.')  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 +    default_detail = _('Authentication credentials were not provided.')  class PermissionDenied(APIException):      status_code = status.HTTP_403_FORBIDDEN -    default_detail = 'You do not have permission to perform this action.' +    default_detail = _('You do not have permission to perform this action.') -    def __init__(self, detail=None): -        self.detail = detail or self.default_detail + +class NotFound(APIException): +    status_code = status.HTTP_404_NOT_FOUND +    default_detail = _('Not found.')  class MethodNotAllowed(APIException):      status_code = status.HTTP_405_METHOD_NOT_ALLOWED -    default_detail = "Method '%s' not allowed." +    default_detail = _('Method "{method}" not allowed.')      def __init__(self, method, detail=None): -        self.detail = (detail or self.default_detail) % method +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail).format(method=method)  class NotAcceptable(APIException):      status_code = status.HTTP_406_NOT_ACCEPTABLE -    default_detail = "Could not satisfy the request's Accept header" +    default_detail = _('Could not satisfy the request Accept header.')      def __init__(self, detail=None, available_renderers=None): -        self.detail = detail or self.default_detail +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail)          self.available_renderers = available_renderers  class UnsupportedMediaType(APIException):      status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE -    default_detail = "Unsupported media type '%s' in request." +    default_detail = _('Unsupported media type "{media_type}" in request.')      def __init__(self, media_type, detail=None): -        self.detail = (detail or self.default_detail) % media_type +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail).format( +                media_type=media_type +            )  class Throttled(APIException):      status_code = status.HTTP_429_TOO_MANY_REQUESTS -    default_detail = "Request was throttled." -    extra_detail = "Expected available in %d second%s." +    default_detail = _('Request was throttled.') +    extra_detail_singular = 'Expected available in {wait} second.' +    extra_detail_plural = 'Expected available in {wait} seconds.'      def __init__(self, wait=None, detail=None): -        import math -        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 '') +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail) + +        if wait is None: +            self.wait = None          else: -            self.detail = detail or self.default_detail +            self.wait = math.ceil(wait) +            self.detail += ' ' + force_text(ungettext( +                self.extra_detail_singular.format(wait=self.wait), +                self.extra_detail_plural.format(wait=self.wait), +                self.wait +            )) | 
