diff options
| author | Tom Christie | 2012-10-05 10:23:47 +0100 |
|---|---|---|
| committer | Tom Christie | 2012-10-05 10:23:47 +0100 |
| commit | d07dc77e91c1f99b47915b3cef30b565f2618e82 (patch) | |
| tree | 9f68ca55f685090f1672a557ce985931ccee47b3 /rest_framework | |
| parent | ad5e6eb16f4db928e1fc8d0a6af4f9f4584f7b08 (diff) | |
| download | django-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.py | 16 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 19 | ||||
| -rw-r--r-- | rest_framework/response.py | 23 | ||||
| -rw-r--r-- | rest_framework/tests/decorators.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/negotiation.py | 58 | ||||
| -rw-r--r-- | rest_framework/views.py | 2 |
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(): |
