aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/exceptions.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/exceptions.py')
-rw-r--r--rest_framework/exceptions.py126
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
+ ))