mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 03:23:59 +03:00
emitters -> renderers
This commit is contained in:
parent
ad552107ff
commit
93aa065fa9
|
@ -233,8 +233,8 @@ class RequestMixin(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_parser(self):
|
def default_parser(self):
|
||||||
"""Return the view's most preffered emitter.
|
"""Return the view's most preffered renderer.
|
||||||
(This has no behavioural effect, but is may be used by documenting emitters)"""
|
(This has no behavioural effect, but is may be used by documenting renderers)"""
|
||||||
return self.parsers[0]
|
return self.parsers[0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ class RequestMixin(object):
|
||||||
########## ResponseMixin ##########
|
########## ResponseMixin ##########
|
||||||
|
|
||||||
class ResponseMixin(object):
|
class ResponseMixin(object):
|
||||||
"""Adds behaviour for pluggable Emitters to a :class:`.Resource` or Django :class:`View`. class.
|
"""Adds behaviour for pluggable Renderers to a :class:`.Resource` or Django :class:`View`. class.
|
||||||
|
|
||||||
Default behaviour is to use standard HTTP Accept header content negotiation.
|
Default behaviour is to use standard HTTP Accept header content negotiation.
|
||||||
Also supports overidding the content type by specifying an _accept= parameter in the URL.
|
Also supports overidding the content type by specifying an _accept= parameter in the URL.
|
||||||
|
@ -260,7 +260,7 @@ class ResponseMixin(object):
|
||||||
|
|
||||||
#request = None
|
#request = None
|
||||||
#response = None
|
#response = None
|
||||||
emitters = ()
|
renderers = ()
|
||||||
|
|
||||||
#def render_to_response(self, obj):
|
#def render_to_response(self, obj):
|
||||||
# if isinstance(obj, Response):
|
# if isinstance(obj, Response):
|
||||||
|
@ -285,21 +285,21 @@ class ResponseMixin(object):
|
||||||
# return content
|
# return content
|
||||||
|
|
||||||
|
|
||||||
def emit(self, response):
|
def render(self, response):
|
||||||
"""Takes a :class:`Response` object and returns a Django :class:`HttpResponse`."""
|
"""Takes a :class:`Response` object and returns a Django :class:`HttpResponse`."""
|
||||||
self.response = response
|
self.response = response
|
||||||
|
|
||||||
try:
|
try:
|
||||||
emitter = self._determine_emitter(self.request)
|
renderer = self._determine_renderer(self.request)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
emitter = self.default_emitter
|
renderer = self.default_renderer
|
||||||
response = exc.response
|
response = exc.response
|
||||||
|
|
||||||
# Serialize the response content
|
# Serialize the response content
|
||||||
if response.has_content_body:
|
if response.has_content_body:
|
||||||
content = emitter(self).emit(output=response.cleaned_content)
|
content = renderer(self).render(output=response.cleaned_content)
|
||||||
else:
|
else:
|
||||||
content = emitter(self).emit()
|
content = renderer(self).render()
|
||||||
|
|
||||||
# Munge DELETE Response code to allow us to return content
|
# Munge DELETE Response code to allow us to return content
|
||||||
# (Do this *after* we've rendered the template so that we include the normal deletion response code in the output)
|
# (Do this *after* we've rendered the template so that we include the normal deletion response code in the output)
|
||||||
|
@ -307,16 +307,16 @@ class ResponseMixin(object):
|
||||||
response.status = 200
|
response.status = 200
|
||||||
|
|
||||||
# Build the HTTP Response
|
# Build the HTTP Response
|
||||||
# TODO: Check if emitter.mimetype is underspecified, or if a content-type header has been set
|
# TODO: Check if renderer.mimetype is underspecified, or if a content-type header has been set
|
||||||
resp = HttpResponse(content, mimetype=emitter.media_type, status=response.status)
|
resp = HttpResponse(content, mimetype=renderer.media_type, status=response.status)
|
||||||
for (key, val) in response.headers.items():
|
for (key, val) in response.headers.items():
|
||||||
resp[key] = val
|
resp[key] = val
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def _determine_emitter(self, request):
|
def _determine_renderer(self, request):
|
||||||
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
|
"""Return the appropriate renderer for the output, given the client's 'Accept' header,
|
||||||
and the content types that this Resource knows how to serve.
|
and the content types that this Resource knows how to serve.
|
||||||
|
|
||||||
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"""
|
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"""
|
||||||
|
@ -333,7 +333,7 @@ class ResponseMixin(object):
|
||||||
accept_list = request.META["HTTP_ACCEPT"].split(',')
|
accept_list = request.META["HTTP_ACCEPT"].split(',')
|
||||||
else:
|
else:
|
||||||
# No accept header specified
|
# No accept header specified
|
||||||
return self.default_emitter
|
return self.default_renderer
|
||||||
|
|
||||||
# Parse the accept header into a dict of {qvalue: set of media types}
|
# Parse the accept header into a dict of {qvalue: set of media types}
|
||||||
# We ignore mietype parameters
|
# We ignore mietype parameters
|
||||||
|
@ -363,34 +363,34 @@ class ResponseMixin(object):
|
||||||
|
|
||||||
for accept_set in accept_sets:
|
for accept_set in accept_sets:
|
||||||
# Return any exact match
|
# Return any exact match
|
||||||
for emitter in self.emitters:
|
for renderer in self.renderers:
|
||||||
if emitter.media_type in accept_set:
|
if renderer.media_type in accept_set:
|
||||||
return emitter
|
return renderer
|
||||||
|
|
||||||
# Return any subtype match
|
# Return any subtype match
|
||||||
for emitter in self.emitters:
|
for renderer in self.renderers:
|
||||||
if emitter.media_type.split('/')[0] + '/*' in accept_set:
|
if renderer.media_type.split('/')[0] + '/*' in accept_set:
|
||||||
return emitter
|
return renderer
|
||||||
|
|
||||||
# Return default
|
# Return default
|
||||||
if '*/*' in accept_set:
|
if '*/*' in accept_set:
|
||||||
return self.default_emitter
|
return self.default_renderer
|
||||||
|
|
||||||
|
|
||||||
raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
|
raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
|
||||||
{'detail': 'Could not satisfy the client\'s Accept header',
|
{'detail': 'Could not satisfy the client\'s Accept header',
|
||||||
'available_types': self.emitted_media_types})
|
'available_types': self.renderted_media_types})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def emitted_media_types(self):
|
def renderted_media_types(self):
|
||||||
"""Return an list of all the media types that this resource can emit."""
|
"""Return an list of all the media types that this resource can render."""
|
||||||
return [emitter.media_type for emitter in self.emitters]
|
return [renderer.media_type for renderer in self.renderers]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_emitter(self):
|
def default_renderer(self):
|
||||||
"""Return the resource's most prefered emitter.
|
"""Return the resource's most prefered renderer.
|
||||||
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
|
(This renderer is used if the client does not send and Accept: header, or sends Accept: */*)"""
|
||||||
return self.emitters[0]
|
return self.renderers[0]
|
||||||
|
|
||||||
|
|
||||||
########## Auth Mixin ##########
|
########## Auth Mixin ##########
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ModelResource(Resource):
|
||||||
|
|
||||||
|
|
||||||
#def get_form(self, content=None):
|
#def get_form(self, content=None):
|
||||||
# """Return a form that may be used in validation and/or rendering an html emitter"""
|
# """Return a form that may be used in validation and/or rendering an html renderer"""
|
||||||
# if self.form:
|
# if self.form:
|
||||||
# return super(self.__class__, self).get_form(content)
|
# return super(self.__class__, self).get_form(content)
|
||||||
#
|
#
|
||||||
|
@ -121,8 +121,8 @@ class ModelResource(Resource):
|
||||||
elif inspect.isfunction(thing):
|
elif inspect.isfunction(thing):
|
||||||
if not inspect.getargspec(thing)[0]:
|
if not inspect.getargspec(thing)[0]:
|
||||||
ret = _any(thing())
|
ret = _any(thing())
|
||||||
elif hasattr(thing, '__emittable__'):
|
elif hasattr(thing, '__rendertable__'):
|
||||||
f = thing.__emittable__
|
f = thing.__rendertable__
|
||||||
if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1:
|
if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1:
|
||||||
ret = _any(f())
|
ret = _any(f())
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,13 +4,13 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
from djangorestframework.compat import View
|
from djangorestframework.compat import View
|
||||||
from djangorestframework.response import Response, ErrorResponse
|
from djangorestframework.response import Response, ErrorResponse
|
||||||
from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin
|
from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin
|
||||||
from djangorestframework import emitters, parsers, authenticators, permissions, validators, status
|
from djangorestframework import renderers, parsers, authenticators, permissions, validators, status
|
||||||
|
|
||||||
|
|
||||||
# TODO: Figure how out references and named urls need to work nicely
|
# TODO: Figure how out references and named urls need to work nicely
|
||||||
# TODO: POST on existing 404 URL, PUT on existing 404 URL
|
# TODO: POST on existing 404 URL, PUT on existing 404 URL
|
||||||
#
|
#
|
||||||
# NEXT: Exceptions on func() -> 500, tracebacks emitted if settings.DEBUG
|
# NEXT: Exceptions on func() -> 500, tracebacks renderted if settings.DEBUG
|
||||||
|
|
||||||
__all__ = ['Resource']
|
__all__ = ['Resource']
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
|
||||||
|
|
||||||
http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace', 'patch']
|
http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace', 'patch']
|
||||||
|
|
||||||
# List of emitters the resource can serialize the response with, ordered by preference.
|
# List of renderers the resource can serialize the response with, ordered by preference.
|
||||||
emitters = ( emitters.JSONEmitter,
|
renderers = ( renderers.JSONRenderer,
|
||||||
emitters.DocumentingHTMLEmitter,
|
renderers.DocumentingHTMLRenderer,
|
||||||
emitters.DocumentingXHTMLEmitter,
|
renderers.DocumentingXHTMLRenderer,
|
||||||
emitters.DocumentingPlainTextEmitter,
|
renderers.DocumentingPlainTextRenderer,
|
||||||
emitters.XMLEmitter )
|
renderers.XMLRenderer )
|
||||||
|
|
||||||
# List of parsers the resource can parse the request with.
|
# List of parsers the resource can parse the request with.
|
||||||
parsers = ( parsers.JSONParser,
|
parsers = ( parsers.JSONParser,
|
||||||
|
@ -48,7 +48,7 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
|
||||||
|
|
||||||
# Allow name and description for the Resource to be set explicitly,
|
# Allow name and description for the Resource to be set explicitly,
|
||||||
# overiding the default classname/docstring behaviour.
|
# overiding the default classname/docstring behaviour.
|
||||||
# These are used for documentation in the standard html and text emitters.
|
# These are used for documentation in the standard html and text renderers.
|
||||||
name = None
|
name = None
|
||||||
description = None
|
description = None
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
|
||||||
Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can.
|
Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can.
|
||||||
|
|
||||||
TODO: This is going to be removed. I think that the 'fields' behaviour is going to move into
|
TODO: This is going to be removed. I think that the 'fields' behaviour is going to move into
|
||||||
the EmitterMixin and Emitter classes."""
|
the RendererMixin and Renderer classes."""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
|
||||||
response.headers['Allow'] = ', '.join(self.allowed_methods)
|
response.headers['Allow'] = ', '.join(self.allowed_methods)
|
||||||
response.headers['Vary'] = 'Authenticate, Accept'
|
response.headers['Vary'] = 'Authenticate, Accept'
|
||||||
|
|
||||||
return self.emit(response)
|
return self.render(response)
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
@ -28,7 +28,7 @@ urlpatterns = patterns('',
|
||||||
|
|
||||||
|
|
||||||
class BreadcrumbTests(TestCase):
|
class BreadcrumbTests(TestCase):
|
||||||
"""Tests the breadcrumb functionality used by the HTML emitter."""
|
"""Tests the breadcrumb functionality used by the HTML renderer."""
|
||||||
|
|
||||||
urls = 'djangorestframework.tests.breadcrumbs'
|
urls = 'djangorestframework.tests.breadcrumbs'
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ def xml2dict(input):
|
||||||
|
|
||||||
|
|
||||||
# Piston:
|
# Piston:
|
||||||
class XMLEmitter():
|
class XMLRenderer():
|
||||||
def _to_xml(self, xml, data):
|
def _to_xml(self, xml, data):
|
||||||
if isinstance(data, (list, tuple)):
|
if isinstance(data, (list, tuple)):
|
||||||
for item in data:
|
for item in data:
|
||||||
|
@ -156,4 +156,4 @@ class XMLEmitter():
|
||||||
return stream.getvalue()
|
return stream.getvalue()
|
||||||
|
|
||||||
def dict2xml(input):
|
def dict2xml(input):
|
||||||
return XMLEmitter().dict2xml(input)
|
return XMLRenderer().dict2xml(input)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3
|
from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3
|
||||||
from djangorestframework.mixins import ResponseMixin
|
from djangorestframework.mixins import ResponseMixin
|
||||||
from djangorestframework.emitters import DEFAULT_EMITTERS
|
from djangorestframework.renderers import DEFAULT_RENDERERS
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
@ -9,8 +9,8 @@ from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
class ExampleView(ResponseMixin, View):
|
class ExampleView(ResponseMixin, View):
|
||||||
"""An example view using Django 1.3's class based views.
|
"""An example view using Django 1.3's class based views.
|
||||||
Uses djangorestframework's EmitterMixin to provide support for multiple output formats."""
|
Uses djangorestframework's RendererMixin to provide support for multiple output formats."""
|
||||||
emitters = DEFAULT_EMITTERS
|
renderers = DEFAULT_RENDERERS
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
response = Response(200, {'description': 'Some example content',
|
response = Response(200, {'description': 'Some example content',
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from djangorestframework.resource import Resource
|
from djangorestframework.resource import Resource
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework.emitters import BaseEmitter
|
from djangorestframework.renderers import BaseRenderer
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
|
||||||
from pygments.formatters import HtmlFormatter
|
from pygments.formatters import HtmlFormatter
|
||||||
|
@ -32,8 +32,8 @@ def remove_oldest_files(dir, max_files):
|
||||||
[os.remove(path) for path in list_dir_sorted_by_ctime(dir)[max_files:]]
|
[os.remove(path) for path in list_dir_sorted_by_ctime(dir)[max_files:]]
|
||||||
|
|
||||||
|
|
||||||
class HTMLEmitter(BaseEmitter):
|
class HTMLRenderer(BaseRenderer):
|
||||||
"""Basic emitter which just returns the content without any further serialization."""
|
"""Basic renderer which just returns the content without any further serialization."""
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ class PygmentsRoot(Resource):
|
||||||
|
|
||||||
class PygmentsInstance(Resource):
|
class PygmentsInstance(Resource):
|
||||||
"""Simply return the stored highlighted HTML file with the correct mime type.
|
"""Simply return the stored highlighted HTML file with the correct mime type.
|
||||||
This Resource only emits HTML and uses a standard HTML emitter rather than the emitters.DocumentingHTMLEmitter class."""
|
This Resource only emits HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class."""
|
||||||
emitters = (HTMLEmitter,)
|
renderers = (HTMLRenderer,)
|
||||||
|
|
||||||
def get(self, request, unique_id):
|
def get(self, request, unique_id):
|
||||||
"""Return the highlighted snippet."""
|
"""Return the highlighted snippet."""
|
||||||
|
|
|
@ -11,14 +11,14 @@ class Sandbox(Resource):
|
||||||
|
|
||||||
All the example APIs allow anonymous access, and can be navigated either through the browser or from the command line...
|
All the example APIs allow anonymous access, and can be navigated either through the browser or from the command line...
|
||||||
|
|
||||||
bash: curl -X GET http://api.django-rest-framework.org/ # (Use default emitter)
|
bash: curl -X GET http://api.django-rest-framework.org/ # (Use default renderer)
|
||||||
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation emitter)
|
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
|
||||||
|
|
||||||
The examples provided:
|
The examples provided:
|
||||||
|
|
||||||
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
|
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
|
||||||
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
|
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
|
||||||
3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [EmitterMixin](http://django-rest-framework.org/library/emitters.html).
|
3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html).
|
||||||
4. A generic object store API.
|
4. A generic object store API.
|
||||||
5. A code highlighting API.
|
5. A code highlighting API.
|
||||||
6. A blog posts and comments API.
|
6. A blog posts and comments API.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user