mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-11 04:07:39 +03:00
Merge pull request #282 from tomchristie/html-template-responses
Html template responses
This commit is contained in:
commit
2455bebd87
|
@ -18,24 +18,25 @@ If the generic views don't suit the needs of your API, you can drop down to usin
|
|||
Typically when using the generic views, you'll override the view, and set several class attributes.
|
||||
|
||||
class UserList(generics.ListCreateAPIView):
|
||||
serializer = UserSerializer
|
||||
model = User
|
||||
permissions = (IsAdminUser,)
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = (IsAdminUser,)
|
||||
paginate_by = 100
|
||||
|
||||
For more complex cases you might also want to override various methods on the view class. For example.
|
||||
|
||||
class UserList(generics.ListCreateAPIView):
|
||||
serializer = UserSerializer
|
||||
model = User
|
||||
permissions = (IsAdminUser,)
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
def get_paginate_by(self):
|
||||
"""
|
||||
Use smaller pagination for HTML representations.
|
||||
"""
|
||||
if self.request.accepted_media_type == 'text/html':
|
||||
return 10
|
||||
page_size_param = self.request.QUERY_PARAMS.get('page_size')
|
||||
if page_size_param:
|
||||
return int(page_size_param)
|
||||
return 100
|
||||
|
||||
For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something the following entry.
|
||||
|
@ -52,24 +53,32 @@ Used for read-only endpoints to represent a collection of model instances.
|
|||
|
||||
Provides a `get` method handler.
|
||||
|
||||
Extends: [MultipleObjectBaseAPIView], [ListModelMixin]
|
||||
|
||||
## ListCreateAPIView
|
||||
|
||||
Used for read-write endpoints to represent a collection of model instances.
|
||||
|
||||
Provides `get` and `post` method handlers.
|
||||
|
||||
Extends: [MultipleObjectBaseAPIView], [ListModelMixin], [CreateModelMixin]
|
||||
|
||||
## RetrieveAPIView
|
||||
|
||||
Used for read-only endpoints to represent a single model instance.
|
||||
|
||||
Provides a `get` method handler.
|
||||
|
||||
Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin]
|
||||
|
||||
## RetrieveUpdateDestroyAPIView
|
||||
|
||||
Used for read-write endpoints to represent a single model instance.
|
||||
|
||||
Provides `get`, `put` and `delete` method handlers.
|
||||
|
||||
Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin]
|
||||
|
||||
---
|
||||
|
||||
# Base views
|
||||
|
@ -123,3 +132,11 @@ Provides a `.destroy(request, *args, **kwargs)` method, that implements deletion
|
|||
[SingleObjectMixin]: https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-single-object/
|
||||
[multiple-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.list/MultipleObjectMixin/
|
||||
[single-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.detail/SingleObjectMixin/
|
||||
|
||||
[SingleObjectBaseAPIView]: #singleobjectbaseapiview
|
||||
[MultipleObjectBaseAPIView]: #multipleobjectbaseapiview
|
||||
[ListModelMixin]: #listmodelmixin
|
||||
[CreateModelMixin]: #createmodelmixin
|
||||
[RetrieveModelMixin]: #retrievemodelmixin
|
||||
[UpdateModelMixin]: #updatemodelmixin
|
||||
[DestroyModelMixin]: #destroymodelmixin
|
|
@ -86,11 +86,28 @@ If your API includes views that can serve both regular webpages and API response
|
|||
|
||||
## DocumentingHTMLRenderer
|
||||
|
||||
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`
|
||||
|
||||
**.format:** `'.api'`
|
||||
|
||||
## TemplateHTMLRenderer
|
||||
## HTMLTemplateRenderer
|
||||
|
||||
Renders data to HTML, using Django's standard template rendering.
|
||||
Unlike other renderers, the data passed to the `Response` does not need to be serialized. Also, unlike other renderers, you may want to include a `template_name` argument when creating the `Response`.
|
||||
|
||||
The HTMLTemplateRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context.
|
||||
|
||||
The template name is determined by (in order of preference):
|
||||
|
||||
1. An explicit `.template_name` attribute set on the response.
|
||||
2. An explicit `.template_name` attribute set on this class.
|
||||
3. The return result of calling `view.get_template_names()`.
|
||||
|
||||
You can use `HTMLTemplateRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
|
||||
|
||||
If you're building websites that use `HTMLTemplateRenderer` along with other renderer classes, you should consider listing `HTMLTemplateRenderer` 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`
|
||||
|
||||
|
@ -115,7 +132,6 @@ For example:
|
|||
|
||||
@api_view(('GET',))
|
||||
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
|
||||
@template_name('list_users.html')
|
||||
def list_users(request):
|
||||
"""
|
||||
A view that can return JSON or HTML representations
|
||||
|
@ -123,15 +139,16 @@ For example:
|
|||
"""
|
||||
queryset = Users.objects.filter(active=True)
|
||||
|
||||
if request.accepted_renderer.format == 'html':
|
||||
if request.accepted_media_type == 'text/html':
|
||||
# TemplateHTMLRenderer takes a context dict,
|
||||
# and does not require serialization.
|
||||
# and additionally requiresa 'template_name'.
|
||||
# It does not require serialization.
|
||||
data = {'users': queryset}
|
||||
else:
|
||||
# JSONRenderer requires serialized data as normal.
|
||||
serializer = UserSerializer(instance=queryset)
|
||||
data = serializer.data
|
||||
return Response(data, template_name='list_users.html')
|
||||
|
||||
# JSONRenderer requires serialized data as normal.
|
||||
serializer = UserSerializer(instance=queryset)
|
||||
data = serializer.data
|
||||
return Response(data)
|
||||
|
||||
## Designing your media types
|
||||
|
|
|
@ -14,7 +14,11 @@ There's no requirement for you to use the `Response` class, you can also return
|
|||
|
||||
Unless you want to heavily customize REST framework for some reason, you should always use an `APIView` class or `@api_view` function for views that return `Response` objects. Doing so ensures that the view can perform content negotiation and select the appropriate renderer for the response, before it is returned from the view.
|
||||
|
||||
## Response(data, status=None, headers=None)
|
||||
---
|
||||
|
||||
# Methods
|
||||
|
||||
## Response(data, status=None, template_name=None, headers=None)
|
||||
|
||||
Unlike regular `HttpResponse` objects, you do not instantiate `Response` objects with rendered content. Instead you pass in unrendered data, which may consist of any python primatives.
|
||||
|
||||
|
@ -22,16 +26,58 @@ The renderers used by the `Response` class cannot natively handle complex dataty
|
|||
|
||||
You can use REST framework's `Serializer` classes to perform this data serialization, or use your own custom serialization.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `data`: The serialized data for the response.
|
||||
* `status`: A status code for the response. Defaults to 200. See also [status codes][statuscodes].
|
||||
* `template_name`: A template name to use if `HTMLTemplateRenderer` is selected.
|
||||
* `headers`: A dictionary of HTTP headers to use in the response.
|
||||
|
||||
## .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 HTTPResponse 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
|
||||
|
||||
## .data
|
||||
|
||||
The unrendered content of a `Request` object can be accessed using the `.data` attribute.
|
||||
|
||||
## .status_code
|
||||
|
||||
The numeric status code of the HTTP response.
|
||||
|
||||
## .content
|
||||
|
||||
To access the rendered content of a `Response` object, you must first call `.render()`. You'll typically only need to do this in cases such as unit testing responses - when you return a `Response` from a view Django's response cycle will handle calling `.render()` for you.
|
||||
The rendered content of the response. `.render()` must have been called before `.content` can be accessed.
|
||||
|
||||
## .renderer
|
||||
## .template_name
|
||||
|
||||
When you return a `Response` instance, the `APIView` class or `@api_view` decorator will select the appropriate renderer, and set the `.renderer` attribute on the `Response`, before returning it from the view.
|
||||
The `template_name`, if supplied. Only required if `HTMLTemplateRenderer` or some other custom template renderer is the accepted renderer for the reponse.
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
|
||||
## .accepted_renderer
|
||||
|
||||
The renderer instance that will be used to render the response.
|
||||
|
||||
Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
|
||||
|
||||
## .accepted_media_type
|
||||
|
||||
The media type that was selected by the content negotiation stage.
|
||||
|
||||
Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
|
||||
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
|
||||
[statuscodes]: status-codes.md
|
|
@ -1,5 +1,5 @@
|
|||
<iframe src="http://ghbtns.com/github-btn.html?user=tomchristie&repo=django-rest-framework&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
|
||||
[![travis-build-image]][travis]
|
||||
[![Travis build image][travis-build-image]][travis]
|
||||
|
||||
# Django REST framework
|
||||
|
||||
|
@ -58,7 +58,7 @@ Note that the base URL can be whatever you want, but you must include `rest_fram
|
|||
|
||||
## Quickstart
|
||||
|
||||
**TODO**
|
||||
|
||||
|
||||
## Tutorial
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,12 +10,13 @@ from django import forms
|
|||
from django.template import RequestContext, loader
|
||||
from django.utils import simplejson as json
|
||||
from rest_framework.compat import yaml
|
||||
from rest_framework.exceptions import ConfigurationError
|
||||
from rest_framework.settings import api_settings
|
||||
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,39 +33,8 @@ 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.
|
||||
|
||||
The requested media type is also passed to this method,
|
||||
as it may contain parameters relevant to how the parser
|
||||
should render the output.
|
||||
EG: ``application/json; indent=4``
|
||||
|
||||
By default render simply returns the output as-is.
|
||||
Override this method to provide for other behavior.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
|
||||
return str(obj)
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
raise NotImplemented('Renderer class requires .render() to be implemented')
|
||||
|
||||
|
||||
class JSONRenderer(BaseRenderer):
|
||||
|
@ -76,16 +46,16 @@ class JSONRenderer(BaseRenderer):
|
|||
format = 'json'
|
||||
encoder_class = encoders.JSONEncoder
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Render `obj` into json.
|
||||
"""
|
||||
if obj is None:
|
||||
if data is None:
|
||||
return ''
|
||||
|
||||
# If the media type looks like 'application/json; indent=4', then
|
||||
# pretty print the result.
|
||||
indent = get_media_type_params(media_type).get('indent', None)
|
||||
indent = get_media_type_params(accepted_media_type).get('indent', None)
|
||||
sort_keys = False
|
||||
try:
|
||||
indent = max(min(int(indent), 8), 0)
|
||||
|
@ -93,7 +63,7 @@ class JSONRenderer(BaseRenderer):
|
|||
except (ValueError, TypeError):
|
||||
indent = None
|
||||
|
||||
return json.dumps(obj, cls=self.encoder_class,
|
||||
return json.dumps(data, cls=self.encoder_class,
|
||||
indent=indent, sort_keys=sort_keys)
|
||||
|
||||
|
||||
|
@ -115,7 +85,7 @@ class JSONPRenderer(JSONRenderer):
|
|||
params = self.view.request.GET
|
||||
return params.get(self.callback_parameter, self.default_callback)
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Renders into jsonp, wrapping the json output in a callback function.
|
||||
|
||||
|
@ -123,7 +93,7 @@ class JSONPRenderer(JSONRenderer):
|
|||
on the URL, for example: ?callback=exampleCallbackName
|
||||
"""
|
||||
callback = self.get_callback()
|
||||
json = super(JSONPRenderer, self).render(obj, media_type)
|
||||
json = super(JSONPRenderer, self).render(data, accepted_media_type)
|
||||
return "%s(%s);" % (callback, json)
|
||||
|
||||
|
||||
|
@ -135,13 +105,13 @@ class XMLRenderer(BaseRenderer):
|
|||
media_type = 'application/xml'
|
||||
format = 'xml'
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Renders *obj* into serialized XML.
|
||||
"""
|
||||
if obj is None:
|
||||
if data is None:
|
||||
return ''
|
||||
return dict2xml(obj)
|
||||
return dict2xml(data)
|
||||
|
||||
|
||||
class YAMLRenderer(BaseRenderer):
|
||||
|
@ -152,17 +122,17 @@ class YAMLRenderer(BaseRenderer):
|
|||
media_type = 'application/yaml'
|
||||
format = 'yaml'
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Renders *obj* into serialized YAML.
|
||||
"""
|
||||
if obj is None:
|
||||
if data is None:
|
||||
return ''
|
||||
|
||||
return yaml.safe_dump(obj)
|
||||
return yaml.safe_dump(data)
|
||||
|
||||
|
||||
class TemplateRenderer(BaseRenderer):
|
||||
class HTMLTemplateRenderer(BaseRenderer):
|
||||
"""
|
||||
A Base class provided for convenience.
|
||||
|
||||
|
@ -171,30 +141,53 @@ class TemplateRenderer(BaseRenderer):
|
|||
the :attr:`media_type` and :attr:`template` attributes.
|
||||
"""
|
||||
|
||||
media_type = None
|
||||
template = None
|
||||
media_type = 'text/html'
|
||||
format = 'html'
|
||||
template_name = None
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Renders *obj* using the :attr:`template` specified on the class.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
Renders data to HTML, using Django's standard template rendering.
|
||||
|
||||
template = loader.get_template(self.template)
|
||||
context = RequestContext(self.view.request, {'object': obj})
|
||||
The template name is determined by (in order of preference):
|
||||
|
||||
1. An explicit .template_name set on the response.
|
||||
2. An explicit .template_name set on this class.
|
||||
3. The return result of calling view.get_template_names().
|
||||
"""
|
||||
view = self.view
|
||||
request, response = view.request, view.response
|
||||
|
||||
template_names = self.get_template_names(response, view)
|
||||
template = self.resolve_template(template_names)
|
||||
context = self.resolve_context(data, request)
|
||||
return template.render(context)
|
||||
|
||||
def resolve_template(self, template_names):
|
||||
return loader.select_template(template_names)
|
||||
|
||||
def resolve_context(self, data, request):
|
||||
return RequestContext(request, data)
|
||||
|
||||
def get_template_names(self, response, view):
|
||||
if response.template_name:
|
||||
return [response.template_name]
|
||||
elif self.template_name:
|
||||
return [self.template_name]
|
||||
elif hasattr(view, 'get_template_names'):
|
||||
return view.get_template_names()
|
||||
raise ConfigurationError('Returned a template response with no template_name')
|
||||
|
||||
|
||||
class DocumentingHTMLRenderer(BaseRenderer):
|
||||
"""
|
||||
HTML renderer used to self-document the API.
|
||||
"""
|
||||
media_type = 'text/html'
|
||||
format = 'html'
|
||||
format = 'api'
|
||||
template = 'rest_framework/api.html'
|
||||
|
||||
def get_content(self, view, request, obj, media_type):
|
||||
def get_content(self, view, request, data, accepted_media_type):
|
||||
"""
|
||||
Get the content as if it had been rendered by a non-documenting renderer.
|
||||
|
||||
|
@ -208,8 +201,8 @@ class DocumentingHTMLRenderer(BaseRenderer):
|
|||
if not renderers:
|
||||
return '[No renderers were found]'
|
||||
|
||||
media_type = add_media_type_param(media_type, 'indent', '4')
|
||||
content = renderers[0](view).render(obj, media_type)
|
||||
accepted_media_type = add_media_type_param(accepted_media_type, 'indent', '4')
|
||||
content = renderers[0](view).render(data, accepted_media_type)
|
||||
if not all(char in string.printable for char in content):
|
||||
return '[%d bytes of binary content]'
|
||||
|
||||
|
@ -333,7 +326,7 @@ class DocumentingHTMLRenderer(BaseRenderer):
|
|||
except AttributeError:
|
||||
return self.view.__doc__
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
def render(self, data=None, accepted_media_type=None):
|
||||
"""
|
||||
Renders *obj* using the :attr:`template` set on the class.
|
||||
|
||||
|
@ -344,7 +337,7 @@ class DocumentingHTMLRenderer(BaseRenderer):
|
|||
request = view.request
|
||||
response = view.response
|
||||
|
||||
content = self.get_content(view, request, obj, media_type)
|
||||
content = self.get_content(view, request, data, accepted_media_type)
|
||||
|
||||
put_form = self.get_form(view, 'PUT', request)
|
||||
post_form = self.get_form(view, 'POST', request)
|
||||
|
|
|
@ -8,8 +8,8 @@ class Response(SimpleTemplateResponse):
|
|||
arbitrary media types.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, status=None, headers=None,
|
||||
renderer=None, accepted_media_type=None):
|
||||
def __init__(self, data=None, status=200,
|
||||
template_name=None, headers=None):
|
||||
"""
|
||||
Alters the init arguments slightly.
|
||||
For example, drop 'template_name', and instead use 'data'.
|
||||
|
@ -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_media_type = accepted_media_type
|
||||
self.template_name = template_name
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
assert self.renderer, "No renderer set on Response"
|
||||
renderer = self.accepted_renderer
|
||||
media_type = self.accepted_media_type
|
||||
|
||||
self['Content-Type'] = self.renderer.media_type
|
||||
assert renderer, "No accepted renderer set on Response"
|
||||
assert media_type, "No accepted media type set on Response"
|
||||
|
||||
self['Content-Type'] = 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, media_type)
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
50
rest_framework/tests/htmlrenderer.py
Normal file
50
rest_framework/tests/htmlrenderer.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from django.template import TemplateDoesNotExist, Template
|
||||
import django.template.loader
|
||||
from rest_framework.decorators import api_view, renderer_classes
|
||||
from rest_framework.renderers import HTMLTemplateRenderer
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
@api_view(('GET',))
|
||||
@renderer_classes((HTMLTemplateRenderer,))
|
||||
def example(request):
|
||||
"""
|
||||
A view that can returns an HTML representation.
|
||||
"""
|
||||
data = {'object': 'foobar'}
|
||||
return Response(data, template_name='example.html')
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', example),
|
||||
)
|
||||
|
||||
|
||||
class HTMLRendererTests(TestCase):
|
||||
urls = 'rest_framework.tests.htmlrenderer'
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Monkeypatch get_template
|
||||
"""
|
||||
self.get_template = django.template.loader.get_template
|
||||
|
||||
def get_template(template_name):
|
||||
if template_name == 'example.html':
|
||||
return Template("example: {{ object }}")
|
||||
raise TemplateDoesNotExist(template_name)
|
||||
|
||||
django.template.loader.get_template = get_template
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Revert monkeypatching
|
||||
"""
|
||||
django.template.loader.get_template = self.get_template
|
||||
|
||||
def test_simple_html_view(self):
|
||||
response = self.client.get('/')
|
||||
self.assertContains(response, "example: foobar")
|
||||
self.assertEquals(response['Content-Type'], 'text/html')
|
37
rest_framework/tests/negotiation.py
Normal file
37
rest_framework/tests/negotiation.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from rest_framework.negotiation import DefaultContentNegotiation
|
||||
|
||||
factory = RequestFactory()
|
||||
|
||||
|
||||
class MockJSONRenderer(object):
|
||||
media_type = 'application/json'
|
||||
|
||||
|
||||
class MockHTMLRenderer(object):
|
||||
media_type = 'text/html'
|
||||
|
||||
|
||||
class TestAcceptedMediaType(TestCase):
|
||||
def setUp(self):
|
||||
self.renderers = [MockJSONRenderer(), MockHTMLRenderer()]
|
||||
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')
|
|
@ -199,20 +199,26 @@ class APIView(View):
|
|||
Runs anything that needs to occur prior to calling the method handlers.
|
||||
"""
|
||||
self.format = self.get_format_suffix(**kwargs)
|
||||
|
||||
if not self.has_permission(request):
|
||||
self.permission_denied(request)
|
||||
self.check_throttles(request)
|
||||
self.renderer, self.accepted_media_type = self.perform_content_negotiation(request)
|
||||
|
||||
# Perform content negotiation and store the accepted info on the request
|
||||
neg = self.perform_content_negotiation(request)
|
||||
request.accepted_renderer, request.accepted_media_type = neg
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
"""
|
||||
Returns the final response object.
|
||||
"""
|
||||
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_media_type = self.accepted_media_type
|
||||
if not getattr(request, 'accepted_renderer', None):
|
||||
neg = self.perform_content_negotiation(request, force=True)
|
||||
request.accepted_renderer, request.accepted_media_type = neg
|
||||
|
||||
response.accepted_renderer = request.accepted_renderer
|
||||
response.accepted_media_type = request.accepted_media_type
|
||||
|
||||
for key, value in self.headers.items():
|
||||
response[key] = value
|
||||
|
|
Loading…
Reference in New Issue
Block a user