From 2133f3157eff853329bafb7fda74c3c8fb4eae42 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 26 Jan 2015 21:57:10 +0100 Subject: [PATCH] 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. --- django/shortcuts.py | 16 ++++++++----- django/template/response.py | 13 ++++++---- django/test/utils.py | 24 ++++++++++++++++++- django/views/generic/base.py | 2 ++ .../ref/class-based-views/flattened-index.txt | 14 +++++++++++ docs/ref/class-based-views/mixins-simple.txt | 9 +++++++ docs/ref/template-response.txt | 20 +++++++++++----- docs/topics/http/shortcuts.txt | 18 +++++++++++--- .../jinja2/generic_views/using.html | 1 + .../templates/generic_views/using.html | 1 + tests/generic_views/test_base.py | 16 ++++++++++++- tests/shortcuts/jinja2/shortcuts/using.html | 1 + .../shortcuts/templates/shortcuts/using.html | 1 + tests/shortcuts/tests.py | 24 +++++++++++++++++++ tests/shortcuts/urls.py | 3 +++ tests/shortcuts/views.py | 17 +++++++++++++ .../jinja2/template_tests/using.html | 1 + .../templates/template_tests/using.html | 1 + tests/template_tests/test_response.py | 20 ++++++++++++++++ 19 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 tests/generic_views/jinja2/generic_views/using.html create mode 100644 tests/generic_views/templates/generic_views/using.html create mode 100644 tests/shortcuts/jinja2/shortcuts/using.html create mode 100644 tests/shortcuts/templates/shortcuts/using.html create mode 100644 tests/template_tests/jinja2/template_tests/using.html create mode 100644 tests/template_tests/templates/template_tests/using.html diff --git a/django/shortcuts.py b/django/shortcuts.py index 3bb2412526..bbfc99299f 100644 --- a/django/shortcuts.py +++ b/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) diff --git a/django/template/response.py b/django/template/response.py index ab9559853a..48295c5e05 100644 --- a/django/template/response.py +++ b/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 diff --git a/django/test/utils.py b/django/test/utils.py index 1991602293..f0d8993a40 100644 --- a/django/test/utils.py +++ b/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 diff --git a/django/views/generic/base.py b/django/views/generic/base.py index a873f11f05..88b6608fd2 100644 --- a/django/views/generic/base.py +++ b/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 ) diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index 8f7c743dd4..5af8a4e7f9 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/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` diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index ce2bd2f3aa..ba18a1325b 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/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 ` 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. diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index c1ae0267a5..ecf984cdf3 100644 --- a/docs/ref/template-response.txt +++ b/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 ` 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 ` 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. The rendering process ===================== diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 957f042d12..b2c5da3666 100644 --- a/docs/topics/http/shortcuts.txt +++ b/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 ` 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 ` 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 diff --git a/tests/generic_views/jinja2/generic_views/using.html b/tests/generic_views/jinja2/generic_views/using.html new file mode 100644 index 0000000000..8ce973e958 --- /dev/null +++ b/tests/generic_views/jinja2/generic_views/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/generic_views/templates/generic_views/using.html b/tests/generic_views/templates/generic_views/using.html new file mode 100644 index 0000000000..65bcbf65a4 --- /dev/null +++ b/tests/generic_views/templates/generic_views/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py index 47518fdd92..f297b2e91b 100644 --- a/tests/generic_views/test_base.py +++ b/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. diff --git a/tests/shortcuts/jinja2/shortcuts/using.html b/tests/shortcuts/jinja2/shortcuts/using.html new file mode 100644 index 0000000000..8ce973e958 --- /dev/null +++ b/tests/shortcuts/jinja2/shortcuts/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/shortcuts/templates/shortcuts/using.html b/tests/shortcuts/templates/shortcuts/using.html new file mode 100644 index 0000000000..65bcbf65a4 --- /dev/null +++ b/tests/shortcuts/templates/shortcuts/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/shortcuts/tests.py b/tests/shortcuts/tests.py index e22cf4d386..98251244c5 100644 --- a/tests/shortcuts/tests.py +++ b/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/') diff --git a/tests/shortcuts/urls.py b/tests/shortcuts/urls.py index cbb6c99c14..449a840f73 100644 --- a/tests/shortcuts/urls.py +++ b/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), ] diff --git a/tests/shortcuts/views.py b/tests/shortcuts/views.py index 42493f3e7b..85d1922d58 100644 --- a/tests/shortcuts/views.py +++ b/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', diff --git a/tests/template_tests/jinja2/template_tests/using.html b/tests/template_tests/jinja2/template_tests/using.html new file mode 100644 index 0000000000..8ce973e958 --- /dev/null +++ b/tests/template_tests/jinja2/template_tests/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/template_tests/templates/template_tests/using.html b/tests/template_tests/templates/template_tests/using.html new file mode 100644 index 0000000000..65bcbf65a4 --- /dev/null +++ b/tests/template_tests/templates/template_tests/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/template_tests/test_response.py b/tests/template_tests/test_response.py index 993b2002c8..3807789e63 100644 --- a/tests/template_tests/test_response.py +++ b/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")