Skip to content
/ django Public

Commit 2f0566f

Browse files
berkerpeksagtimgraham
authored andcommitted
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.
1 parent 8931985 commit 2f0566f

File tree

10 files changed

+111
-12
lines changed

10 files changed

+111
-12
lines changed

django/template/loader.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ def find_template(name, dirs=None):
130130
pass
131131
raise TemplateDoesNotExist(name)
132132

133-
def get_template(template_name):
133+
def get_template(template_name, dirs=None):
134134
"""
135135
Returns a compiled Template object for the given template name,
136136
handling template inheritance recursively.
137137
"""
138-
template, origin = find_template(template_name)
138+
template, origin = find_template(template_name, dirs)
139139
if not hasattr(template, 'render'):
140140
# template needs to be compiled
141141
template = get_template_from_string(template, origin, template_name)
@@ -148,7 +148,8 @@ def get_template_from_string(source, origin=None, name=None):
148148
"""
149149
return Template(source, origin, name)
150150

151-
def render_to_string(template_name, dictionary=None, context_instance=None):
151+
def render_to_string(template_name, dictionary=None, context_instance=None,
152+
dirs=None):
152153
"""
153154
Loads the given template_name and renders it with the given dictionary as
154155
context. The template_name may be a string to load a single template using
@@ -157,24 +158,24 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
157158
"""
158159
dictionary = dictionary or {}
159160
if isinstance(template_name, (list, tuple)):
160-
t = select_template(template_name)
161+
t = select_template(template_name, dirs)
161162
else:
162-
t = get_template(template_name)
163+
t = get_template(template_name, dirs)
163164
if not context_instance:
164165
return t.render(Context(dictionary))
165166
# Add the dictionary to the context stack, ensuring it gets removed again
166167
# to keep the context_instance in the same state it started in.
167168
with context_instance.push(dictionary):
168169
return t.render(context_instance)
169170

170-
def select_template(template_name_list):
171+
def select_template(template_name_list, dirs=None):
171172
"Given a list of template names, returns the first that can be loaded."
172173
if not template_name_list:
173174
raise TemplateDoesNotExist("No template names provided")
174175
not_found = []
175176
for template_name in template_name_list:
176177
try:
177-
return get_template(template_name)
178+
return get_template(template_name, dirs)
178179
except TemplateDoesNotExist as e:
179180
if e.args[0] not in not_found:
180181
not_found.append(e.args[0])

docs/ref/templates/api.txt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,17 +594,31 @@ The Python API
594594

595595
``django.template.loader`` has two functions to load templates from files:
596596

597-
.. function:: get_template(template_name)
597+
.. function:: get_template(template_name[, dirs])
598598

599599
``get_template`` returns the compiled template (a ``Template`` object) for
600600
the template with the given name. If the template doesn't exist, it raises
601601
``django.template.TemplateDoesNotExist``.
602602

603-
.. function:: select_template(template_name_list)
603+
To override the :setting:`TEMPLATE_DIRS` setting, use the ``dirs``
604+
parameter. The ``dirs`` parameter may be a tuple or list.
605+
606+
.. versionchanged:: 1.7
607+
608+
The ``dirs`` parameter was added.
609+
610+
.. function:: select_template(template_name_list[, dirs])
604611

605612
``select_template`` is just like ``get_template``, except it takes a list
606613
of template names. Of the list, it returns the first template that exists.
607614

615+
To override the :setting:`TEMPLATE_DIRS` setting, use the ``dirs``
616+
parameter. The ``dirs`` parameter may be a tuple or list.
617+
618+
.. versionchanged:: 1.7
619+
620+
The ``dirs`` parameter was added.
621+
608622
For example, if you call ``get_template('story_detail.html')`` and have the
609623
above :setting:`TEMPLATE_DIRS` setting, here are the files Django will look for,
610624
in order:

docs/releases/1.7.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,14 @@ Templates
285285
* ``TypeError`` exceptions are not longer silenced when raised during the
286286
rendering of a template.
287287

288+
* The following functions now accept a ``dirs`` parameter which is a list or
289+
tuple to override :setting:`TEMPLATE_DIRS`:
290+
291+
* :func:`django.template.loader.get_template()`
292+
* :func:`django.template.loader.select_template()`
293+
* :func:`django.shortcuts.render()`
294+
* :func:`django.shortcuts.render_to_response()`
295+
288296
Tests
289297
^^^^^
290298

docs/topics/http/shortcuts.txt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake.
1515
``render``
1616
==========
1717

18-
.. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app])
18+
.. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app][, dirs])
1919

2020
Combines a given template with a given context dictionary and returns an
2121
:class:`~django.http.HttpResponse` object with that rendered text.
@@ -58,6 +58,13 @@ Optional arguments
5858
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`
5959
for more information.
6060

61+
``dirs``
62+
A tuple or list of values to override the :setting:`TEMPLATE_DIRS` setting.
63+
64+
.. versionchanged:: 1.7
65+
66+
The ``dirs`` parameter was added.
67+
6168
Example
6269
-------
6370

@@ -83,11 +90,19 @@ This example is equivalent to::
8390
return HttpResponse(t.render(c),
8491
content_type="application/xhtml+xml")
8592

93+
If you want to override the :setting:`TEMPLATE_DIRS` setting, use the
94+
``dirs`` parameter::
95+
96+
from django.shortcuts import render
97+
98+
def my_view(request):
99+
# View code here...
100+
return render(request, 'index.html', dirs=('custom_templates',))
86101

87102
``render_to_response``
88103
======================
89104

90-
.. function:: render_to_response(template_name[, dictionary][, context_instance][, content_type])
105+
.. function:: render_to_response(template_name[, dictionary][, context_instance][, content_type][, dirs])
91106

92107
Renders a given template with a given context dictionary and returns an
93108
:class:`~django.http.HttpResponse` object with that rendered text.
@@ -125,6 +140,13 @@ Optional arguments
125140
The MIME type to use for the resulting document. Defaults to the value of
126141
the :setting:`DEFAULT_CONTENT_TYPE` setting.
127142

143+
``dirs``
144+
A tuple or list of values to override the :setting:`TEMPLATE_DIRS` setting.
145+
146+
.. versionchanged:: 1.7
147+
148+
The ``dirs`` parameter was added.
149+
128150
Example
129151
-------
130152

@@ -150,6 +172,15 @@ This example is equivalent to::
150172
return HttpResponse(t.render(c),
151173
content_type="application/xhtml+xml")
152174

175+
If you want to override the :setting:`TEMPLATE_DIRS` setting, use the
176+
``dirs`` parameter::
177+
178+
from django.shortcuts import render_to_response
179+
180+
def my_view(request):
181+
# View code here...
182+
return render_to_response('index.html', dirs=('custom_templates',))
183+
153184
``redirect``
154185
============
155186

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spam eggs{{ obj }}

tests/template_tests/test_loaders.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,28 @@ def test_empty_list(self):
174174
'No template names provided$',
175175
loader.render_to_string, [])
176176

177-
178177
def test_select_templates_from_empty_list(self):
179178
six.assertRaisesRegex(self, TemplateDoesNotExist,
180179
'No template names provided$',
181180
loader.select_template, [])
181+
182+
183+
class TemplateDirsOverrideTest(unittest.TestCase):
184+
185+
dirs_tuple = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
186+
dirs_list = list(dirs_tuple)
187+
dirs_iter = (dirs_tuple, dirs_list)
188+
189+
def test_render_to_string(self):
190+
for dirs in self.dirs_iter:
191+
self.assertEqual(loader.render_to_string('test_dirs.html', dirs=dirs), 'spam eggs\n')
192+
193+
def test_get_template(self):
194+
for dirs in self.dirs_iter:
195+
template = loader.get_template('test_dirs.html', dirs=dirs)
196+
self.assertEqual(template.render(Context({})), 'spam eggs\n')
197+
198+
def test_select_template(self):
199+
for dirs in self.dirs_iter:
200+
template = loader.select_template(['test_dirs.html'], dirs=dirs)
201+
self.assertEqual(template.render(Context({})), 'spam eggs\n')

tests/view_tests/generic_urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@
4848
(r'^shortcuts/render_to_response/$', 'render_to_response_view'),
4949
(r'^shortcuts/render_to_response/request_context/$', 'render_to_response_view_with_request_context'),
5050
(r'^shortcuts/render_to_response/content_type/$', 'render_to_response_view_with_content_type'),
51+
(r'^shortcuts/render_to_response/dirs/$', 'render_to_response_view_with_dirs'),
5152
(r'^shortcuts/render/$', 'render_view'),
5253
(r'^shortcuts/render/base_context/$', 'render_view_with_base_context'),
5354
(r'^shortcuts/render/content_type/$', 'render_view_with_content_type'),
5455
(r'^shortcuts/render/status/$', 'render_view_with_status'),
5556
(r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
57+
(r'^shortcuts/render/dirs/$', 'render_with_dirs'),
5658
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
5759
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spam eggs

tests/view_tests/tests/test_shortcuts.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def test_render_to_response_with_content_type(self):
2727
self.assertEqual(response.content, b'FOO.BAR..\n')
2828
self.assertEqual(response['Content-Type'], 'application/x-rendertest')
2929

30+
def test_render_to_response_with_dirs(self):
31+
response = self.client.get('/shortcuts/render_to_response/dirs/')
32+
self.assertEqual(response.status_code, 200)
33+
self.assertEqual(response.content, b'spam eggs\n')
34+
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
35+
3036
def test_render(self):
3137
response = self.client.get('/shortcuts/render/')
3238
self.assertEqual(response.status_code, 200)
@@ -55,5 +61,11 @@ def test_render_with_current_app(self):
5561
response = self.client.get('/shortcuts/render/current_app/')
5662
self.assertEqual(response.context.current_app, "foobar_app")
5763

64+
def test_render_with_dirs(self):
65+
response = self.client.get('/shortcuts/render/dirs/')
66+
self.assertEqual(response.status_code, 200)
67+
self.assertEqual(response.content, b'spam eggs\n')
68+
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
69+
5870
def test_render_with_current_app_conflict(self):
5971
self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/')

tests/view_tests/views.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
import os
34
import sys
45

56
from django.core.exceptions import PermissionDenied, SuspiciousOperation
@@ -10,10 +11,12 @@
1011
from django.views.debug import technical_500_response, SafeExceptionReporterFilter
1112
from django.views.decorators.debug import (sensitive_post_parameters,
1213
sensitive_variables)
14+
from django.utils._os import upath
1315
from django.utils.log import getLogger
1416

1517
from . import BrokenException, except_args
1618

19+
dirs = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
1720

1821

1922
def index_page(request):
@@ -85,6 +88,9 @@ def render_to_response_view_with_content_type(request):
8588
'bar': 'BAR',
8689
}, content_type='application/x-rendertest')
8790

91+
def render_to_response_view_with_dirs(request):
92+
return render_to_response('render_dirs_test.html', dirs=dirs)
93+
8894
def render_view(request):
8995
return render(request, 'debug/render_test.html', {
9096
'foo': 'FOO',
@@ -123,6 +129,9 @@ def render_view_with_current_app_conflict(request):
123129
'bar': 'BAR',
124130
}, current_app="foobar_app", context_instance=RequestContext(request))
125131

132+
def render_with_dirs(request):
133+
return render(request, 'render_dirs_test.html', dirs=dirs)
134+
126135
def raises_template_does_not_exist(request, path='i_dont_exist.html'):
127136
# We need to inspect the HTML generated by the fancy 500 debug view but
128137
# the test client ignores it, so we send it explicitly.

0 commit comments

Comments
 (0)