1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Accounted for multiple template engines in template responses.

This commit is contained in:
Aymeric Augustin 2015-01-09 22:59:00 +01:00
parent a3e783fe11
commit 79deb6a071
11 changed files with 215 additions and 111 deletions

View File

@ -3,7 +3,7 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django import forms
from django.http import HttpResponseRedirect, HttpResponse
from django.template import RequestContext, Template
from django.template import engines
from django.template.response import TemplateResponse
from django.views.decorators.cache import never_cache
from django.contrib.messages.views import SuccessMessageMixin
@ -48,13 +48,14 @@ def add_template_response(request, message_type):
@never_cache
def show(request):
t = Template(TEMPLATE)
return HttpResponse(t.render(RequestContext(request)))
template = engines['django'].from_string(TEMPLATE)
return HttpResponse(template.render(request=request))
@never_cache
def show_template_response(request):
return TemplateResponse(request, Template(TEMPLATE))
template = engines['django'].from_string(TEMPLATE)
return TemplateResponse(request, template)
class ContactForm(forms.Form):

View File

@ -1,7 +1,8 @@
import warnings
from django.http import HttpResponse
from django.template import loader, Context, RequestContext
from django.template import loader, Context, RequestContext, Template
from django.template.backends.django import Template as BackendTemplate
from django.template.context import _current_app_undefined
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
@ -16,14 +17,30 @@ class SimpleTemplateResponse(HttpResponse):
def __init__(self, template, context=None, content_type=None, status=None,
charset=None):
if isinstance(template, Template):
warnings.warn(
"{}'s template argument cannot be a django.template.Template "
"anymore. It may be a backend-specific template like those "
"created by get_template().".format(self.__class__.__name__),
RemovedInDjango20Warning, stacklevel=2)
template = BackendTemplate(template)
# It would seem obvious to call these next two members 'template' and
# 'context', but those names are reserved as part of the test Client
# API. To avoid the name collision, we use tricky-to-debug problems
# API. To avoid the name collision, we use different names.
self.template_name = template
self.context_data = context
self._post_render_callbacks = []
# _request stores the current request object in subclasses that know
# about requests, like TemplateResponse. It's defined in the base class
# to minimize code duplication.
# It's called self._request because self.request gets overwritten by
# django.test.client.Client. Unlike template_name and context_data,
# _request should not be considered part of the public API.
self._request = None
# content argument doesn't make sense here because it will be replaced
# with rendered template so we always pass empty string in order to
# prevent errors and provide shorter signature.
@ -62,14 +79,45 @@ class SimpleTemplateResponse(HttpResponse):
else:
return template
def _resolve_template(self, template):
# This wrapper deprecates returning a django.template.Template in
# subclasses that override resolve_template. It can be removed in
# Django 2.0.
new_template = self.resolve_template(template)
if isinstance(new_template, Template):
warnings.warn(
"{}.resolve_template() must return a backend-specific "
"template like those created by get_template(), not a "
"{}.".format(
self.__class__.__name__, new_template.__class__.__name__),
RemovedInDjango20Warning, stacklevel=2)
new_template = BackendTemplate(new_template)
return new_template
def resolve_context(self, context):
"""Converts context data into a full Context object
(assuming it isn't already a Context object).
"""
if isinstance(context, Context):
return context
else:
return Context(context)
return context
def _resolve_context(self, context):
# This wrapper deprecates returning a Context or a RequestContext in
# subclasses that override resolve_context. It can be removed in
# Django 2.0. If returning a Context or a RequestContext works by
# accident, it won't be an issue per se, but it won't be officially
# supported either.
new_context = self.resolve_context(context)
if isinstance(new_context, RequestContext) and self._request is None:
self._request = new_context.request
if isinstance(new_context, Context):
warnings.warn(
"{}.resolve_context() must return a dict, not a {}.".format(
self.__class__.__name__, new_context.__class__.__name__),
RemovedInDjango20Warning, stacklevel=2)
# It would be tempting to do new_context = new_context.flatten()
# here but that would cause template context processors to run for
# TemplateResponse(request, template, Context({})), which would be
# backwards-incompatible. As a consequence another deprecation
# warning will be raised when rendering the template. There isn't
# much we can do about that.
return new_context
@property
def rendered_content(self):
@ -80,14 +128,9 @@ class SimpleTemplateResponse(HttpResponse):
response content, you must either call render(), or set the
content explicitly using the value of this property.
"""
template = self.resolve_template(self.template_name)
context = self.resolve_context(self.context_data)
# TODO - remove this hack - makes the tests pass until the next commit
try:
template = template.template
except AttributeError:
pass
content = template.render(context)
template = self._resolve_template(self.template_name)
context = self._resolve_context(self.context_data)
content = template.render(context, self._request)
return content
def add_post_render_callback(self, callback):
@ -147,10 +190,6 @@ class TemplateResponse(SimpleTemplateResponse):
def __init__(self, request, template, context=None, content_type=None,
status=None, current_app=_current_app_undefined, charset=None):
# self.request gets over-written by django.test.client.Client - and
# unlike context_data and template_name the _request should not
# be considered part of the public API.
self._request = request
# As a convenience we'll allow callers to provide current_app without
# having to avoid needing to create the RequestContext directly
if current_app is not _current_app_undefined:
@ -161,14 +200,4 @@ class TemplateResponse(SimpleTemplateResponse):
request.current_app = current_app
super(TemplateResponse, self).__init__(
template, context, content_type, status, charset)
def resolve_context(self, context):
"""Convert context data into a full RequestContext object
(assuming it isn't already a Context object).
"""
if isinstance(context, Context):
return context
context_instance = RequestContext(self._request)
if context:
context_instance.push(context)
return context_instance
self._request = request

View File

@ -114,6 +114,11 @@ details on these changes.
:class:`~django.template.Context` in their
:meth:`~django.template.backends.base.Template.render()` method anymore.
* :doc:`Template response APIs </ref/template-response>` will enforce the use
of :class:`dict` and backend-dependent template objects instead of
:class:`~django.template.Context` and :class:`~django.template.Template`
respectively.
* The ``current_app`` parameter for the following function and classes will be
removed:

View File

@ -31,19 +31,28 @@ Attributes
.. attribute:: SimpleTemplateResponse.template_name
The name of the template to be rendered. Accepts a
:class:`~django.template.Template` object, a path to a template or list
of template paths.
The name of the template to be rendered. Accepts a backend-dependent
template object (such as those returned by
:func:`~django.template.loader.get_template()`), the name of a template,
or a list of template names.
Example: ``['foo.html', 'path/to/bar.html']``
.. deprecated:: 1.8
``template_name`` used to accept a :class:`~django.template.Template`.
.. attribute:: SimpleTemplateResponse.context_data
The context data to be used when rendering the template. It can be
a dictionary or a context object.
The context data to be used when rendering the template. It must be a
:class:`dict`.
Example: ``{'foo': 123}``
.. deprecated:: 1.8
``context_data`` used to accept a :class:`~django.template.Context`.
.. attribute:: SimpleTemplateResponse.rendered_content
The current rendered value of the response content, using the current
@ -58,21 +67,26 @@ Methods
.. method:: SimpleTemplateResponse.__init__(template, context=None, content_type=None, status=None, charset=None)
Instantiates a
:class:`~django.template.response.SimpleTemplateResponse` object
with the given template, context, content type, and HTTP status.
Instantiates a :class:`~django.template.response.SimpleTemplateResponse`
object with the given template, context, content type, HTTP status, and
charset.
``template``
The full name of a template, or a sequence of template names.
:class:`~django.template.Template` instances can also be used.
A backend-dependent template object (such as those returned by
:func:`~django.template.loader.get_template()`), the name of a template,
or a list of template names.
.. deprecated:: 1.8
``template`` used to accept a :class:`~django.template.Template`.
``context``
A dictionary of values to add to the template context. By default,
this is an empty dictionary. :class:`~django.template.Context` objects
are also accepted as ``context`` values.
A :class:`dict` of values to add to the template context. By default,
this is an empty dictionary.
``status``
The HTTP Status code for the response.
.. deprecated:: 1.8
``context`` used to accept a :class:`~django.template.Context`.
``content_type``
The value included in the HTTP ``Content-Type`` header, including the
@ -80,6 +94,9 @@ Methods
``content_type`` is specified, then its value is used. Otherwise,
:setting:`DEFAULT_CONTENT_TYPE` is used.
``status``
The HTTP status code for the response.
``charset``
The charset in which the response will be encoded. If not given it will
be extracted from ``content_type``, and if that is unsuccessful, the
@ -91,22 +108,42 @@ Methods
.. method:: SimpleTemplateResponse.resolve_context(context)
Converts context data into a context instance that can be used for
rendering a template. Accepts a dictionary of context data or a
context object. Returns a :class:`~django.template.Context`
instance containing the provided data.
Preprocesses context data that will be used for rendering a template.
Accepts a :class:`dict` of context data. By default, returns the same
:class:`dict`.
Override this method in order to customize context instantiation.
Override this method in order to customize the context.
.. versionchanged:: 1.8
``resolve_context`` returns a :class:`dict`. It used to return a
:class:`~django.template.Context`.
.. deprecated:: 1.8
``resolve_context`` no longer accepts a
:class:`~django.template.Context`.
.. method:: SimpleTemplateResponse.resolve_template(template)
Resolves the template instance to use for rendering. Accepts a
path of a template to use, or a sequence of template paths.
:class:`~django.template.Template` instances may also be provided.
Returns the :class:`~django.template.Template` instance to be
rendered.
backend-dependent template object (such as those returned by
:func:`~django.template.loader.get_template()`), the name of a template,
or a list of template names.
Override this method in order to customize template rendering.
Returns the backend-dependent template object instance to be rendered.
Override this method in order to customize template loading.
.. versionchanged:: 1.8
``resolve_template`` returns backend-dependent template object. It
used to return a :class:`~django.template.Template`.
.. deprecated:: 1.8
``resolve_template`` no longer accepts a
:class:`~django.template.Template`.
.. method:: SimpleTemplateResponse.add_post_render_callback()
@ -142,34 +179,37 @@ TemplateResponse objects
.. class:: TemplateResponse()
``TemplateResponse`` is a subclass of
:class:`~django.template.response.SimpleTemplateResponse` that uses
a :class:`~django.template.RequestContext` instead of
a :class:`~django.template.Context`.
:class:`~django.template.response.SimpleTemplateResponse` that knows about
the current :class:`~django.http.HttpRequest`.
Methods
-------
.. method:: TemplateResponse.__init__(request, template, context=None, content_type=None, status=None, current_app=None, charset=None)
Instantiates an ``TemplateResponse`` object with the given
template, context, MIME type and HTTP status.
Instantiates a :class:`~django.template.response.TemplateResponse` object
with the given request, template, context, content type, HTTP status, and
charset.
``request``
An :class:`~django.http.HttpRequest` instance.
``template``
The full name of a template, or a sequence of template names.
:class:`~django.template.Template` instances can also be used.
A backend-dependent template object (such as those returned by
:func:`~django.template.loader.get_template()`), the name of a template,
or a list of template names.
.. deprecated:: 1.8
``template`` used to accept a :class:`~django.template.Template`.
``context``
A dictionary of values to add to the template context. By default,
this is an empty dictionary. :class:`~django.template.Context` objects
are also accepted as ``context`` values. If you pass a
:class:`~django.template.Context` instance or subclass, it will be used
instead of creating a new :class:`~django.template.RequestContext`.
A :class:`dict` of values to add to the template context. By default,
this is an empty dictionary.
``status``
The HTTP Status code for the response.
.. deprecated:: 1.8
``context`` used to accept a :class:`~django.template.Context`.
``content_type``
The value included in the HTTP ``Content-Type`` header, including the
@ -177,6 +217,9 @@ Methods
``content_type`` is specified, then its value is used. Otherwise,
:setting:`DEFAULT_CONTENT_TYPE` is used.
``status``
The HTTP status code for the response.
``current_app``
A hint indicating which application contains the current view. See the
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`
@ -292,14 +335,13 @@ invoked immediately.
Using TemplateResponse and SimpleTemplateResponse
=================================================
A TemplateResponse object can be used anywhere that a normal HttpResponse can be
used. It can also be used as an alternative to calling
:func:`~django.shortcuts.render()` or
A :class:`TemplateResponse` object can be used anywhere that a normal
:class:`django.http.HttpResponse` can be used. It can also be used as an
alternative to calling :func:`~django.shortcuts.render()` or
:func:`~django.shortcuts.render_to_response()`.
For example, the following simple view returns a
:class:`TemplateResponse()` with a simple template, and a context
containing a queryset::
For example, the following simple view returns a :class:`TemplateResponse`
with a simple template and a context containing a queryset::
from django.template.response import TemplateResponse

View File

@ -1397,6 +1397,21 @@ Since it's easier to understand with examples, the :ref:`upgrade guide
All this also applies to :func:`~django.template.loader.select_template()`.
:class:`~django.template.Template` and :class:`~django.template.Context` classes in template responses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some methods of :class:`~django.template.response.SimpleTemplateResponse` and
:class:`~django.template.response.TemplateResponse` accepted
:class:`django.template.Context` and :class:`django.template.Template` objects
as arguments. They should now receive :class:`dict` and backend-dependent
template objects respectively.
This also applies to the return types if you have subclassed either template
response class.
Check the :doc:`template response API documentation </ref/template-response>`
for details.
``current_app`` argument of template-related APIs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

19
tests/cache/tests.py vendored
View File

@ -22,11 +22,11 @@ from django.core.cache import (cache, caches, CacheKeyWarning,
close_caches)
from django.db import connection, connections, transaction
from django.core.cache.utils import make_template_fragment_key
from django.http import HttpResponse, StreamingHttpResponse
from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
from django.middleware.cache import (FetchFromCacheMiddleware,
UpdateCacheMiddleware, CacheMiddleware)
from django.middleware.csrf import CsrfViewMiddleware
from django.template import Template
from django.template import engines
from django.template.context_processors import csrf
from django.template.response import TemplateResponse
from django.test import (TestCase, TransactionTestCase, RequestFactory,
@ -2022,7 +2022,8 @@ class TestWithTemplateResponse(TestCase):
('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
)
for initial_vary, newheaders, resulting_vary in headers:
response = TemplateResponse(HttpResponse(), Template("This is a test"))
template = engines['django'].from_string("This is a test")
response = TemplateResponse(HttpRequest(), template)
if initial_vary is not None:
response['Vary'] = initial_vary
patch_vary_headers(response, newheaders)
@ -2030,7 +2031,8 @@ class TestWithTemplateResponse(TestCase):
def test_get_cache_key(self):
request = self.factory.get(self.path)
response = TemplateResponse(HttpResponse(), Template("This is a test"))
template = engines['django'].from_string("This is a test")
response = TemplateResponse(HttpRequest(), template)
key_prefix = 'localprefix'
# Expect None if no headers have been set yet.
self.assertIsNone(get_cache_key(request))
@ -2052,7 +2054,8 @@ class TestWithTemplateResponse(TestCase):
def test_get_cache_key_with_query(self):
request = self.factory.get(self.path, {'test': 1})
response = TemplateResponse(HttpResponse(), Template("This is a test"))
template = engines['django'].from_string("This is a test")
response = TemplateResponse(HttpRequest(), template)
# Expect None if no headers have been set yet.
self.assertIsNone(get_cache_key(request))
# Set headers to an empty list.
@ -2066,7 +2069,8 @@ class TestWithTemplateResponse(TestCase):
@override_settings(USE_ETAGS=False)
def test_without_etag(self):
response = TemplateResponse(HttpResponse(), Template("This is a test"))
template = engines['django'].from_string("This is a test")
response = TemplateResponse(HttpRequest(), template)
self.assertFalse(response.has_header('ETag'))
patch_response_headers(response)
self.assertFalse(response.has_header('ETag'))
@ -2075,7 +2079,8 @@ class TestWithTemplateResponse(TestCase):
@override_settings(USE_ETAGS=True)
def test_with_etag(self):
response = TemplateResponse(HttpResponse(), Template("This is a test"))
template = engines['django'].from_string("This is a test")
response = TemplateResponse(HttpRequest(), template)
self.assertFalse(response.has_header('ETag'))
patch_response_headers(response)
self.assertFalse(response.has_header('ETag'))

View File

@ -5,7 +5,7 @@ from django.core.exceptions import MiddlewareNotUsed
from django.core.signals import got_request_exception
from django.http import HttpResponse
from django.template.response import TemplateResponse
from django.template import Template
from django.template import engines
from django.test import RequestFactory, TestCase, override_settings
from django.test.utils import patch_logger
@ -63,7 +63,8 @@ class ResponseMiddleware(TestMiddleware):
class TemplateResponseMiddleware(TestMiddleware):
def process_template_response(self, request, response):
super(TemplateResponseMiddleware, self).process_template_response(request, response)
return TemplateResponse(request, Template('Template Response Middleware'))
template = engines['django'].from_string('Template Response Middleware')
return TemplateResponse(request, template)
class ExceptionMiddleware(TestMiddleware):

View File

@ -1,6 +1,6 @@
from django import http
from django.core.exceptions import PermissionDenied
from django.template import Template
from django.template import engines
from django.template.response import TemplateResponse
@ -9,11 +9,13 @@ def normal_view(request):
def template_response(request):
return TemplateResponse(request, Template('OK'))
template = engines['django'].from_string('OK')
return TemplateResponse(request, template)
def template_response_error(request):
return TemplateResponse(request, Template('{%'))
template = engines['django'].from_string('{%')
return TemplateResponse(request, template)
def not_found(request):

View File

@ -7,7 +7,7 @@ import time
from django.test import RequestFactory, SimpleTestCase
from django.conf import settings
from django.template import Template, Context
from django.template import Context, engines
from django.template.response import (TemplateResponse, SimpleTemplateResponse,
ContentNotRenderedError)
from django.test import ignore_warnings, override_settings
@ -29,7 +29,8 @@ class CustomURLConfMiddleware(object):
class SimpleTemplateResponseTest(SimpleTestCase):
def _response(self, template='foo', *args, **kwargs):
return SimpleTemplateResponse(Template(template), *args, **kwargs)
template = engines['django'].from_string(template)
return SimpleTemplateResponse(template, *args, **kwargs)
def test_template_resolving(self):
response = SimpleTemplateResponse('first/test.html')
@ -58,7 +59,8 @@ class SimpleTemplateResponseTest(SimpleTestCase):
self.assertEqual(response.content, b'foo')
# rebaking doesn't change the rendered content
response.template_name = Template('bar{{ baz }}')
template = engines['django'].from_string('bar{{ baz }}')
response.template_name = template
response.render()
self.assertEqual(response.content, b'foo')
@ -113,6 +115,7 @@ class SimpleTemplateResponseTest(SimpleTestCase):
response.render()
self.assertEqual(response.content, b'bar')
@ignore_warnings(category=RemovedInDjango20Warning)
def test_context_instance(self):
response = self._response('{{ foo }}{{ processors }}',
Context({'foo': 'bar'}))
@ -220,8 +223,9 @@ class TemplateResponseTest(SimpleTestCase):
self.factory = RequestFactory()
def _response(self, template='foo', *args, **kwargs):
return TemplateResponse(self.factory.get('/'), Template(template),
*args, **kwargs)
self._request = self.factory.get('/')
template = engines['django'].from_string(template)
return TemplateResponse(self._request, template, *args, **kwargs)
def test_render(self):
response = self._response('{{ foo }}{{ processors }}').render()
@ -232,6 +236,7 @@ class TemplateResponseTest(SimpleTestCase):
{'foo': 'bar'}).render()
self.assertEqual(response.content, b'baryes')
@ignore_warnings(category=RemovedInDjango20Warning)
def test_render_with_context(self):
response = self._response('{{ foo }}{{ processors }}',
Context({'foo': 'bar'})).render()
@ -257,11 +262,8 @@ class TemplateResponseTest(SimpleTestCase):
@ignore_warnings(category=RemovedInDjango20Warning)
def test_custom_app(self):
response = self._response('{{ foo }}', current_app="foobar")
rc = response.resolve_context(response.context_data)
self.assertEqual(rc.request.current_app, 'foobar')
self._response('{{ foo }}', current_app="foobar")
self.assertEqual(self._request.current_app, 'foobar')
def test_pickling(self):
# Create a template response. The context is

View File

@ -8,7 +8,7 @@ import os
import itertools
from django.core.urlresolvers import reverse, NoReverseMatch
from django.template import TemplateSyntaxError, Context, Template
from django.template import TemplateSyntaxError, Context, engines
from django.test import Client, TestCase, ignore_warnings, override_settings
from django.test.client import RedirectCycleError, RequestFactory, encode_file
from django.test.utils import ContextList, str_prefix
@ -158,7 +158,8 @@ class AssertContainsTests(TestCase):
without throwing an error.
Refs #15826.
"""
response = SimpleTemplateResponse(Template('Hello'), status=200)
template = engines['django'].from_string('Hello')
response = SimpleTemplateResponse(template)
self.assertContains(response, 'Hello')
def test_assert_contains_using_non_template_response(self):
@ -174,7 +175,8 @@ class AssertContainsTests(TestCase):
without throwing an error.
Refs #15826.
"""
response = SimpleTemplateResponse(Template('Hello'), status=200)
template = engines['django'].from_string('Hello')
response = SimpleTemplateResponse(template)
self.assertNotContains(response, 'Bye')
def test_assert_not_contains_using_non_template_response(self):

View File

@ -1,5 +1,5 @@
from django.http import HttpResponse
from django.template import Template, Context
from django.template import engines
from django.template.response import TemplateResponse
from django.test import TestCase, RequestFactory
from django.utils.decorators import decorator_from_middleware
@ -70,8 +70,8 @@ class DecoratorFromMiddlewareTests(TestCase):
@full_dec
def normal_view(request):
t = Template("Hello world")
return HttpResponse(t.render(Context({})))
template = engines['django'].from_string("Hello world")
return HttpResponse(template.render())
request = self.rf.get('/')
normal_view(request)
@ -89,8 +89,8 @@ class DecoratorFromMiddlewareTests(TestCase):
@full_dec
def template_response_view(request):
t = Template("Hello world")
return TemplateResponse(request, t, {})
template = engines['django'].from_string("Hello world")
return TemplateResponse(request, template)
request = self.rf.get('/')
response = template_response_view(request)