Browse Source

Fixed #24168 -- Allowed selecting a template engine in a few APIs.

Specifically in rendering shortcuts, template responses, and class-based
views that return template responses.

Also added a test for render_to_response(status=...) which was missing
from fdbfc980.

Thanks Tim and Carl for the review.
Aymeric Augustin 10 years ago
parent
commit
2133f3157e

+ 10 - 6
django/shortcuts.py

@@ -25,7 +25,7 @@ from django.utils.functional import Promise
 def render_to_response(template_name, context=None,
                        context_instance=_context_instance_undefined,
                        content_type=None, status=None, dirs=_dirs_undefined,
-                       dictionary=_dictionary_undefined):
+                       dictionary=_dictionary_undefined, using=None):
     """
     Returns a HttpResponse whose content is filled with the result of calling
     django.template.loader.render_to_string() with the passed arguments.
@@ -34,12 +34,13 @@ def render_to_response(template_name, context=None,
             and dirs is _dirs_undefined
             and dictionary is _dictionary_undefined):
         # No deprecated arguments were passed - use the new code path
-        content = loader.render_to_string(template_name, context)
+        content = loader.render_to_string(template_name, context, using=using)
 
     else:
         # Some deprecated arguments were passed - use the legacy code path
         content = loader.render_to_string(
-            template_name, context, context_instance, dirs, dictionary)
+            template_name, context, context_instance, dirs, dictionary,
+            using=using)
 
     return HttpResponse(content, content_type, status)
 
@@ -47,7 +48,8 @@ def render_to_response(template_name, context=None,
 def render(request, template_name, context=None,
            context_instance=_context_instance_undefined,
            content_type=None, status=None, current_app=_current_app_undefined,
-           dirs=_dirs_undefined, dictionary=_dictionary_undefined):
+           dirs=_dirs_undefined, dictionary=_dictionary_undefined,
+           using=None):
     """
     Returns a HttpResponse whose content is filled with the result of calling
     django.template.loader.render_to_string() with the passed arguments.
@@ -59,7 +61,8 @@ def render(request, template_name, context=None,
             and dictionary is _dictionary_undefined):
         # No deprecated arguments were passed - use the new code path
         # In Django 2.0, request should become a positional argument.
-        content = loader.render_to_string(template_name, context, request=request)
+        content = loader.render_to_string(
+            template_name, context, request=request, using=using)
 
     else:
         # Some deprecated arguments were passed - use the legacy code path
@@ -80,7 +83,8 @@ def render(request, template_name, context=None,
                 context_instance._current_app = current_app
 
         content = loader.render_to_string(
-            template_name, context, context_instance, dirs, dictionary)
+            template_name, context, context_instance, dirs, dictionary,
+            using=using)
 
     return HttpResponse(content, content_type, status)
 

+ 8 - 5
django/template/response.py

@@ -16,7 +16,7 @@ class SimpleTemplateResponse(HttpResponse):
     rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks']
 
     def __init__(self, template, context=None, content_type=None, status=None,
-                 charset=None):
+                 charset=None, using=None):
         if isinstance(template, Template):
             warnings.warn(
                 "{}'s template argument cannot be a django.template.Template "
@@ -31,6 +31,8 @@ class SimpleTemplateResponse(HttpResponse):
         self.template_name = template
         self.context_data = context
 
+        self.using = using
+
         self._post_render_callbacks = []
 
         # _request stores the current request object in subclasses that know
@@ -73,9 +75,9 @@ class SimpleTemplateResponse(HttpResponse):
     def resolve_template(self, template):
         "Accepts a template object, path-to-template or list of paths"
         if isinstance(template, (list, tuple)):
-            return loader.select_template(template)
+            return loader.select_template(template, using=self.using)
         elif isinstance(template, six.string_types):
-            return loader.get_template(template)
+            return loader.get_template(template, using=self.using)
         else:
             return template
 
@@ -189,7 +191,8 @@ class TemplateResponse(SimpleTemplateResponse):
     rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request', '_current_app']
 
     def __init__(self, request, template, context=None, content_type=None,
-            status=None, current_app=_current_app_undefined, charset=None):
+            status=None, current_app=_current_app_undefined, charset=None,
+            using=None):
         # 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:
@@ -199,5 +202,5 @@ class TemplateResponse(SimpleTemplateResponse):
                 RemovedInDjango20Warning, stacklevel=2)
             request.current_app = current_app
         super(TemplateResponse, self).__init__(
-            template, context, content_type, status, charset)
+            template, context, content_type, status, charset, using)
         self._request = request

+ 23 - 1
django/test/utils.py

@@ -3,7 +3,7 @@ import logging
 import re
 import sys
 import time
-from unittest import skipUnless
+from unittest import skipIf, skipUnless
 import warnings
 from functools import wraps
 from xml.dom.minidom import parseString, Node
@@ -20,6 +20,11 @@ from django.utils import six
 from django.utils.encoding import force_str
 from django.utils.translation import deactivate
 
+try:
+    import jinja2
+except ImportError:
+    jinja2 = None
+
 
 __all__ = (
     'Approximate', 'ContextList', 'get_runner',
@@ -573,3 +578,20 @@ def freeze_time(t):
         yield
     finally:
         time.time = _real_time
+
+
+def require_jinja2(test_func):
+    """
+    Decorator to enable a Jinja2 template engine in addition to the regular
+    Django template engine for a test or skip it if Jinja2 isn't available.
+    """
+    test_func = skipIf(jinja2 is None, "this test requires jinja2")(test_func)
+    test_func = override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'APP_DIRS': True,
+    }, {
+        'BACKEND': 'django.template.backends.jinja2.Jinja2',
+        'APP_DIRS': True,
+        'OPTIONS': {'keep_trailing_newline': True},
+    }])(test_func)
+    return test_func

+ 2 - 0
django/views/generic/base.py

@@ -114,6 +114,7 @@ class TemplateResponseMixin(object):
     A mixin that can be used to render a template.
     """
     template_name = None
+    template_engine = None
     response_class = TemplateResponse
     content_type = None
 
@@ -130,6 +131,7 @@ class TemplateResponseMixin(object):
             request=self.request,
             template=self.get_template_names(),
             context=context,
+            using=self.template_engine,
             **response_kwargs
         )
 

+ 14 - 0
docs/ref/class-based-views/flattened-index.txt

@@ -35,6 +35,7 @@ TemplateView
 * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type`
 * :attr:`~django.views.generic.base.View.http_method_names`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 
 **Methods**
@@ -89,6 +90,7 @@ DetailView
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
@@ -124,6 +126,7 @@ ListView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 
@@ -155,6 +158,7 @@ FormView
 * :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
 * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 
 **Methods**
@@ -191,6 +195,7 @@ CreateView
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
 * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
@@ -232,6 +237,7 @@ UpdateView
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
 * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
@@ -269,6 +275,7 @@ DeleteView
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
 * :attr:`~django.views.generic.edit.DeletionMixin.success_url` [:meth:`~django.views.generic.edit.DeletionMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
@@ -308,6 +315,7 @@ ArchiveIndexView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 
@@ -346,6 +354,7 @@ YearArchiveView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
@@ -387,6 +396,7 @@ MonthArchiveView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
@@ -428,6 +438,7 @@ WeekArchiveView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 * :attr:`~django.views.generic.dates.WeekMixin.week` [:meth:`~django.views.generic.dates.WeekMixin.get_week`]
@@ -473,6 +484,7 @@ DayArchiveView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
@@ -520,6 +532,7 @@ TodayArchiveView
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
 * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
@@ -565,6 +578,7 @@ DateDetailView
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
 * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine`
 * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
 * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`

+ 9 - 0
docs/ref/class-based-views/mixins-simple.txt

@@ -49,6 +49,15 @@ TemplateResponseMixin
         a ``template_name`` will raise a
         :class:`django.core.exceptions.ImproperlyConfigured` exception.
 
+    .. attribute:: template_engine
+
+        .. versionadded:: 1.8
+
+        The :setting:`NAME <TEMPLATES-NAME>` of a template engine to use for
+        loading the template. ``template_engine`` is passed as the ``using``
+        keyword argument to ``response_class``. Default is ``None``, which
+        tells Django to search for the template in all configured engines.
+
     .. attribute:: response_class
 
         The response class to be returned by ``render_to_response`` method.

+ 14 - 6
docs/ref/template-response.txt

@@ -65,7 +65,7 @@ Attributes
 Methods
 -------
 
-.. method:: SimpleTemplateResponse.__init__(template, context=None, content_type=None, status=None, charset=None)
+.. method:: SimpleTemplateResponse.__init__(template, context=None, content_type=None, status=None, charset=None, using=None)
 
     Instantiates a :class:`~django.template.response.SimpleTemplateResponse`
     object with the given template, context, content type, HTTP status, and
@@ -102,9 +102,13 @@ Methods
         be extracted from ``content_type``, and if that is unsuccessful, the
         :setting:`DEFAULT_CHARSET` setting will be used.
 
-    .. versionadded:: 1.8
+    ``using``
+        The :setting:`NAME <TEMPLATES-NAME>` of a template engine to use for
+        loading the template.
 
-        The ``charset`` parameter was added.
+    .. versionchanged:: 1.8
+
+        The ``charset`` and ``using`` parameters were added.
 
 .. method:: SimpleTemplateResponse.resolve_context(context)
 
@@ -185,7 +189,7 @@ TemplateResponse objects
 Methods
 -------
 
-.. method:: TemplateResponse.__init__(request, template, context=None, content_type=None, status=None, current_app=None, charset=None)
+.. method:: TemplateResponse.__init__(request, template, context=None, content_type=None, status=None, current_app=None, charset=None, using=None)
 
     Instantiates a :class:`~django.template.response.TemplateResponse` object
     with the given request, template, context, content type, HTTP status, and
@@ -235,9 +239,13 @@ Methods
         be extracted from ``content_type``, and if that is unsuccessful, the
         :setting:`DEFAULT_CHARSET` setting will be used.
 
-    .. versionadded:: 1.8
+    ``using``
+        The :setting:`NAME <TEMPLATES-NAME>` of a template engine to use for
+        loading the template.
+
+    .. versionchanged:: 1.8
 
-        The ``charset`` parameter was added.
+        The ``charset`` and ``using`` parameters were added.
 
 The rendering process
 =====================

+ 15 - 3
docs/topics/http/shortcuts.txt

@@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake.
 ``render``
 ==========
 
-.. function:: render(request, template_name[, context][, context_instance][, content_type][, status][, current_app][, dirs])
+.. function:: render(request, template_name[, context][, context_instance][, content_type][, status][, current_app][, dirs][, using])
 
    Combines a given template with a given context dictionary and returns an
    :class:`~django.http.HttpResponse` object with that rendered text.
@@ -77,6 +77,14 @@ Optional arguments
        The ``current_app`` argument is deprecated. Instead you should set
        ``request.current_app``.
 
+``using``
+    The :setting:`NAME <TEMPLATES-NAME>` of a template engine to use for
+    loading the template.
+
+.. versionchanged:: 1.8
+
+    The ``using`` parameter was added.
+
 .. deprecated:: 1.8
 
    The ``dirs`` parameter was deprecated.
@@ -109,7 +117,7 @@ This example is equivalent to::
 ``render_to_response``
 ======================
 
-.. function:: render_to_response(template_name[, context][, context_instance][, content_type][, status][, dirs])
+.. function:: render_to_response(template_name[, context][, context_instance][, content_type][, status][, dirs][, using])
 
    Renders a given template with a given context dictionary and returns an
    :class:`~django.http.HttpResponse` object with that rendered text.
@@ -159,9 +167,13 @@ Optional arguments
 ``status``
     The status code for the response. Defaults to ``200``.
 
+``using``
+    The :setting:`NAME <TEMPLATES-NAME>` of a template engine to use for
+    loading the template.
+
 .. versionchanged:: 1.8
 
-   The ``status`` parameter was added.
+   The ``status`` and ``using`` parameters were added.
 
 .. deprecated:: 1.8
 

+ 1 - 0
tests/generic_views/jinja2/generic_views/using.html

@@ -0,0 +1 @@
+Jinja2

+ 1 - 0
tests/generic_views/templates/generic_views/using.html

@@ -0,0 +1 @@
+DTL

+ 15 - 1
tests/generic_views/test_base.py

@@ -7,6 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.core.urlresolvers import resolve
 from django.http import HttpResponse
 from django.test import TestCase, RequestFactory, override_settings
+from django.test.utils import require_jinja2
 from django.views.generic import View, TemplateView, RedirectView
 
 from . import views
@@ -278,10 +279,23 @@ class TemplateViewTest(TestCase):
 
     def test_template_name_required(self):
         """
-        A template view must provide a template name
+        A template view must provide a template name.
         """
         self.assertRaises(ImproperlyConfigured, self.client.get, '/template/no_template/')
 
+    @require_jinja2
+    def test_template_engine(self):
+        """
+        A template view may provide a template engine.
+        """
+        request = self.rf.get('/using/')
+        view = TemplateView.as_view(template_name='generic_views/using.html')
+        self.assertEqual(view(request).render().content, b'DTL\n')
+        view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='django')
+        self.assertEqual(view(request).render().content, b'DTL\n')
+        view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='jinja2')
+        self.assertEqual(view(request).render().content, b'Jinja2\n')
+
     def test_template_params(self):
         """
         A generic template view passes kwargs as context.

+ 1 - 0
tests/shortcuts/jinja2/shortcuts/using.html

@@ -0,0 +1 @@
+Jinja2

+ 1 - 0
tests/shortcuts/templates/shortcuts/using.html

@@ -0,0 +1 @@
+DTL

+ 24 - 0
tests/shortcuts/tests.py

@@ -1,5 +1,6 @@
 from django.utils.deprecation import RemovedInDjango20Warning
 from django.test import TestCase, ignore_warnings, override_settings
+from django.test.utils import require_jinja2
 
 
 @override_settings(
@@ -38,6 +39,20 @@ class ShortcutTests(TestCase):
         self.assertEqual(response.content, b'spam eggs\n')
         self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
 
+    def test_render_to_response_with_status(self):
+        response = self.client.get('/render_to_response/status/')
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.content, b'FOO.BAR..\n')
+
+    @require_jinja2
+    def test_render_to_response_with_using(self):
+        response = self.client.get('/render_to_response/using/')
+        self.assertEqual(response.content, b'DTL\n')
+        response = self.client.get('/render_to_response/using/?using=django')
+        self.assertEqual(response.content, b'DTL\n')
+        response = self.client.get('/render_to_response/using/?using=jinja2')
+        self.assertEqual(response.content, b'Jinja2\n')
+
     @ignore_warnings(category=RemovedInDjango20Warning)
     def test_render_to_response_with_context_instance_misuse(self):
         """
@@ -78,6 +93,15 @@ class ShortcutTests(TestCase):
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.content, b'FOO.BAR../render/status/\n')
 
+    @require_jinja2
+    def test_render_with_using(self):
+        response = self.client.get('/render/using/')
+        self.assertEqual(response.content, b'DTL\n')
+        response = self.client.get('/render/using/?using=django')
+        self.assertEqual(response.content, b'DTL\n')
+        response = self.client.get('/render/using/?using=jinja2')
+        self.assertEqual(response.content, b'Jinja2\n')
+
     @ignore_warnings(category=RemovedInDjango20Warning)
     def test_render_with_current_app(self):
         response = self.client.get('/render/current_app/')

+ 3 - 0
tests/shortcuts/urls.py

@@ -8,6 +8,8 @@ urlpatterns = [
     url(r'^render_to_response/request_context/$', views.render_to_response_view_with_request_context),
     url(r'^render_to_response/content_type/$', views.render_to_response_view_with_content_type),
     url(r'^render_to_response/dirs/$', views.render_to_response_view_with_dirs),
+    url(r'^render_to_response/status/$', views.render_to_response_view_with_status),
+    url(r'^render_to_response/using/$', views.render_to_response_view_with_using),
     url(r'^render_to_response/context_instance_misuse/$', views.render_to_response_with_context_instance_misuse),
     url(r'^render/$', views.render_view),
     url(r'^render/multiple_templates/$', views.render_view_with_multiple_templates),
@@ -15,6 +17,7 @@ urlpatterns = [
     url(r'^render/content_type/$', views.render_view_with_content_type),
     url(r'^render/dirs/$', views.render_with_dirs),
     url(r'^render/status/$', views.render_view_with_status),
+    url(r'^render/using/$', views.render_view_with_using),
     url(r'^render/current_app/$', views.render_view_with_current_app),
     url(r'^render/current_app_conflict/$', views.render_view_with_current_app_conflict),
 ]

+ 17 - 0
tests/shortcuts/views.py

@@ -43,6 +43,18 @@ def render_to_response_view_with_dirs(request):
     return render_to_response('render_dirs_test.html', dirs=dirs)
 
 
+def render_to_response_view_with_status(request):
+    return render_to_response('shortcuts/render_test.html', {
+        'foo': 'FOO',
+        'bar': 'BAR',
+    }, status=403)
+
+
+def render_to_response_view_with_using(request):
+    using = request.GET.get('using')
+    return render_to_response('shortcuts/using.html', using=using)
+
+
 def context_processor(request):
     return {'bar': 'context processor output'}
 
@@ -95,6 +107,11 @@ def render_view_with_status(request):
     }, status=403)
 
 
+def render_view_with_using(request):
+    using = request.GET.get('using')
+    return render(request, 'shortcuts/using.html', using=using)
+
+
 def render_view_with_current_app(request):
     return render(request, 'shortcuts/render_test.html', {
         'foo': 'FOO',

+ 1 - 0
tests/template_tests/jinja2/template_tests/using.html

@@ -0,0 +1 @@
+Jinja2

+ 1 - 0
tests/template_tests/templates/template_tests/using.html

@@ -0,0 +1 @@
+DTL

+ 20 - 0
tests/template_tests/test_response.py

@@ -11,6 +11,7 @@ from django.template import Context, engines
 from django.template.response import (TemplateResponse, SimpleTemplateResponse,
                                       ContentNotRenderedError)
 from django.test import ignore_warnings, override_settings
+from django.test.utils import require_jinja2
 from django.utils._os import upath
 from django.utils.deprecation import RemovedInDjango20Warning
 
@@ -133,6 +134,15 @@ class SimpleTemplateResponseTest(SimpleTestCase):
         self.assertEqual(response['content-type'], 'application/json')
         self.assertEqual(response.status_code, 504)
 
+    @require_jinja2
+    def test_using(self):
+        response = SimpleTemplateResponse('template_tests/using.html').render()
+        self.assertEqual(response.content, b'DTL\n')
+        response = SimpleTemplateResponse('template_tests/using.html', using='django').render()
+        self.assertEqual(response.content, b'DTL\n')
+        response = SimpleTemplateResponse('template_tests/using.html', using='jinja2').render()
+        self.assertEqual(response.content, b'Jinja2\n')
+
     def test_post_callbacks(self):
         "Rendering a template response triggers the post-render callbacks"
         post = []
@@ -260,6 +270,16 @@ class TemplateResponseTest(SimpleTestCase):
         self.assertEqual(response['content-type'], 'application/json')
         self.assertEqual(response.status_code, 504)
 
+    @require_jinja2
+    def test_using(self):
+        request = self.factory.get('/')
+        response = TemplateResponse(request, 'template_tests/using.html').render()
+        self.assertEqual(response.content, b'DTL\n')
+        response = TemplateResponse(request, 'template_tests/using.html', using='django').render()
+        self.assertEqual(response.content, b'DTL\n')
+        response = TemplateResponse(request, 'template_tests/using.html', using='jinja2').render()
+        self.assertEqual(response.content, b'Jinja2\n')
+
     @ignore_warnings(category=RemovedInDjango20Warning)
     def test_custom_app(self):
         self._response('{{ foo }}', current_app="foobar")