mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 16:40:03 +03:00
Add _reason_phrase to Response
Improve reason_phrase for error details that are length 1 lists. Use reason_phrase property rather than non-public attribute Replace f-strings with format for py35 compatability Replace missed f-string with format
This commit is contained in:
parent
96993d817a
commit
3e1fca171b
|
@ -17,9 +17,16 @@ class Response(SimpleTemplateResponse):
|
||||||
arbitrary media types.
|
arbitrary media types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data=None, status=None,
|
def __init__(
|
||||||
template_name=None, headers=None,
|
self,
|
||||||
exception=False, content_type=None):
|
data=None,
|
||||||
|
status=None,
|
||||||
|
template_name=None,
|
||||||
|
headers=None,
|
||||||
|
exception=False,
|
||||||
|
content_type=None,
|
||||||
|
reason=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Alters the init arguments slightly.
|
Alters the init arguments slightly.
|
||||||
For example, drop 'template_name', and instead use 'data'.
|
For example, drop 'template_name', and instead use 'data'.
|
||||||
|
@ -41,6 +48,8 @@ class Response(SimpleTemplateResponse):
|
||||||
self.template_name = template_name
|
self.template_name = template_name
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
|
if reason:
|
||||||
|
self.reason_phrase = "{} ({})".format(self.status_text, reason)
|
||||||
|
|
||||||
if headers:
|
if headers:
|
||||||
for name, value in headers.items():
|
for name, value in headers.items():
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.http import Http404
|
||||||
from django.http.response import HttpResponseBase
|
from django.http.response import HttpResponseBase
|
||||||
from django.utils.cache import cc_delim_re, patch_vary_headers
|
from django.utils.cache import cc_delim_re, patch_vary_headers
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
|
@ -92,11 +93,15 @@ def exception_handler(exc, context):
|
||||||
|
|
||||||
if isinstance(exc.detail, (list, dict)):
|
if isinstance(exc.detail, (list, dict)):
|
||||||
data = exc.detail
|
data = exc.detail
|
||||||
|
reason = _('See response body for full details')
|
||||||
else:
|
else:
|
||||||
data = {'detail': exc.detail}
|
data = {"detail": exc.detail}
|
||||||
|
reason = str(exc.detail)
|
||||||
|
if isinstance(exc.detail, list) and len(exc.detail) == 1:
|
||||||
|
reason = exc.detail[0]
|
||||||
|
|
||||||
set_rollback()
|
set_rollback()
|
||||||
return Response(data, status=exc.status_code, headers=headers)
|
return Response(data, status=exc.status_code, headers=headers, reason=reason)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class MockTextMediaRenderer(BaseRenderer):
|
||||||
|
|
||||||
DUMMYSTATUS = status.HTTP_200_OK
|
DUMMYSTATUS = status.HTTP_200_OK
|
||||||
DUMMYCONTENT = 'dummycontent'
|
DUMMYCONTENT = 'dummycontent'
|
||||||
|
DUMMYREASON = 'dummyreason'
|
||||||
|
|
||||||
|
|
||||||
def RENDERER_A_SERIALIZER(x):
|
def RENDERER_A_SERIALIZER(x):
|
||||||
|
@ -78,6 +79,12 @@ class MockViewSettingContentType(APIView):
|
||||||
return Response(DUMMYCONTENT, status=DUMMYSTATUS, content_type='setbyview')
|
return Response(DUMMYCONTENT, status=DUMMYSTATUS, content_type='setbyview')
|
||||||
|
|
||||||
|
|
||||||
|
class MockViewSettingReason(APIView):
|
||||||
|
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
return Response(DUMMYCONTENT, status=DUMMYSTATUS, reason=DUMMYREASON)
|
||||||
|
|
||||||
|
|
||||||
class JSONView(APIView):
|
class JSONView(APIView):
|
||||||
parser_classes = (JSONParser,)
|
parser_classes = (JSONParser,)
|
||||||
|
|
||||||
|
@ -125,7 +132,8 @@ urlpatterns = [
|
||||||
path('html1', HTMLView1.as_view()),
|
path('html1', HTMLView1.as_view()),
|
||||||
path('html_new_model', HTMLNewModelView.as_view()),
|
path('html_new_model', HTMLNewModelView.as_view()),
|
||||||
path('html_new_model_viewset', include(new_model_viewset_router.urls)),
|
path('html_new_model_viewset', include(new_model_viewset_router.urls)),
|
||||||
path('restframework', include('rest_framework.urls', namespace='rest_framework'))
|
path('restframework', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
path('with_reason', MockViewSettingReason.as_view())
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -283,3 +291,14 @@ class Issue807Tests(TestCase):
|
||||||
self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
|
self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
|
||||||
# self.assertContains(resp, 'Text comes here')
|
# self.assertContains(resp, 'Text comes here')
|
||||||
# self.assertContains(resp, 'Text description.')
|
# self.assertContains(resp, 'Text description.')
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='tests.test_response')
|
||||||
|
class ReasonPhraseTests(TestCase):
|
||||||
|
def test_reason_is_set(self):
|
||||||
|
resp = self.client.get('/with_reason')
|
||||||
|
self.assertEqual("OK ({})".format(DUMMYREASON), resp.reason_phrase)
|
||||||
|
|
||||||
|
def test_reason_phrase_with_no_reason(self):
|
||||||
|
resp = self.client.get('/')
|
||||||
|
self.assertEqual("OK", resp.reason_phrase)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import APISettings, api_settings
|
from rest_framework.settings import APISettings, api_settings
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
|
@ -12,6 +14,9 @@ from rest_framework.views import APIView
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
JSON_ERROR = 'JSON parse error - Expecting value:'
|
JSON_ERROR = 'JSON parse error - Expecting value:'
|
||||||
|
STATUS_CODE_400 = 'Bad Request'
|
||||||
|
REASON_PHRASE_RE = r"{} \({}.*\)".format(STATUS_CODE_400, JSON_ERROR)
|
||||||
|
VALIDATION_ERROR = "Data isn't valid!"
|
||||||
|
|
||||||
|
|
||||||
class BasicView(APIView):
|
class BasicView(APIView):
|
||||||
|
@ -39,6 +44,11 @@ class ErrorView(APIView):
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationErrorView(APIView):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
raise ValidationError(VALIDATION_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def custom_handler(exc, context):
|
def custom_handler(exc, context):
|
||||||
if isinstance(exc, SyntaxError):
|
if isinstance(exc, SyntaxError):
|
||||||
return Response({'error': 'SyntaxError'}, status=400)
|
return Response({'error': 'SyntaxError'}, status=400)
|
||||||
|
@ -57,6 +67,11 @@ def error_view(request):
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def validation_error_view(request):
|
||||||
|
raise ValidationError(VALIDATION_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def sanitise_json_error(error_dict):
|
def sanitise_json_error(error_dict):
|
||||||
"""
|
"""
|
||||||
Exact contents of JSON error messages depend on the installed version
|
Exact contents of JSON error messages depend on the installed version
|
||||||
|
@ -69,32 +84,44 @@ def sanitise_json_error(error_dict):
|
||||||
|
|
||||||
|
|
||||||
class ClassBasedViewIntegrationTests(TestCase):
|
class ClassBasedViewIntegrationTests(TestCase):
|
||||||
def setUp(self):
|
|
||||||
self.view = BasicView.as_view()
|
|
||||||
|
|
||||||
def test_400_parse_error(self):
|
def test_400_parse_error(self):
|
||||||
request = factory.post('/', 'f00bar', content_type='application/json')
|
request = factory.post('/', 'f00bar', content_type='application/json')
|
||||||
response = self.view(request)
|
response = BasicView.as_view()(request)
|
||||||
expected = {
|
expected = {
|
||||||
'detail': JSON_ERROR
|
'detail': JSON_ERROR
|
||||||
}
|
}
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert re.match(REASON_PHRASE_RE, response.reason_phrase)
|
||||||
assert sanitise_json_error(response.data) == expected
|
assert sanitise_json_error(response.data) == expected
|
||||||
|
|
||||||
|
def test_400_validation_error(self):
|
||||||
|
request = factory.get('/')
|
||||||
|
response = ValidationErrorView.as_view()(request)
|
||||||
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.reason_phrase == "{} ({})".format(STATUS_CODE_400, VALIDATION_ERROR)
|
||||||
|
assert response.data == [VALIDATION_ERROR]
|
||||||
|
|
||||||
|
|
||||||
class FunctionBasedViewIntegrationTests(TestCase):
|
class FunctionBasedViewIntegrationTests(TestCase):
|
||||||
def setUp(self):
|
|
||||||
self.view = basic_view
|
|
||||||
|
|
||||||
def test_400_parse_error(self):
|
def test_400_parse_error(self):
|
||||||
request = factory.post('/', 'f00bar', content_type='application/json')
|
request = factory.post('/', 'f00bar', content_type='application/json')
|
||||||
response = self.view(request)
|
response = basic_view(request)
|
||||||
expected = {
|
expected = {
|
||||||
'detail': JSON_ERROR
|
'detail': JSON_ERROR
|
||||||
}
|
}
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert re.match(REASON_PHRASE_RE, response.reason_phrase)
|
||||||
assert sanitise_json_error(response.data) == expected
|
assert sanitise_json_error(response.data) == expected
|
||||||
|
|
||||||
|
def test_400_validation_error(self):
|
||||||
|
request = factory.get('/')
|
||||||
|
response = validation_error_view(request)
|
||||||
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.reason_phrase == "{} ({})".format(STATUS_CODE_400, VALIDATION_ERROR)
|
||||||
|
assert response.data == [VALIDATION_ERROR]
|
||||||
|
|
||||||
|
|
||||||
class TestCustomExceptionHandler(TestCase):
|
class TestCustomExceptionHandler(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user