Supported multiple template engines in render_to_string.

Adjusted its API through a deprecation path according to the DEP.
This commit is contained in:
Aymeric Augustin 2014-11-28 23:50:34 +01:00
parent f9a6ebf6f5
commit 90805b240f
7 changed files with 146 additions and 27 deletions

View File

@ -3,6 +3,8 @@ This module collects helper functions and classes that "span" multiple levels
of MVC. In other words, these functions/classes introduce controlled coupling of MVC. In other words, these functions/classes introduce controlled coupling
for convenience's sake. for convenience's sake.
""" """
import warnings
from django.template import loader, RequestContext from django.template import loader, RequestContext
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
@ -11,6 +13,7 @@ from django.db.models.manager import Manager
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.core import urlresolvers from django.core import urlresolvers
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
def render_to_response(*args, **kwargs): def render_to_response(*args, **kwargs):
@ -20,7 +23,12 @@ def render_to_response(*args, **kwargs):
""" """
httpresponse_kwargs = {'content_type': kwargs.pop('content_type', None)} httpresponse_kwargs = {'content_type': kwargs.pop('content_type', None)}
return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) # TODO: refactor to avoid the deprecated code path.
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=RemovedInDjango20Warning)
content = loader.render_to_string(*args, **kwargs)
return HttpResponse(content, **httpresponse_kwargs)
def render(request, *args, **kwargs): def render(request, *args, **kwargs):
@ -45,8 +53,12 @@ def render(request, *args, **kwargs):
kwargs['context_instance'] = context_instance kwargs['context_instance'] = context_instance
return HttpResponse(loader.render_to_string(*args, **kwargs), # TODO: refactor to avoid the deprecated code path.
**httpresponse_kwargs) with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=RemovedInDjango20Warning)
content = loader.render_to_string(*args, **kwargs)
return HttpResponse(content, **httpresponse_kwargs)
def redirect(to, *args, **kwargs): def redirect(to, *args, **kwargs):

View File

@ -12,6 +12,8 @@ from .base import Context, Lexer, Parser, Template, TemplateDoesNotExist
from .context import _builtin_context_processors from .context import _builtin_context_processors
_context_instance_undefined = object()
_dictionary_undefined = object()
_dirs_undefined = object() _dirs_undefined = object()
@ -165,14 +167,22 @@ class Engine(object):
template = Template(template, origin, template_name, engine=self) template = Template(template, origin, template_name, engine=self)
return template return template
def render_to_string(self, template_name, dictionary=None, context_instance=None, def render_to_string(self, template_name, context=None,
dirs=_dirs_undefined): context_instance=_context_instance_undefined,
dirs=_dirs_undefined,
dictionary=_dictionary_undefined):
""" """
Loads the given template_name and renders it with the given dictionary as Loads the given template_name and renders it with the given dictionary as
context. The template_name may be a string to load a single template using context. The template_name may be a string to load a single template using
get_template, or it may be a tuple to use select_template to find one of get_template, or it may be a tuple to use select_template to find one of
the templates in the list. Returns a string. the templates in the list. Returns a string.
""" """
if context_instance is _context_instance_undefined:
context_instance = None
else:
warnings.warn(
"The context_instance argument of render_to_string is "
"deprecated.", RemovedInDjango20Warning, stacklevel=2)
if dirs is _dirs_undefined: if dirs is _dirs_undefined:
# Do not set dirs to None here to avoid triggering the deprecation # Do not set dirs to None here to avoid triggering the deprecation
# warning in select_template or get_template. # warning in select_template or get_template.
@ -181,23 +191,30 @@ class Engine(object):
warnings.warn( warnings.warn(
"The dirs argument of render_to_string is deprecated.", "The dirs argument of render_to_string is deprecated.",
RemovedInDjango20Warning, stacklevel=2) RemovedInDjango20Warning, stacklevel=2)
if dictionary is _dictionary_undefined:
dictionary = None
else:
warnings.warn(
"The dictionary argument of render_to_string was renamed to "
"context.", RemovedInDjango20Warning, stacklevel=2)
context = dictionary
if isinstance(template_name, (list, tuple)): if isinstance(template_name, (list, tuple)):
t = self.select_template(template_name, dirs) t = self.select_template(template_name, dirs)
else: else:
t = self.get_template(template_name, dirs) t = self.get_template(template_name, dirs)
if not context_instance: if not context_instance:
# Django < 1.8 accepted a Context in `dictionary` even though that's # Django < 1.8 accepted a Context in `context` even though that's
# unintended. Preserve this ability but don't rewrap `dictionary`. # unintended. Preserve this ability but don't rewrap `context`.
if isinstance(dictionary, Context): if isinstance(context, Context):
return t.render(dictionary) return t.render(context)
else: else:
return t.render(Context(dictionary)) return t.render(Context(context))
if not dictionary: if not context:
return t.render(context_instance) return t.render(context_instance)
# Add the dictionary to the context stack, ensuring it gets removed again # Add the context to the context stack, ensuring it gets removed again
# to keep the context_instance in the same state it started in. # to keep the context_instance in the same state it started in.
with context_instance.push(dictionary): with context_instance.push(context):
return t.render(context_instance) return t.render(context_instance)
def select_template(self, template_name_list, dirs=_dirs_undefined): def select_template(self, template_name_list, dirs=_dirs_undefined):

View File

@ -5,7 +5,8 @@ from django.utils.deprecation import RemovedInDjango20Warning
from . import engines from . import engines
from .backends.django import DjangoTemplates from .backends.django import DjangoTemplates
from .base import Origin, TemplateDoesNotExist from .base import Origin, TemplateDoesNotExist
from .engine import _dirs_undefined, Engine from .engine import (
_context_instance_undefined, _dictionary_undefined, _dirs_undefined)
class LoaderOrigin(Origin): class LoaderOrigin(Origin):
@ -75,8 +76,61 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
raise TemplateDoesNotExist("No template names provided") raise TemplateDoesNotExist("No template names provided")
def render_to_string(*args, **kwargs): def render_to_string(template_name, context=None,
return Engine.get_default().render_to_string(*args, **kwargs) context_instance=_context_instance_undefined,
dirs=_dirs_undefined,
dictionary=_dictionary_undefined,
using=None):
"""
Loads a template and renders it with a context. Returns a string.
template_name may be a string or a list of strings.
"""
if (context_instance is _context_instance_undefined
and dirs is _dirs_undefined
and dictionary is _dictionary_undefined):
# No deprecated arguments were passed - use the new code path
if isinstance(template_name, (list, tuple)):
template = select_template(template_name, using=using)
else:
template = get_template(template_name, using=using)
return template.render(context)
else:
# Some deprecated arguments were passed - use the legacy code path
for engine in _engine_list(using):
try:
# This is required for deprecating arguments specific to Django
# templates. Simply return engine.render_to_string(template_name,
# context) in Django 2.0.
if isinstance(engine, DjangoTemplates):
# Hack -- use the internal Engine instance of DjangoTemplates.
return engine.engine.render_to_string(
template_name, context, context_instance, dirs, dictionary)
elif context_instance is not _context_instance_undefined:
warnings.warn(
"Skipping template backend %s because its render_to_string "
"method doesn't support the context_instance argument." %
engine.name, stacklevel=2)
elif dirs is not _dirs_undefined:
warnings.warn(
"Skipping template backend %s because its render_to_string "
"method doesn't support the dirs argument." % engine.name,
stacklevel=2)
elif dictionary is not _dictionary_undefined:
warnings.warn(
"Skipping template backend %s because its render_to_string "
"method doesn't support the dictionary argument." %
engine.name, stacklevel=2)
except TemplateDoesNotExist:
continue
if template_name:
if isinstance(template_name, (list, tuple)):
template_name = ', '.join(template_name)
raise TemplateDoesNotExist(template_name)
else:
raise TemplateDoesNotExist("No template names provided")
def _engine_list(using=None): def _engine_list(using=None):

View File

@ -88,6 +88,11 @@ details on these changes.
* The backwards compatibility alias ``django.template.loader.BaseLoader`` will * The backwards compatibility alias ``django.template.loader.BaseLoader`` will
be removed. be removed.
* The ``dictionary`` and ``context_instance`` parameters for the following
functions will be removed:
* ``django.template.loader.render_to_string()``
* The ``dirs`` parameter for the following functions will be removed: * The ``dirs`` parameter for the following functions will be removed:
* ``django.template.loader.get_template()`` * ``django.template.loader.get_template()``

View File

@ -890,7 +890,7 @@ When :setting:`TEMPLATE_DEBUG` is ``True`` template objects will have an
The ``render_to_string`` shortcut The ``render_to_string`` shortcut
=================================== ===================================
.. function:: loader.render_to_string(template_name, dictionary=None, context_instance=None) .. function:: loader.render_to_string(template_name, context=None, context_instance=None)
To cut down on the repetitive nature of loading and rendering To cut down on the repetitive nature of loading and rendering
templates, Django provides a shortcut function which largely templates, Django provides a shortcut function which largely
@ -906,16 +906,25 @@ The ``render_to_string`` shortcut takes one required argument --
and render (or a list of template names, in which case Django will use and render (or a list of template names, in which case Django will use
the first template in the list that exists) -- and two optional arguments: the first template in the list that exists) -- and two optional arguments:
dictionary ``context``
A dictionary to be used as variables and values for the A dictionary to be used as variables and values for the
template's context. This can also be passed as the second template's context. This should be passed as the second
positional argument. positional argument.
context_instance .. versionchanged:: 1.8
The ``context`` argument used to be called ``dictionary``. That name
is deprecated in Django 1.8 and will be removed in Django 2.0.
``context_instance``
An instance of :class:`~django.template.Context` or a subclass (e.g., an An instance of :class:`~django.template.Context` or a subclass (e.g., an
instance of :class:`~django.template.RequestContext`) to use as the instance of :class:`~django.template.RequestContext`) to use as the
template's context. This can also be passed as the third positional argument. template's context. This can also be passed as the third positional argument.
.. deprecated:: 1.8
The ``context_instance`` argument is deprecated. Simply use ``context``.
See also the :func:`~django.shortcuts.render_to_response()` shortcut, which See also the :func:`~django.shortcuts.render_to_response()` shortcut, which
calls ``render_to_string`` and feeds the result into an :class:`~django.http.HttpResponse` calls ``render_to_string`` and feeds the result into an :class:`~django.http.HttpResponse`
suitable for returning directly from a view. suitable for returning directly from a view.

View File

@ -1198,6 +1198,20 @@ to construct the "view on site" URL. This URL is now accessible using the
sure to provide a default value for the ``form_class`` argument since it's sure to provide a default value for the ``form_class`` argument since it's
now optional. now optional.
``dictionary`` and ``context_instance`` arguments of rendering functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions will no longer accept the ``dictionary`` and
``context_instance`` parameters in Django 2.0:
* ``django.template.loader.render_to_string()``
Use the ``context`` parameter instead. When ``dictionary`` is passed as a
positional argument, which is the most common idiom, no changes are needed.
There is no replacement for ``context_instance``. All data must be passed to
templates through the ``context`` dict.
``dirs`` argument of template-finding functions ``dirs`` argument of template-finding functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -153,13 +153,6 @@ class RenderToStringTest(SimpleTestCase):
self.assertEqual(loader.render_to_string('test_context.html', self.assertEqual(loader.render_to_string('test_context.html',
{'obj': 'test'}), 'obj:test\n') {'obj': 'test'}), 'obj:test\n')
def test_existing_context_kept_clean(self):
context = Context({'obj': 'before'})
output = loader.render_to_string('test_context.html', {'obj': 'after'},
context_instance=context)
self.assertEqual(output, 'obj:after\n')
self.assertEqual(context['obj'], 'before')
def test_empty_list(self): def test_empty_list(self):
six.assertRaisesRegex(self, TemplateDoesNotExist, six.assertRaisesRegex(self, TemplateDoesNotExist,
'No template names provided$', 'No template names provided$',
@ -170,6 +163,21 @@ class RenderToStringTest(SimpleTestCase):
'No template names provided$', 'No template names provided$',
loader.select_template, []) loader.select_template, [])
@override_settings(
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
)
)
class DeprecatedRenderToStringTest(IgnorePendingDeprecationWarningsMixin, SimpleTestCase):
def test_existing_context_kept_clean(self):
context = Context({'obj': 'before'})
output = loader.render_to_string('test_context.html', {'obj': 'after'},
context_instance=context)
self.assertEqual(output, 'obj:after\n')
self.assertEqual(context['obj'], 'before')
def test_no_empty_dict_pushed_to_stack(self): def test_no_empty_dict_pushed_to_stack(self):
""" """
No empty dict should be pushed to the context stack when render_to_string No empty dict should be pushed to the context stack when render_to_string