diff --git a/rest_framework/handlers.py b/rest_framework/handlers.py new file mode 100644 index 000000000..3dd54510c --- /dev/null +++ b/rest_framework/handlers.py @@ -0,0 +1,43 @@ +"""Default handlers, configurable in settings""" + +from rest_framework.response import Response +from rest_framework import exceptions +from rest_framework import status +from django.core.exceptions import PermissionDenied +from django.http import Http404 + + +def handle_exception(view_instance, exc): + """ + Default exception handler for APIView. + + Handle any exception that occurs, by returning an appropriate response, + or re-raising the error. + """ + if isinstance(exc, exceptions.Throttled): + # Throttle wait header + view_instance.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait + + if isinstance(exc, (exceptions.NotAuthenticated, + exceptions.AuthenticationFailed)): + # WWW-Authenticate header for 401 responses, else coerce to 403 + auth_header = view_instance.get_authenticate_header( + view_instance.request) + + if auth_header: + view_instance.headers['WWW-Authenticate'] = auth_header + else: + exc.status_code = status.HTTP_403_FORBIDDEN + + if isinstance(exc, exceptions.APIException): + return Response(exc.data, status=exc.status_code, + exception=True) + elif isinstance(exc, Http404): + return Response({'detail': 'Not found'}, + status=status.HTTP_404_NOT_FOUND, + exception=True) + elif isinstance(exc, PermissionDenied): + return Response({'detail': 'Permission denied'}, + status=status.HTTP_403_FORBIDDEN, + exception=True) + raise diff --git a/rest_framework/settings.py b/rest_framework/settings.py index beb511aca..769ff2178 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -59,6 +59,9 @@ DEFAULTS = { 'rest_framework.pagination.PaginationSerializer', 'DEFAULT_FILTER_BACKENDS': (), + # Exception handling + 'DEFAULT_EXCEPTION_HANDLER': 'rest_framework.handlers.handle_exception', + # Throttling 'DEFAULT_THROTTLE_RATES': { 'user': None, @@ -117,6 +120,7 @@ IMPORT_STRINGS = ( 'FILTER_BACKEND', 'UNAUTHENTICATED_USER', 'UNAUTHENTICATED_TOKEN', + 'DEFAULT_EXCEPTION_HANDLER', ) diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index 70da08704..7dee3b78a 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -343,7 +343,7 @@ class TestInstanceView(TestCase): self.assertIn('text', response.data) self.assertEqual( response.data['text'], - [u'Ensure this value has at most 100 characters (it has 120).']) + ['Ensure this value has at most 100 characters (it has 120).']) def test_put_to_deleted_instance(self): """ diff --git a/rest_framework/views.py b/rest_framework/views.py index 9f36597c9..1304cebe7 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -5,8 +5,7 @@ from __future__ import unicode_literals import warnings -from django.core.exceptions import PermissionDenied -from django.http import Http404, HttpResponse +from django.http import HttpResponse from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions @@ -26,6 +25,7 @@ class APIView(View): throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS + handle_exception = api_settings.DEFAULT_EXCEPTION_HANDLER @classmethod def as_view(cls, **initkwargs): @@ -265,38 +265,6 @@ class APIView(View): return response - def handle_exception(self, exc): - """ - Handle any exception that occurs, by returning an appropriate response, - or re-raising the error. - """ - if isinstance(exc, exceptions.Throttled): - # Throttle wait header - self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait - - if isinstance(exc, (exceptions.NotAuthenticated, - exceptions.AuthenticationFailed)): - # WWW-Authenticate header for 401 responses, else coerce to 403 - auth_header = self.get_authenticate_header(self.request) - - if auth_header: - self.headers['WWW-Authenticate'] = auth_header - else: - exc.status_code = status.HTTP_403_FORBIDDEN - - if isinstance(exc, exceptions.APIException): - return Response(exc.data, status=exc.status_code, - exception=True) - elif isinstance(exc, Http404): - return Response({'detail': 'Not found'}, - status=status.HTTP_404_NOT_FOUND, - exception=True) - elif isinstance(exc, PermissionDenied): - return Response({'detail': 'Permission denied'}, - status=status.HTTP_403_FORBIDDEN, - exception=True) - raise - # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. @csrf_exempt