aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2012-10-05 10:23:47 +0100
committerTom Christie2012-10-05 10:23:47 +0100
commitd07dc77e91c1f99b47915b3cef30b565f2618e82 (patch)
tree9f68ca55f685090f1672a557ce985931ccee47b3 /rest_framework
parentad5e6eb16f4db928e1fc8d0a6af4f9f4584f7b08 (diff)
downloaddjango-rest-framework-d07dc77e91c1f99b47915b3cef30b565f2618e82.tar.bz2
Accepted media type uses most specific of client/renderer media types.
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/negotiation.py16
-rw-r--r--rest_framework/renderers.py19
-rw-r--r--rest_framework/response.py23
-rw-r--r--rest_framework/tests/decorators.py2
-rw-r--r--rest_framework/tests/negotiation.py58
-rw-r--r--rest_framework/views.py2
6 files changed, 82 insertions, 38 deletions
diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py
index 0d3b368c..73ae7899 100644
--- a/rest_framework/negotiation.py
+++ b/rest_framework/negotiation.py
@@ -1,6 +1,6 @@
from rest_framework import exceptions
from rest_framework.settings import api_settings
-from rest_framework.utils.mediatypes import order_by_precedence
+from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
class BaseContentNegotiation(object):
@@ -46,8 +46,16 @@ class DefaultContentNegotiation(object):
for media_type_set in order_by_precedence(accepts):
for renderer in renderers:
for media_type in media_type_set:
- if renderer.can_handle_media_type(media_type):
- return renderer, media_type
+ if media_type_matches(renderer.media_type, media_type):
+ # Return the most specific media type as accepted.
+ if len(renderer.media_type) > len(media_type):
+ # Eg client requests '*/*'
+ # Accepted media type is 'application/json'
+ return renderer, renderer.media_type
+ else:
+ # Eg client requests 'application/json; indent=8'
+ # Accepted media type is 'application/json; indent=8'
+ return renderer, media_type
raise exceptions.NotAcceptable(available_renderers=renderers)
@@ -57,7 +65,7 @@ class DefaultContentNegotiation(object):
so that we only negotiation against those that accept that format.
"""
renderers = [renderer for renderer in renderers
- if renderer.can_handle_format(format)]
+ if renderer.format == format]
if not renderers:
raise exceptions.InvalidFormat(format)
return renderers
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index e33fa30e..6a95815a 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -15,7 +15,7 @@ from rest_framework.request import clone_request
from rest_framework.utils import dict2xml
from rest_framework.utils import encoders
from rest_framework.utils.breadcrumbs import get_breadcrumbs
-from rest_framework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
+from rest_framework.utils.mediatypes import get_media_type_params, add_media_type_param
from rest_framework import VERSION
from rest_framework import serializers
@@ -32,23 +32,6 @@ class BaseRenderer(object):
def __init__(self, view=None):
self.view = view
- def can_handle_format(self, format):
- return format == self.format
-
- def can_handle_media_type(self, media_type):
- """
- Returns `True` if this renderer is able to deal with the given
- media type.
-
- The default implementation for this function is to check the media type
- argument against the media_type attribute set on the class to see if
- they match.
-
- This may be overridden to provide for other behavior, but typically
- you'll instead want to just set the `media_type` attribute on the class.
- """
- return media_type_matches(self.media_type, media_type)
-
def render(self, obj=None, media_type=None):
"""
Given an object render it into a string.
diff --git a/rest_framework/response.py b/rest_framework/response.py
index db6bf3e2..fca631c3 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -20,26 +20,21 @@ class Response(SimpleTemplateResponse):
super(Response, self).__init__(None, status=status)
self.data = data
self.headers = headers and headers[:] or []
- self.renderer = renderer
-
- # Accepted media type is the portion of the request Accept header
- # that the renderer satisfied. It could be '*/*', or somthing like
- # application/json; indent=4
- #
- # This is NOT the value that will be returned in the 'Content-Type'
- # header, but we do need to know the value in case there are
- # any specific parameters which affect the rendering process.
+
+ self.accepted_renderer = renderer
self.accepted_media_type = accepted_media_type
@property
def rendered_content(self):
- assert self.renderer, "No renderer set on Response"
+ renderer = self.accepted_renderer
+
+ assert renderer, "No renderer set on Response"
- self['Content-Type'] = self.renderer.media_type
+ self['content-type'] = self.accepted_media_type
if self.data is None:
- return self.renderer.render()
- render_media_type = self.accepted_media_type or self.renderer.media_type
- return self.renderer.render(self.data, render_media_type)
+ return renderer.render()
+
+ return renderer.render(self.data, self.accepted_media_type)
@property
def status_text(self):
diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py
index 4be53786..e943d8fe 100644
--- a/rest_framework/tests/decorators.py
+++ b/rest_framework/tests/decorators.py
@@ -58,7 +58,7 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
- self.assertTrue(isinstance(response.renderer, JSONRenderer))
+ self.assertTrue(isinstance(response.accepted_renderer, JSONRenderer))
def test_parser_classes(self):
diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/negotiation.py
new file mode 100644
index 00000000..dd9f6a76
--- /dev/null
+++ b/rest_framework/tests/negotiation.py
@@ -0,0 +1,58 @@
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework.decorators import api_view, renderer_classes
+from rest_framework.negotiation import DefaultContentNegotiation
+from rest_framework.response import Response
+
+factory = RequestFactory()
+
+
+class MockJSONRenderer(object):
+ media_type = 'application/json'
+
+ def __init__(self, view):
+ pass
+
+
+class MockHTMLRenderer(object):
+ media_type = 'text/html'
+
+ def __init__(self, view):
+ pass
+
+
+@api_view(('GET',))
+@renderer_classes((MockJSONRenderer, MockHTMLRenderer))
+def example(request):
+ return Response()
+
+
+class TestAcceptedMediaType(TestCase):
+ def setUp(self):
+ self.renderers = [MockJSONRenderer(None), MockHTMLRenderer(None)]
+ self.negotiator = DefaultContentNegotiation()
+
+ def negotiate(self, request):
+ return self.negotiator.negotiate(request, self.renderers)
+
+ def test_client_without_accept_use_renderer(self):
+ request = factory.get('/')
+ accepted_renderer, accepted_media_type = self.negotiate(request)
+ self.assertEquals(accepted_media_type, 'application/json')
+
+ def test_client_underspecifies_accept_use_renderer(self):
+ request = factory.get('/', HTTP_ACCEPT='*/*')
+ accepted_renderer, accepted_media_type = self.negotiate(request)
+ self.assertEquals(accepted_media_type, 'application/json')
+
+ def test_client_overspecifies_accept_use_client(self):
+ request = factory.get('/', HTTP_ACCEPT='application/json; indent=8')
+ accepted_renderer, accepted_media_type = self.negotiate(request)
+ self.assertEquals(accepted_media_type, 'application/json; indent=8')
+
+
+class IntegrationTests(TestCase):
+ def test_accepted_negotiation_set_on_request(self):
+ request = factory.get('/', HTTP_ACCEPT='*/*')
+ response = example(request)
+ self.assertEquals(response.accepted_media_type, 'application/json')
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 2bbdbe17..4dd0d208 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -211,7 +211,7 @@ class APIView(View):
if isinstance(response, Response):
if not getattr(self, 'renderer', None):
self.renderer, self.accepted_media_type = self.perform_content_negotiation(request, force=True)
- response.renderer = self.renderer
+ response.accepted_renderer = self.renderer
response.accepted_media_type = self.accepted_media_type
for key, value in self.headers.items():