From fb56f215ae50da0aebe99e05036ece259fd3e6f1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Oct 2012 22:39:07 +0100 Subject: [PATCH] Added `media_type` to `.parse()` - Consistency with renderer API. --- docs/api-guide/parsers.md | 10 ++++++++-- rest_framework/parsers.py | 28 +++++++++++----------------- rest_framework/renderers.py | 13 +++++++------ rest_framework/request.py | 12 ++++++++---- rest_framework/tests/request.py | 2 +- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 70abad9b6..18a5872cf 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -101,6 +101,12 @@ The arguments passed to `.parse()` are: A stream-like object representing the body of the request. +### media_type + +Optional. If provided, this is the media type of the incoming request. + +Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"text/plain; charset=utf-8"`. + ### parser_context Optional. If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. @@ -118,7 +124,7 @@ The following is an example plaintext parser that will populate the `request.DAT media_type = 'text/plain' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Simply return a string representing the body of the request. """ @@ -135,7 +141,7 @@ For example: A naive raw file upload parser. """ - def parse(self, stream, parser_context): + def parse(self, stream, media_type=None, parser_context=None): content = stream.read() name = 'example.dat' content_type = 'application/octet-stream' diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 7e13c3d82..4841676c9 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -1,14 +1,8 @@ """ -Django supports parsing the content of an HTTP request, but only for form POST requests. -That behavior is sufficient for dealing with standard HTML forms, but it doesn't map well -to general HTTP requests. +Parsers are used to parse the content of incoming HTTP requests. -We need a method to be able to: - -1.) Determine the parsed content on a request for methods other than POST (eg typically also PUT) - -2.) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded - and multipart/form-data. (eg also handle multipart/json) +They give us a generic way of being able to handle various media types +on the request, such as form content or json encoded data. """ from django.http import QueryDict @@ -37,10 +31,10 @@ class BaseParser(object): media_type = None - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ - Given a stream to read from, return the deserialized output. - Should return parsed data, or a DataAndFiles object consisting of the + Given a stream to read from, return the parsed representation. + Should return parsed data, or a `DataAndFiles` object consisting of the parsed data and files. """ raise NotImplementedError(".parse() must be overridden.") @@ -53,7 +47,7 @@ class JSONParser(BaseParser): media_type = 'application/json' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Returns a 2-tuple of `(data, files)`. @@ -73,7 +67,7 @@ class YAMLParser(BaseParser): media_type = 'application/yaml' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Returns a 2-tuple of `(data, files)`. @@ -93,7 +87,7 @@ class FormParser(BaseParser): media_type = 'application/x-www-form-urlencoded' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Returns a 2-tuple of `(data, files)`. @@ -111,7 +105,7 @@ class MultiPartParser(BaseParser): media_type = 'multipart/form-data' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Returns a DataAndFiles object. @@ -138,7 +132,7 @@ class XMLParser(BaseParser): media_type = 'application/xml' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): try: tree = ET.parse(stream) except (ExpatError, ETParseError, ValueError), exc: diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 94d253c94..23fd961b7 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -1,9 +1,10 @@ """ -Renderers are used to serialize a View's output into specific media types. +Renderers are used to serialize a response into specific media types. -Django REST framework also provides HTML and PlainText renderers that help self-document the API, -by serializing the output along with documentation regarding the View, output status and headers, -and providing forms and links depending on the allowed methods, renderers and parsers on the View. +They give us a generic way of being able to handle various media types +on the response, such as JSON encoded data or HTML output. + +REST framework also provides an HTML renderer the renders the browseable API. """ import string from django import forms @@ -23,8 +24,8 @@ from rest_framework import serializers, parsers class BaseRenderer(object): """ - All renderers must extend this class, set the :attr:`media_type` attribute, - and override the :meth:`render` method. + All renderers should extend this class, setting the `media_type` + and `format` attributes, and override the `.render()` method. """ media_type = None diff --git a/rest_framework/request.py b/rest_framework/request.py index d739d27db..b9d55de40 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -260,15 +260,19 @@ class Request(object): May raise an `UnsupportedMediaType`, or `ParseError` exception. """ - if self.stream is None or self.content_type is None: + stream = self.stream + media_type = self.content_type + + if stream is None or media_type is None: return (None, None) - parser = self.negotiator.select_parser(self.parsers, self.content_type) + parser = self.negotiator.select_parser(self.parsers, media_type) if not parser: - raise exceptions.UnsupportedMediaType(self.content_type) + raise exceptions.UnsupportedMediaType(media_type) + + parsed = parser.parse(stream, media_type, self.parser_context) - parsed = parser.parse(self.stream, self.parser_context) # Parser classes may return the raw data, or a # DataAndFiles object. Unpack the result as required. try: diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index f698e845a..ff48f3fa3 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -27,7 +27,7 @@ factory = RequestFactory() class PlainTextParser(BaseParser): media_type = 'text/plain' - def parse(self, stream, parser_context=None): + def parse(self, stream, media_type=None, parser_context=None): """ Returns a 2-tuple of `(data, files)`.