diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index df8cad42d..95268cf60 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -74,16 +74,18 @@ In order to alter the style of the response, you could write the following custo The context argument is not used by the default handler, but can be useful if the exception handler needs further information such as the view currently being handled, which can be accessed as `context['view']`. -The exception handler must also be configured in your settings, using the `EXCEPTION_HANDLER` setting key. For example: +The exception handler and its error key must also be configured in your settings, using the `EXCEPTION_HANDLER` and `EXCEPTION_HANDLER_ERROR_KEY` setting keys. For example: REST_FRAMEWORK = { - 'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler' + 'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler', + 'EXCEPTION_HANDLER_ERROR_KEY': 'my_error' } -If not specified, the `'EXCEPTION_HANDLER'` setting defaults to the standard exception handler provided by REST framework: +If not specified, the `'EXCEPTION_HANDLER'` and `'EXCEPTION_HANDLER_ERROR_KEY'` settings default to the standard values provided by REST framework: REST_FRAMEWORK = { - 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler' + 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', + 'EXCEPTION_HANDLER_ERROR_KEY': 'detail' } Note that the exception handler will only be called for responses generated by raised exceptions. It will not be used for any responses returned directly by the view, such as the `HTTP_400_BAD_REQUEST` responses that are returned by the generic views when serializer validation fails. diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 6d9ed2355..1a68ea2ac 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -82,6 +82,7 @@ DEFAULTS = { # Exception handling 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', + 'EXCEPTION_HANDLER_ERROR_KEY': 'detail', 'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing diff --git a/rest_framework/views.py b/rest_framework/views.py index a8710c7a0..d7790c422 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -63,6 +63,8 @@ def exception_handler(exc, context): Any unhandled exceptions may return `None`, which will cause a 500 error to be raised. """ + error_key = api_settings.EXCEPTION_HANDLER_ERROR_KEY + if isinstance(exc, exceptions.APIException): headers = {} if getattr(exc, 'auth_header', None): @@ -73,21 +75,21 @@ def exception_handler(exc, context): if isinstance(exc.detail, (list, dict)): data = exc.detail else: - data = {'detail': exc.detail} + data = {error_key: exc.detail} set_rollback() return Response(data, status=exc.status_code, headers=headers) elif isinstance(exc, Http404): msg = _('Not found.') - data = {'detail': six.text_type(msg)} + data = {error_key: six.text_type(msg)} set_rollback() return Response(data, status=status.HTTP_404_NOT_FOUND) elif isinstance(exc, PermissionDenied): msg = _('Permission denied.') - data = {'detail': six.text_type(msg)} + data = {error_key: six.text_type(msg)} set_rollback() return Response(data, status=status.HTTP_403_FORBIDDEN) diff --git a/tests/test_views.py b/tests/test_views.py index 05c499481..b301975f3 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -7,6 +7,7 @@ from django.test import TestCase from rest_framework import status from rest_framework.decorators import api_view +from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory @@ -42,12 +43,12 @@ def basic_view(request): class ErrorView(APIView): def get(self, request, *args, **kwargs): - raise Exception + raise APIException('Error!') @api_view(['GET']) def error_view(request): - raise Exception + raise APIException('Error!') def sanitise_json_error(error_dict): @@ -118,3 +119,32 @@ class TestCustomExceptionHandler(TestCase): expected = 'Error!' self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data, expected) + + +class TestCustomExceptionHandlerErrorKey(TestCase): + def setUp(self): + self.DEFAULT_ERROR_KEY = api_settings.EXCEPTION_HANDLER_ERROR_KEY + api_settings.EXCEPTION_HANDLER_ERROR_KEY = 'my_error' + + def tearDown(self): + api_settings.EXCEPTION_HANDLER_ERROR_KEY = self.DEFAULT_ERROR_KEY + + def test_class_based_view_exception_handler_error_key(self): + view = ErrorView.as_view() + + request = factory.get('/', content_type='application/json') + response = view(request) + expected = {'my_error': 'Error!'} + self.assertEqual(response.status_code, + status.HTTP_500_INTERNAL_SERVER_ERROR) + self.assertEqual(response.data, expected) + + def test_function_based_view_exception_handler_error_key(self): + view = error_view + + request = factory.get('/', content_type='application/json') + response = view(request) + expected = {'my_error': 'Error!'} + self.assertEqual(response.status_code, + status.HTTP_500_INTERNAL_SERVER_ERROR) + self.assertEqual(response.data, expected)