1
0
mirror of https://github.com/django/django.git synced 2024-12-27 19:46:22 +00:00

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.
This commit is contained in:
Aymeric Augustin 2015-01-26 21:57:10 +01:00
parent a53541852d
commit 2133f3157e
19 changed files with 180 additions and 22 deletions

View File

@ -25,7 +25,7 @@ from django.utils.functional import Promise
def render_to_response(template_name, context=None, def render_to_response(template_name, context=None,
context_instance=_context_instance_undefined, context_instance=_context_instance_undefined,
content_type=None, status=None, dirs=_dirs_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 Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments. 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 dirs is _dirs_undefined
and dictionary is _dictionary_undefined): and dictionary is _dictionary_undefined):
# No deprecated arguments were passed - use the new code path # 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: else:
# Some deprecated arguments were passed - use the legacy code path # Some deprecated arguments were passed - use the legacy code path
content = loader.render_to_string( 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) 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, def render(request, template_name, context=None,
context_instance=_context_instance_undefined, context_instance=_context_instance_undefined,
content_type=None, status=None, current_app=_current_app_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 Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments. 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): and dictionary is _dictionary_undefined):
# No deprecated arguments were passed - use the new code path # No deprecated arguments were passed - use the new code path
# In Django 2.0, request should become a positional argument. # 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: else:
# Some deprecated arguments were passed - use the legacy code path # 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 context_instance._current_app = current_app
content = loader.render_to_string( 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) return HttpResponse(content, content_type, status)

View File

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

View File

@ -3,7 +3,7 @@ import logging
import re import re
import sys import sys
import time import time
from unittest import skipUnless from unittest import skipIf, skipUnless
import warnings import warnings
from functools import wraps from functools import wraps
from xml.dom.minidom import parseString, Node 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.encoding import force_str
from django.utils.translation import deactivate from django.utils.translation import deactivate
try:
import jinja2
except ImportError:
jinja2 = None
__all__ = ( __all__ = (
'Approximate', 'ContextList', 'get_runner', 'Approximate', 'ContextList', 'get_runner',
@ -573,3 +578,20 @@ def freeze_time(t):
yield yield
finally: finally:
time.time = _real_time 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

View File

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

View File

@ -35,6 +35,7 @@ TemplateView
* :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type`
* :attr:`~django.views.generic.base.View.http_method_names` * :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.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.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
**Methods** **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.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_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :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.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_field`
* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.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.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.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.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.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
**Methods** **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_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :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.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.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_field`
* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` * :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_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :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.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.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_field`
* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` * :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_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :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.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.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_field`
* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
@ -346,6 +354,7 @@ YearArchiveView
* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :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.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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
* :attr:`~django.views.generic.dates.WeekMixin.week` [:meth:`~django.views.generic.dates.WeekMixin.get_week`] * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] * :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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :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.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.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.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] * :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.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_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :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.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_field`
* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`

View File

@ -49,6 +49,15 @@ TemplateResponseMixin
a ``template_name`` will raise a a ``template_name`` will raise a
:class:`django.core.exceptions.ImproperlyConfigured` exception. :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 .. attribute:: response_class
The response class to be returned by ``render_to_response`` method. The response class to be returned by ``render_to_response`` method.

View File

@ -65,7 +65,7 @@ Attributes
Methods 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` Instantiates a :class:`~django.template.response.SimpleTemplateResponse`
object with the given template, context, content type, HTTP status, and 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 be extracted from ``content_type``, and if that is unsuccessful, the
:setting:`DEFAULT_CHARSET` setting will be used. :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) .. method:: SimpleTemplateResponse.resolve_context(context)
@ -185,7 +189,7 @@ TemplateResponse objects
Methods 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 Instantiates a :class:`~django.template.response.TemplateResponse` object
with the given request, template, context, content type, HTTP status, and 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 be extracted from ``content_type``, and if that is unsuccessful, the
:setting:`DEFAULT_CHARSET` setting will be used. :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.
The rendering process The rendering process
===================== =====================

View File

@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake.
``render`` ``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 Combines a given template with a given context dictionary and returns an
:class:`~django.http.HttpResponse` object with that rendered text. :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 The ``current_app`` argument is deprecated. Instead you should set
``request.current_app``. ``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 .. deprecated:: 1.8
The ``dirs`` parameter was deprecated. The ``dirs`` parameter was deprecated.
@ -109,7 +117,7 @@ This example is equivalent to::
``render_to_response`` ``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 Renders a given template with a given context dictionary and returns an
:class:`~django.http.HttpResponse` object with that rendered text. :class:`~django.http.HttpResponse` object with that rendered text.
@ -159,9 +167,13 @@ Optional arguments
``status`` ``status``
The status code for the response. Defaults to ``200``. 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 .. versionchanged:: 1.8
The ``status`` parameter was added. The ``status`` and ``using`` parameters were added.
.. deprecated:: 1.8 .. deprecated:: 1.8

View File

@ -0,0 +1 @@
Jinja2

View File

@ -0,0 +1 @@
DTL

View File

@ -7,6 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import resolve from django.core.urlresolvers import resolve
from django.http import HttpResponse from django.http import HttpResponse
from django.test import TestCase, RequestFactory, override_settings from django.test import TestCase, RequestFactory, override_settings
from django.test.utils import require_jinja2
from django.views.generic import View, TemplateView, RedirectView from django.views.generic import View, TemplateView, RedirectView
from . import views from . import views
@ -278,10 +279,23 @@ class TemplateViewTest(TestCase):
def test_template_name_required(self): 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/') 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): def test_template_params(self):
""" """
A generic template view passes kwargs as context. A generic template view passes kwargs as context.

View File

@ -0,0 +1 @@
Jinja2

View File

@ -0,0 +1 @@
DTL

View File

@ -1,5 +1,6 @@
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from django.test import TestCase, ignore_warnings, override_settings from django.test import TestCase, ignore_warnings, override_settings
from django.test.utils import require_jinja2
@override_settings( @override_settings(
@ -38,6 +39,20 @@ class ShortcutTests(TestCase):
self.assertEqual(response.content, b'spam eggs\n') self.assertEqual(response.content, b'spam eggs\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') 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) @ignore_warnings(category=RemovedInDjango20Warning)
def test_render_to_response_with_context_instance_misuse(self): 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.status_code, 403)
self.assertEqual(response.content, b'FOO.BAR../render/status/\n') 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) @ignore_warnings(category=RemovedInDjango20Warning)
def test_render_with_current_app(self): def test_render_with_current_app(self):
response = self.client.get('/render/current_app/') response = self.client.get('/render/current_app/')

View File

@ -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/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/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/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_to_response/context_instance_misuse/$', views.render_to_response_with_context_instance_misuse),
url(r'^render/$', views.render_view), url(r'^render/$', views.render_view),
url(r'^render/multiple_templates/$', views.render_view_with_multiple_templates), 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/content_type/$', views.render_view_with_content_type),
url(r'^render/dirs/$', views.render_with_dirs), url(r'^render/dirs/$', views.render_with_dirs),
url(r'^render/status/$', views.render_view_with_status), 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/$', views.render_view_with_current_app),
url(r'^render/current_app_conflict/$', views.render_view_with_current_app_conflict), url(r'^render/current_app_conflict/$', views.render_view_with_current_app_conflict),
] ]

View File

@ -43,6 +43,18 @@ def render_to_response_view_with_dirs(request):
return render_to_response('render_dirs_test.html', dirs=dirs) 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): def context_processor(request):
return {'bar': 'context processor output'} return {'bar': 'context processor output'}
@ -95,6 +107,11 @@ def render_view_with_status(request):
}, status=403) }, 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): def render_view_with_current_app(request):
return render(request, 'shortcuts/render_test.html', { return render(request, 'shortcuts/render_test.html', {
'foo': 'FOO', 'foo': 'FOO',

View File

@ -0,0 +1 @@
Jinja2

View File

@ -0,0 +1 @@
DTL

View File

@ -11,6 +11,7 @@ from django.template import Context, engines
from django.template.response import (TemplateResponse, SimpleTemplateResponse, from django.template.response import (TemplateResponse, SimpleTemplateResponse,
ContentNotRenderedError) ContentNotRenderedError)
from django.test import ignore_warnings, override_settings from django.test import ignore_warnings, override_settings
from django.test.utils import require_jinja2
from django.utils._os import upath from django.utils._os import upath
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
@ -133,6 +134,15 @@ class SimpleTemplateResponseTest(SimpleTestCase):
self.assertEqual(response['content-type'], 'application/json') self.assertEqual(response['content-type'], 'application/json')
self.assertEqual(response.status_code, 504) 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): def test_post_callbacks(self):
"Rendering a template response triggers the post-render callbacks" "Rendering a template response triggers the post-render callbacks"
post = [] post = []
@ -260,6 +270,16 @@ class TemplateResponseTest(SimpleTestCase):
self.assertEqual(response['content-type'], 'application/json') self.assertEqual(response['content-type'], 'application/json')
self.assertEqual(response.status_code, 504) 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) @ignore_warnings(category=RemovedInDjango20Warning)
def test_custom_app(self): def test_custom_app(self):
self._response('{{ foo }}', current_app="foobar") self._response('{{ foo }}', current_app="foobar")