Fixed #4278 -- Added a dirs parameter to a few functions to override TEMPLATE_DIRS.

* django.template.loader.get_template()
* django.template.loader.select_template()
* django.shortcuts.render()
* django.shortcuts.render_to_response()

Thanks amcnabb for the suggestion.
This commit is contained in:
Berker Peksag 2013-09-16 00:51:24 +03:00 committed by Tim Graham
parent 893198509e
commit 2f0566fa61
10 changed files with 111 additions and 12 deletions

View File

@ -130,12 +130,12 @@ def find_template(name, dirs=None):
pass pass
raise TemplateDoesNotExist(name) raise TemplateDoesNotExist(name)
def get_template(template_name): def get_template(template_name, dirs=None):
""" """
Returns a compiled Template object for the given template name, Returns a compiled Template object for the given template name,
handling template inheritance recursively. handling template inheritance recursively.
""" """
template, origin = find_template(template_name) template, origin = find_template(template_name, dirs)
if not hasattr(template, 'render'): if not hasattr(template, 'render'):
# template needs to be compiled # template needs to be compiled
template = get_template_from_string(template, origin, template_name) template = get_template_from_string(template, origin, template_name)
@ -148,7 +148,8 @@ def get_template_from_string(source, origin=None, name=None):
""" """
return Template(source, origin, name) return Template(source, origin, name)
def render_to_string(template_name, dictionary=None, context_instance=None): def render_to_string(template_name, dictionary=None, context_instance=None,
dirs=None):
""" """
Loads the given template_name and renders it with the given dictionary as Loads the given template_name and renders it with the given dictionary as
context. The template_name may be a string to load a single template using context. The template_name may be a string to load a single template using
@ -157,9 +158,9 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
""" """
dictionary = dictionary or {} dictionary = dictionary or {}
if isinstance(template_name, (list, tuple)): if isinstance(template_name, (list, tuple)):
t = select_template(template_name) t = select_template(template_name, dirs)
else: else:
t = get_template(template_name) t = get_template(template_name, dirs)
if not context_instance: if not context_instance:
return t.render(Context(dictionary)) return t.render(Context(dictionary))
# Add the dictionary to the context stack, ensuring it gets removed again # Add the dictionary to the context stack, ensuring it gets removed again
@ -167,14 +168,14 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
with context_instance.push(dictionary): with context_instance.push(dictionary):
return t.render(context_instance) return t.render(context_instance)
def select_template(template_name_list): def select_template(template_name_list, dirs=None):
"Given a list of template names, returns the first that can be loaded." "Given a list of template names, returns the first that can be loaded."
if not template_name_list: if not template_name_list:
raise TemplateDoesNotExist("No template names provided") raise TemplateDoesNotExist("No template names provided")
not_found = [] not_found = []
for template_name in template_name_list: for template_name in template_name_list:
try: try:
return get_template(template_name) return get_template(template_name, dirs)
except TemplateDoesNotExist as e: except TemplateDoesNotExist as e:
if e.args[0] not in not_found: if e.args[0] not in not_found:
not_found.append(e.args[0]) not_found.append(e.args[0])

View File

@ -594,17 +594,31 @@ The Python API
``django.template.loader`` has two functions to load templates from files: ``django.template.loader`` has two functions to load templates from files:
.. function:: get_template(template_name) .. function:: get_template(template_name[, dirs])
``get_template`` returns the compiled template (a ``Template`` object) for ``get_template`` returns the compiled template (a ``Template`` object) for
the template with the given name. If the template doesn't exist, it raises the template with the given name. If the template doesn't exist, it raises
``django.template.TemplateDoesNotExist``. ``django.template.TemplateDoesNotExist``.
.. function:: select_template(template_name_list) To override the :setting:`TEMPLATE_DIRS` setting, use the ``dirs``
parameter. The ``dirs`` parameter may be a tuple or list.
.. versionchanged:: 1.7
The ``dirs`` parameter was added.
.. function:: select_template(template_name_list[, dirs])
``select_template`` is just like ``get_template``, except it takes a list ``select_template`` is just like ``get_template``, except it takes a list
of template names. Of the list, it returns the first template that exists. of template names. Of the list, it returns the first template that exists.
To override the :setting:`TEMPLATE_DIRS` setting, use the ``dirs``
parameter. The ``dirs`` parameter may be a tuple or list.
.. versionchanged:: 1.7
The ``dirs`` parameter was added.
For example, if you call ``get_template('story_detail.html')`` and have the For example, if you call ``get_template('story_detail.html')`` and have the
above :setting:`TEMPLATE_DIRS` setting, here are the files Django will look for, above :setting:`TEMPLATE_DIRS` setting, here are the files Django will look for,
in order: in order:

View File

@ -285,6 +285,14 @@ Templates
* ``TypeError`` exceptions are not longer silenced when raised during the * ``TypeError`` exceptions are not longer silenced when raised during the
rendering of a template. rendering of a template.
* The following functions now accept a ``dirs`` parameter which is a list or
tuple to override :setting:`TEMPLATE_DIRS`:
* :func:`django.template.loader.get_template()`
* :func:`django.template.loader.select_template()`
* :func:`django.shortcuts.render()`
* :func:`django.shortcuts.render_to_response()`
Tests Tests
^^^^^ ^^^^^

View File

@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake.
``render`` ``render``
========== ==========
.. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app]) .. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app][, dirs])
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.
@ -58,6 +58,13 @@ Optional arguments
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>` :ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`
for more information. for more information.
``dirs``
A tuple or list of values to override the :setting:`TEMPLATE_DIRS` setting.
.. versionchanged:: 1.7
The ``dirs`` parameter was added.
Example Example
------- -------
@ -83,11 +90,19 @@ This example is equivalent to::
return HttpResponse(t.render(c), return HttpResponse(t.render(c),
content_type="application/xhtml+xml") content_type="application/xhtml+xml")
If you want to override the :setting:`TEMPLATE_DIRS` setting, use the
``dirs`` parameter::
from django.shortcuts import render
def my_view(request):
# View code here...
return render(request, 'index.html', dirs=('custom_templates',))
``render_to_response`` ``render_to_response``
====================== ======================
.. function:: render_to_response(template_name[, dictionary][, context_instance][, content_type]) .. function:: render_to_response(template_name[, dictionary][, context_instance][, content_type][, dirs])
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.
@ -125,6 +140,13 @@ Optional arguments
The MIME type to use for the resulting document. Defaults to the value of The MIME type to use for the resulting document. Defaults to the value of
the :setting:`DEFAULT_CONTENT_TYPE` setting. the :setting:`DEFAULT_CONTENT_TYPE` setting.
``dirs``
A tuple or list of values to override the :setting:`TEMPLATE_DIRS` setting.
.. versionchanged:: 1.7
The ``dirs`` parameter was added.
Example Example
------- -------
@ -150,6 +172,15 @@ This example is equivalent to::
return HttpResponse(t.render(c), return HttpResponse(t.render(c),
content_type="application/xhtml+xml") content_type="application/xhtml+xml")
If you want to override the :setting:`TEMPLATE_DIRS` setting, use the
``dirs`` parameter::
from django.shortcuts import render_to_response
def my_view(request):
# View code here...
return render_to_response('index.html', dirs=('custom_templates',))
``redirect`` ``redirect``
============ ============

View File

@ -0,0 +1 @@
spam eggs{{ obj }}

View File

@ -174,8 +174,28 @@ class RenderToStringTest(unittest.TestCase):
'No template names provided$', 'No template names provided$',
loader.render_to_string, []) loader.render_to_string, [])
def test_select_templates_from_empty_list(self): def test_select_templates_from_empty_list(self):
six.assertRaisesRegex(self, TemplateDoesNotExist, six.assertRaisesRegex(self, TemplateDoesNotExist,
'No template names provided$', 'No template names provided$',
loader.select_template, []) loader.select_template, [])
class TemplateDirsOverrideTest(unittest.TestCase):
dirs_tuple = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
dirs_list = list(dirs_tuple)
dirs_iter = (dirs_tuple, dirs_list)
def test_render_to_string(self):
for dirs in self.dirs_iter:
self.assertEqual(loader.render_to_string('test_dirs.html', dirs=dirs), 'spam eggs\n')
def test_get_template(self):
for dirs in self.dirs_iter:
template = loader.get_template('test_dirs.html', dirs=dirs)
self.assertEqual(template.render(Context({})), 'spam eggs\n')
def test_select_template(self):
for dirs in self.dirs_iter:
template = loader.select_template(['test_dirs.html'], dirs=dirs)
self.assertEqual(template.render(Context({})), 'spam eggs\n')

View File

@ -48,10 +48,12 @@ urlpatterns += patterns('view_tests.views',
(r'^shortcuts/render_to_response/$', 'render_to_response_view'), (r'^shortcuts/render_to_response/$', 'render_to_response_view'),
(r'^shortcuts/render_to_response/request_context/$', 'render_to_response_view_with_request_context'), (r'^shortcuts/render_to_response/request_context/$', 'render_to_response_view_with_request_context'),
(r'^shortcuts/render_to_response/content_type/$', 'render_to_response_view_with_content_type'), (r'^shortcuts/render_to_response/content_type/$', 'render_to_response_view_with_content_type'),
(r'^shortcuts/render_to_response/dirs/$', 'render_to_response_view_with_dirs'),
(r'^shortcuts/render/$', 'render_view'), (r'^shortcuts/render/$', 'render_view'),
(r'^shortcuts/render/base_context/$', 'render_view_with_base_context'), (r'^shortcuts/render/base_context/$', 'render_view_with_base_context'),
(r'^shortcuts/render/content_type/$', 'render_view_with_content_type'), (r'^shortcuts/render/content_type/$', 'render_view_with_content_type'),
(r'^shortcuts/render/status/$', 'render_view_with_status'), (r'^shortcuts/render/status/$', 'render_view_with_status'),
(r'^shortcuts/render/current_app/$', 'render_view_with_current_app'), (r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
(r'^shortcuts/render/dirs/$', 'render_with_dirs'),
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'), (r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
) )

View File

@ -0,0 +1 @@
spam eggs

View File

@ -27,6 +27,12 @@ class ShortcutTests(TestCase):
self.assertEqual(response.content, b'FOO.BAR..\n') self.assertEqual(response.content, b'FOO.BAR..\n')
self.assertEqual(response['Content-Type'], 'application/x-rendertest') self.assertEqual(response['Content-Type'], 'application/x-rendertest')
def test_render_to_response_with_dirs(self):
response = self.client.get('/shortcuts/render_to_response/dirs/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b'spam eggs\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
def test_render(self): def test_render(self):
response = self.client.get('/shortcuts/render/') response = self.client.get('/shortcuts/render/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -55,5 +61,11 @@ class ShortcutTests(TestCase):
response = self.client.get('/shortcuts/render/current_app/') response = self.client.get('/shortcuts/render/current_app/')
self.assertEqual(response.context.current_app, "foobar_app") self.assertEqual(response.context.current_app, "foobar_app")
def test_render_with_dirs(self):
response = self.client.get('/shortcuts/render/dirs/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b'spam eggs\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
def test_render_with_current_app_conflict(self): def test_render_with_current_app_conflict(self):
self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/') self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/')

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import sys import sys
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
@ -10,10 +11,12 @@ from django.template import Context, RequestContext, TemplateDoesNotExist
from django.views.debug import technical_500_response, SafeExceptionReporterFilter from django.views.debug import technical_500_response, SafeExceptionReporterFilter
from django.views.decorators.debug import (sensitive_post_parameters, from django.views.decorators.debug import (sensitive_post_parameters,
sensitive_variables) sensitive_variables)
from django.utils._os import upath
from django.utils.log import getLogger from django.utils.log import getLogger
from . import BrokenException, except_args from . import BrokenException, except_args
dirs = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
def index_page(request): def index_page(request):
@ -85,6 +88,9 @@ def render_to_response_view_with_content_type(request):
'bar': 'BAR', 'bar': 'BAR',
}, content_type='application/x-rendertest') }, content_type='application/x-rendertest')
def render_to_response_view_with_dirs(request):
return render_to_response('render_dirs_test.html', dirs=dirs)
def render_view(request): def render_view(request):
return render(request, 'debug/render_test.html', { return render(request, 'debug/render_test.html', {
'foo': 'FOO', 'foo': 'FOO',
@ -123,6 +129,9 @@ def render_view_with_current_app_conflict(request):
'bar': 'BAR', 'bar': 'BAR',
}, current_app="foobar_app", context_instance=RequestContext(request)) }, current_app="foobar_app", context_instance=RequestContext(request))
def render_with_dirs(request):
return render(request, 'render_dirs_test.html', dirs=dirs)
def raises_template_does_not_exist(request, path='i_dont_exist.html'): def raises_template_does_not_exist(request, path='i_dont_exist.html'):
# We need to inspect the HTML generated by the fancy 500 debug view but # We need to inspect the HTML generated by the fancy 500 debug view but
# the test client ignores it, so we send it explicitly. # the test client ignores it, so we send it explicitly.