diff --git a/django/shortcuts.py b/django/shortcuts.py index 62560472c10..d1c0770af97 100644 --- a/django/shortcuts.py +++ b/django/shortcuts.py @@ -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 for convenience's sake. """ +import warnings + from django.template import loader, RequestContext from django.http import HttpResponse, Http404 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.core import urlresolvers from django.utils import six +from django.utils.deprecation import RemovedInDjango20Warning 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)} - 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): @@ -45,8 +53,12 @@ def render(request, *args, **kwargs): kwargs['context_instance'] = context_instance - 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 redirect(to, *args, **kwargs): diff --git a/django/template/engine.py b/django/template/engine.py index 3bf953ce7c9..a76d52c5653 100644 --- a/django/template/engine.py +++ b/django/template/engine.py @@ -12,6 +12,8 @@ from .base import Context, Lexer, Parser, Template, TemplateDoesNotExist from .context import _builtin_context_processors +_context_instance_undefined = object() +_dictionary_undefined = object() _dirs_undefined = object() @@ -165,14 +167,22 @@ class Engine(object): template = Template(template, origin, template_name, engine=self) return template - def render_to_string(self, template_name, dictionary=None, context_instance=None, - dirs=_dirs_undefined): + def render_to_string(self, template_name, context=None, + context_instance=_context_instance_undefined, + dirs=_dirs_undefined, + dictionary=_dictionary_undefined): """ 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 get_template, or it may be a tuple to use select_template to find one of 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: # Do not set dirs to None here to avoid triggering the deprecation # warning in select_template or get_template. @@ -181,23 +191,30 @@ class Engine(object): warnings.warn( "The dirs argument of render_to_string is deprecated.", 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)): t = self.select_template(template_name, dirs) else: t = self.get_template(template_name, dirs) if not context_instance: - # Django < 1.8 accepted a Context in `dictionary` even though that's - # unintended. Preserve this ability but don't rewrap `dictionary`. - if isinstance(dictionary, Context): - return t.render(dictionary) + # Django < 1.8 accepted a Context in `context` even though that's + # unintended. Preserve this ability but don't rewrap `context`. + if isinstance(context, Context): + return t.render(context) else: - return t.render(Context(dictionary)) - if not dictionary: + return t.render(Context(context)) + if not context: 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. - with context_instance.push(dictionary): + with context_instance.push(context): return t.render(context_instance) def select_template(self, template_name_list, dirs=_dirs_undefined): diff --git a/django/template/loader.py b/django/template/loader.py index 87a9789b313..0bc64e4d7d8 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -5,7 +5,8 @@ from django.utils.deprecation import RemovedInDjango20Warning from . import engines from .backends.django import DjangoTemplates from .base import Origin, TemplateDoesNotExist -from .engine import _dirs_undefined, Engine +from .engine import ( + _context_instance_undefined, _dictionary_undefined, _dirs_undefined) class LoaderOrigin(Origin): @@ -75,8 +76,61 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None): raise TemplateDoesNotExist("No template names provided") -def render_to_string(*args, **kwargs): - return Engine.get_default().render_to_string(*args, **kwargs) +def render_to_string(template_name, context=None, + 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): diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 50ff5ce3b93..216aab95748 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -88,6 +88,11 @@ details on these changes. * The backwards compatibility alias ``django.template.loader.BaseLoader`` will 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: * ``django.template.loader.get_template()`` diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index c86b7873aeb..f1845448387 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -890,7 +890,7 @@ When :setting:`TEMPLATE_DEBUG` is ``True`` template objects will have an 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 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 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 - template's context. This can also be passed as the second + template's context. This should be passed as the second 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 instance of :class:`~django.template.RequestContext`) to use as the 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 calls ``render_to_string`` and feeds the result into an :class:`~django.http.HttpResponse` suitable for returning directly from a view. diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 8bf3659ba28..54ff4c605e1 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -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 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index f068d8b71a4..14390c3f468 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -153,13 +153,6 @@ class RenderToStringTest(SimpleTestCase): self.assertEqual(loader.render_to_string('test_context.html', {'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): six.assertRaisesRegex(self, TemplateDoesNotExist, 'No template names provided$', @@ -170,6 +163,21 @@ class RenderToStringTest(SimpleTestCase): 'No template names provided$', 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): """ No empty dict should be pushed to the context stack when render_to_string