diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index e1c83477b..ef0ed46f1 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -127,7 +127,7 @@ For example: # and additionally requiresa 'template_name'. # It does not require serialization. data = {'users': queryset} - return Response(data, template='list_users.html') + return Response(data, template_name='list_users.html') # JSONRenderer requires serialized data as normal. serializer = UserSerializer(instance=queryset) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3227a03a9..4157468fd 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -10,6 +10,7 @@ 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 @@ -142,19 +143,41 @@ class HTMLTemplateRenderer(BaseRenderer): media_type = 'text/html' format = 'html' - template = None + template_name = None def render(self, data=None, accepted_media_type=None): """ - Renders *obj* using the :attr:`template` specified on the class. - """ - if data 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': data}) + 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): """ diff --git a/rest_framework/response.py b/rest_framework/response.py index fca631c3a..796750fc4 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -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=None, + template_name=None, headers=None): """ Alters the init arguments slightly. For example, drop 'template_name', and instead use 'data'. @@ -20,21 +20,21 @@ class Response(SimpleTemplateResponse): super(Response, self).__init__(None, status=status) self.data = data self.headers = headers and headers[:] or [] - - self.accepted_renderer = renderer - self.accepted_media_type = accepted_media_type + self.template_name = template_name @property def rendered_content(self): renderer = self.accepted_renderer + media_type = self.accepted_media_type - assert renderer, "No renderer set on Response" + assert renderer, "No accepted renderer set on Response" + assert media_type, "No accepted media type set on Response" - self['content-type'] = self.accepted_media_type + self['content-type'] = media_type if self.data is None: return renderer.render() - return renderer.render(self.data, self.accepted_media_type) + return renderer.render(self.data, media_type) @property def status_text(self): diff --git a/rest_framework/tests/htmlrenderer.py b/rest_framework/tests/htmlrenderer.py new file mode 100644 index 000000000..2c672dd08 --- /dev/null +++ b/rest_framework/tests/htmlrenderer.py @@ -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')