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.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, status=None,
|
||||
template_name=None, headers=None,
|
||||
exception=False, content_type=None):
|
||||
def __init__(
|
||||
self,
|
||||
data=None,
|
||||
status=None,
|
||||
template_name=None,
|
||||
headers=None,
|
||||
exception=False,
|
||||
content_type=None,
|
||||
reason=None,
|
||||
):
|
||||
"""
|
||||
Alters the init arguments slightly.
|
||||
For example, drop 'template_name', and instead use 'data'.
|
||||
|
@ -41,6 +48,8 @@ class Response(SimpleTemplateResponse):
|
|||
self.template_name = template_name
|
||||
self.exception = exception
|
||||
self.content_type = content_type
|
||||
if reason:
|
||||
self.reason_phrase = "{} ({})".format(self.status_text, reason)
|
||||
|
||||
if headers:
|
||||
for name, value in headers.items():
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.http import Http404
|
|||
from django.http.response import HttpResponseBase
|
||||
from django.utils.cache import cc_delim_re, patch_vary_headers
|
||||
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.generic import View
|
||||
|
||||
|
@ -92,11 +93,15 @@ def exception_handler(exc, context):
|
|||
|
||||
if isinstance(exc.detail, (list, dict)):
|
||||
data = exc.detail
|
||||
reason = _('See response body for full details')
|
||||
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()
|
||||
return Response(data, status=exc.status_code, headers=headers)
|
||||
return Response(data, status=exc.status_code, headers=headers, reason=reason)
|
||||
|
||||
return None
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class MockTextMediaRenderer(BaseRenderer):
|
|||
|
||||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
DUMMYREASON = 'dummyreason'
|
||||
|
||||
|
||||
def RENDERER_A_SERIALIZER(x):
|
||||
|
@ -78,6 +79,12 @@ class MockViewSettingContentType(APIView):
|
|||
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):
|
||||
parser_classes = (JSONParser,)
|
||||
|
||||
|
@ -125,7 +132,8 @@ urlpatterns = [
|
|||
path('html1', HTMLView1.as_view()),
|
||||
path('html_new_model', HTMLNewModelView.as_view()),
|
||||
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.assertContains(resp, 'Text comes here')
|
||||
# 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 re
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import APISettings, api_settings
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
@ -12,6 +14,9 @@ from rest_framework.views import APIView
|
|||
factory = APIRequestFactory()
|
||||
|
||||
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):
|
||||
|
@ -39,6 +44,11 @@ class ErrorView(APIView):
|
|||
raise Exception
|
||||
|
||||
|
||||
class ValidationErrorView(APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
raise ValidationError(VALIDATION_ERROR)
|
||||
|
||||
|
||||
def custom_handler(exc, context):
|
||||
if isinstance(exc, SyntaxError):
|
||||
return Response({'error': 'SyntaxError'}, status=400)
|
||||
|
@ -57,6 +67,11 @@ def error_view(request):
|
|||
raise Exception
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def validation_error_view(request):
|
||||
raise ValidationError(VALIDATION_ERROR)
|
||||
|
||||
|
||||
def sanitise_json_error(error_dict):
|
||||
"""
|
||||
Exact contents of JSON error messages depend on the installed version
|
||||
|
@ -69,32 +84,44 @@ def sanitise_json_error(error_dict):
|
|||
|
||||
|
||||
class ClassBasedViewIntegrationTests(TestCase):
|
||||
def setUp(self):
|
||||
self.view = BasicView.as_view()
|
||||
|
||||
def test_400_parse_error(self):
|
||||
request = factory.post('/', 'f00bar', content_type='application/json')
|
||||
response = self.view(request)
|
||||
response = BasicView.as_view()(request)
|
||||
expected = {
|
||||
'detail': JSON_ERROR
|
||||
}
|
||||
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
|
||||
|
||||
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):
|
||||
def setUp(self):
|
||||
self.view = basic_view
|
||||
|
||||
def test_400_parse_error(self):
|
||||
request = factory.post('/', 'f00bar', content_type='application/json')
|
||||
response = self.view(request)
|
||||
response = basic_view(request)
|
||||
expected = {
|
||||
'detail': JSON_ERROR
|
||||
}
|
||||
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
|
||||
|
||||
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):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue
Block a user