diff options
| -rw-r--r-- | docs/api-guide/parsers.md | 129 | ||||
| -rw-r--r-- | docs/api-guide/renderers.md | 57 | ||||
| -rw-r--r-- | docs/api-guide/requests.md | 11 | ||||
| -rw-r--r-- | docs/api-guide/responses.md | 34 | ||||
| -rw-r--r-- | docs/topics/browsable-api.md | 7 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 17 | ||||
| -rw-r--r-- | rest_framework/tests/request.py | 15 |
7 files changed, 216 insertions, 54 deletions
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index b9bb4900..4b769672 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -8,17 +8,144 @@ sending more complex data than simple forms > > — Malcom Tredinnick, [Django developers group][cite] +REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types. There is also support for defining your own custom parsers, which gives you the flexiblity to design the media types that your API accepts. + +## How the parser is determined + +The set of valid parsers for a view is always defined as a list of classes. When either `request.DATA` or `request.FILES` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. + +## Setting the parsers + +The default set of parsers may be set globally, using the `DEFAULT_PARSERS` setting. For example, the following settings would allow requests with `YAML` content. + + REST_FRAMEWORK = { + 'DEFAULT_PARSERS': ( + 'rest_framework.parsers.YAMLParser', + ) + } + +You can also set the renderers used for an individual view, using the `APIView` class based views. + + class ExampleView(APIView): + """ + A view that can accept POST requests with YAML content. + """ + parser_classes = (YAMLParser,) + + def post(self, request, format=None): + return Response({'received data': request.DATA}) + +Or, if you're using the `@api_view` decorator with function based views. + + @api_view(('POST',)), + @parser_classes((YAMLParser,)) + def example_view(request, format=None): + """ + A view that can accept POST requests with YAML content. + """ + return Response({'received data': request.DATA}) + +--- + +# API Reference ## JSONParser +Parses `JSON` request content. + +**.media_type**: `application/json` + ## YAMLParser +Parses `YAML` request content. + +**.media_type**: `application/yaml` + ## XMLParser +Parses REST framework's default style of `XML` request content. + +Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, `SOAP`, and `XHTML`. + +If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. + +**.media_type**: `application/xml` + ## FormParser +Parses HTML form content. `request.DATA` will be populated with a `QueryDict` of data, `request.FILES` will be populated with an empty `QueryDict` of data. + +You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. + +**.media_type**: `application/x-www-form-urlencoded` + ## MultiPartParser -## Custom parsers +Parses multipart HTML form content, which supports file uploads. Both `request.DATA` and `request.FILES` will be populated with a `QueryDict`. + +You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. + +**.media_type**: `multipart/form-data` + +--- + +# Custom parsers + +To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse_stream(self, stream, parser_context)` method. + +The method should return the data that will be used to populate the `request.DATA` property. + +For example: + + class PlainTextParser(BaseParser): + """ + Plain text parser. + """ + + media_type = 'text/plain' + + def parse_stream(self, stream, parser_context=None): + """ + Simply return a string representing the body of the request. + """ + return stream.read() + +The arguments passed to `.parse_stream()` are: + +### stream + +A stream-like object representing the body of the request. + +### parser_context + +If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. By default it includes the keys `'upload_handlers'` and `'meta'`, which contain the values of the `request.upload_handlers` and `request.meta` properties. + +## Uploading file content + +If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse_stream()` method. `DataAndFiles` should be instantiated with two arguments. The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property. + +For example: + + class SimpleFileUploadParser(BaseParser): + """ + A naive raw file upload parser. + """ + + def parse_stream(self, stream, parser_context): + content = stream.read() + name = 'example.dat' + content_type = 'application/octet-stream' + size = len(content) + charset = 'utf-8' + + # Write a temporary file based on the request content + temp = tempfile.NamedTemporaryFile(delete=False) + temp.write(content) + uploaded = UploadedFile(temp, name, content_type, size, charset) + + # Return the uploaded file + data = {} + files = {name: uploaded} + return DataAndFiles(data, files) [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index b2ebd0c7..024a4ee2 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -42,8 +42,8 @@ You can also set the renderers used for an individual view, using the `APIView` Or, if you're using the `@api_view` decorator with function based views. - @api_view('GET'), - @renderer_classes(JSONRenderer, JSONPRenderer) + @api_view(('GET',)), + @renderer_classes((JSONRenderer, JSONPRenderer)) def user_count_view(request, format=None): """ A view that returns the count of active users, in JSON or JSONp. @@ -66,27 +66,45 @@ If your API includes views that can serve both regular webpages and API response ## JSONRenderer -**.media_type:** `application/json` +Renders the request data into `JSON`. -**.format:** `'.json'` +The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`. + +**.media_type**: `application/json` + +**.format**: `'.json'` ## JSONPRenderer -**.media_type:** `application/javascript` +Renders the request data into `JSONP`. The `JSONP` media type provides a mechanism of allowing cross-domain AJAX requests, by wrapping a `JSON` response in a javascript callback. + +The javascript callback function must be set by the client including a `callback` URL query parameter. For example `http://example.com/api/users?callback=jsonpCallback`. If the callback function is not explicitly set by the client it will default to `'callback'`. -**.format:** `'.jsonp'` +**Note**: If you require cross-domain AJAX requests, you may also want to consider using [CORS] as an alternative to `JSONP`. + +**.media_type**: `application/javascript` + +**.format**: `'.jsonp'` ## YAMLRenderer -**.media_type:** `application/yaml` +Renders the request data into `YAML`. + +**.media_type**: `application/yaml` -**.format:** `'.yaml'` +**.format**: `'.yaml'` ## XMLRenderer -**.media_type:** `application/xml` +Renders REST framework's default style of `XML` response content. -**.format:** `'.xml'` +Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, `SOAP`, and `XHTML`. + +If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. + +**.media_type**: `application/xml` + +**.format**: `'.xml'` ## HTMLRenderer @@ -118,19 +136,21 @@ You can use `HTMLRenderer` either to return regular HTML pages using REST framew If you're building websites that use `HTMLRenderer` along with other renderer classes, you should consider listing `HTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers. -**.media_type:** `text/html` +**.media_type**: `text/html` -**.format:** `'.html'` +**.format**: `'.html'` ## BrowsableAPIRenderer Renders data into HTML for the Browseable API. This renderer will determine which other renderer would have been given highest priority, and use that to display an API style response within the HTML page. -**.media_type:** `text/html` +**.media_type**: `text/html` -**.format:** `'.api'` +**.format**: `'.api'` + +--- -## Custom renderers +# Custom renderers To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type=None, renderer_context=None)` method. @@ -151,15 +171,15 @@ For example: The arguments passed to the `.render()` method are: -#### `data` +### `data` The request data, as set by the `Response()` instantiation. -#### `media_type=None` +### `media_type=None` Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`. -#### `renderer_context=None` +### `renderer_context=None` Optional. If provided, this is a dictionary of contextual information provided by the view. By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`. @@ -213,6 +233,7 @@ For good examples of custom media types, see GitHub's use of a custom [applicati [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process [conneg]: content-negotiation.md [browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers +[CORS]: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing [HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas [quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven [application/vnd.github+json]: http://developer.github.com/v3/media/ diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 9711d989..439c97bc 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -45,6 +45,8 @@ You won't typically need to access this property. **Note:** If a client sends malformed content, then accessing `request.DATA` or `request.FILES` may raise a `ParseError`. By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response. +If a client sends a request with a content-type that cannot be parsed then a `UnsupportedMediaType` exception will be raised, which by default will be caught and return a `415 Unsupported Media Type` response. + --- # Authentication @@ -111,6 +113,15 @@ If you do need to access the raw content directly, you should use the `.stream` For more information see the [browser enhancements documentation]. +--- + +# Standard HttpRequest attributes + +As REST framework's `Request` extends Django's `HttpRequest`, all the other standard attributes and methods are also available. For example the `request.META` dictionary is available as normal. + +Note that due to implementation reasons the `Request` class does not inherit from `HttpRequest` class, but instead extends the class using composition. + + [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion [parsers documentation]: parsers.md [authentication documentation]: authentication.md diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index b0de6824..395decda 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -16,7 +16,7 @@ Unless you want to heavily customize REST framework for some reason, you should --- -# Methods +# Creating responses ## Response() @@ -35,21 +35,6 @@ Arguments: * `template_name`: A template name to use if `HTMLRenderer` is selected. * `headers`: A dictionary of HTTP headers to use in the response. -## .render() - -**Signature:** `.render()` - -This methd is called to render the serialized data of the response into the final response content. When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type)` method on the accepted renderer instance. - -You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle. - -## Standard response methods - -The `Response` class extends `SimpleTemplateResponse`, and all the usual methods are also available on the response. For example you can set headers on the response in the standard way: - - response = Response() - response['Cache-Control'] = 'no-cache' - --- # Attributes @@ -88,5 +73,22 @@ A dictionary of additional context information that will be passed to the render Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view. +--- + +# Standard HttpResponse attributes + +The `Response` class extends `SimpleTemplateResponse`, and all the usual attributes and methods are also available on the response. For example you can set headers on the response in the standard way: + + response = Response() + response['Cache-Control'] = 'no-cache' + +## .render() + +**Signature:** `.render()` + +As with any other `TemplateResponse`, this methd is called to render the serialized data of the response into the final response content. When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type, renderer_context)` method on the `accepted_renderer` instance. + +You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle. + [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/ [statuscodes]: status-codes.md diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index df0eb353..9fe82e69 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -1,5 +1,10 @@ # The Browsable API +> It is a profoundly erroneous truism... that we should cultivate the habit of thinking of what we are doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform without thinking about them. +> +> — [Alfred North Whitehead][cite], An Introduction to Mathematics (1911) + + API may stand for Application *Programming* Interface, but humans have to be able to read the APIs, too; someone has to do the programming. Django REST Framework supports generating human-friendly HTML output for each resource when the `HTML` format is requested. These pages allow for easy browsing of resources, as well as forms for submitting data to the resources using `POST`, `PUT`, and `DELETE`. ## URLs @@ -85,7 +90,7 @@ The context that's available to the template: For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`. Then the page content and capabilities are entirely up to you. - +[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead [drfreverse]: ../api-guide/reverse.md [ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/ [chromejsonview]: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 5325a64b..672f6a16 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -98,23 +98,6 @@ class YAMLParser(BaseParser): raise ParseError('YAML parse error - %s' % unicode(exc)) -class PlainTextParser(BaseParser): - """ - Plain text parser. - """ - - media_type = 'text/plain' - - def parse_stream(self, stream, **opts): - """ - Returns a 2-tuple of `(data, files)`. - - `data` will simply be a string representing the body of the request. - `files` will always be `None`. - """ - return stream.read() - - class FormParser(BaseParser): """ Parser for form data. diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index 7b24b036..f00ee85f 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -10,9 +10,9 @@ from rest_framework import status from rest_framework.authentication import SessionAuthentication from django.test.client import RequestFactory from rest_framework.parsers import ( + BaseParser, FormParser, MultiPartParser, - PlainTextParser, JSONParser ) from rest_framework.request import Request @@ -24,6 +24,19 @@ from rest_framework.views import APIView factory = RequestFactory() +class PlainTextParser(BaseParser): + media_type = 'text/plain' + + def parse_stream(self, stream, **opts): + """ + Returns a 2-tuple of `(data, files)`. + + `data` will simply be a string representing the body of the request. + `files` will always be `None`. + """ + return stream.read() + + class TestMethodOverloading(TestCase): def test_method(self): """ |
