diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index ad98de3b9..b95091c59 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -7,3 +7,9 @@ > — [RFC 2616][cite], Fielding et al. [cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html + +**TODO**: Describe content negotiation style used by REST framework. + +## Custom content negotiation + +It's unlikley that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme, override `BaseContentNegotiation`, and implement the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` \ No newline at end of file diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index 8b22f6690..444f80561 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -4,45 +4,34 @@ from rest_framework.utils.mediatypes import order_by_precedence, media_type_matc class BaseContentNegotiation(object): - def negotiate(self, request, renderers, format=None, force=False): - raise NotImplementedError('.negotiate() must be implemented') + def select_parser(self, request, parsers): + raise NotImplementedError('.select_parser() must be implemented') + + def select_renderer(self, request, renderers, format_suffix=None): + raise NotImplementedError('.select_renderer() must be implemented') -class DefaultContentNegotiation(object): +class DefaultContentNegotiation(BaseContentNegotiation): settings = api_settings - def select_parser(self, parsers, media_type): + def select_parser(self, request, parsers): """ Given a list of parsers and a media type, return the appropriate parser to handle the incoming request. """ for parser in parsers: - if media_type_matches(parser.media_type, media_type): + if media_type_matches(parser.media_type, request.content_type): return parser return None - def negotiate(self, request, renderers, format=None, force=False): + def select_renderer(self, request, renderers, format_suffix=None): """ Given a request and a list of renderers, return a two-tuple of: (renderer, media type). - - If force is set, then suppress exceptions, and forcibly return a - fallback renderer and media_type. - """ - try: - return self.unforced_negotiate(request, renderers, format) - except (exceptions.InvalidFormat, exceptions.NotAcceptable): - if force: - return (renderers[0], renderers[0].media_type) - raise - - def unforced_negotiate(self, request, renderers, format=None): - """ - As `.negotiate()`, but does not take the optional `force` agument, - or suppress exceptions. """ # Allow URL style format override. eg. "?format=json - format = format or request.GET.get(self.settings.URL_FORMAT_OVERRIDE) + format_query_param = self.settings.URL_FORMAT_OVERRIDE + format = format_suffix or request.GET.get(format_query_param) if format: renderers = self.filter_renderers(renderers, format) diff --git a/rest_framework/request.py b/rest_framework/request.py index b9d55de40..b212680f8 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -266,7 +266,7 @@ class Request(object): if stream is None or media_type is None: return (None, None) - parser = self.negotiator.select_parser(self.parsers, media_type) + parser = self.negotiator.select_parser(self, self.parsers) if not parser: raise exceptions.UnsupportedMediaType(media_type) diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/negotiation.py index d8265b438..e06354ead 100644 --- a/rest_framework/tests/negotiation.py +++ b/rest_framework/tests/negotiation.py @@ -18,20 +18,20 @@ class TestAcceptedMediaType(TestCase): self.renderers = [MockJSONRenderer(), MockHTMLRenderer()] self.negotiator = DefaultContentNegotiation() - def negotiate(self, request): - return self.negotiator.negotiate(request, self.renderers) + def select_renderer(self, request): + return self.negotiator.select_renderer(request, self.renderers) def test_client_without_accept_use_renderer(self): request = factory.get('/') - accepted_renderer, accepted_media_type = self.negotiate(request) + accepted_renderer, accepted_media_type = self.select_renderer(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) + accepted_renderer, accepted_media_type = self.select_renderer(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) + accepted_renderer, accepted_media_type = self.select_renderer(request) self.assertEquals(accepted_media_type, 'application/json; indent=8') diff --git a/rest_framework/views.py b/rest_framework/views.py index 066c0bb92..357d89399 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -238,7 +238,13 @@ class APIView(View): """ renderers = self.get_renderers() conneg = self.get_content_negotiator() - return conneg.negotiate(request, renderers, self.format_kwarg, force) + + try: + return conneg.select_renderer(request, renderers, self.format_kwarg) + except: + if force: + return (renderers[0], renderers[0].media_type) + raise def has_permission(self, request, obj=None): """