diff --git a/django/template/loader.py b/django/template/loader.py index 30d32719606..410b03726ae 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -130,12 +130,12 @@ def find_template(name, dirs=None): pass 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, handling template inheritance recursively. """ - template, origin = find_template(template_name) + template, origin = find_template(template_name, dirs) if not hasattr(template, 'render'): # template needs to be compiled 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) -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 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 {} if isinstance(template_name, (list, tuple)): - t = select_template(template_name) + t = select_template(template_name, dirs) else: - t = get_template(template_name) + t = get_template(template_name, dirs) if not context_instance: return t.render(Context(dictionary)) # 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): 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." if not template_name_list: raise TemplateDoesNotExist("No template names provided") not_found = [] for template_name in template_name_list: try: - return get_template(template_name) + return get_template(template_name, dirs) except TemplateDoesNotExist as e: if e.args[0] not in not_found: not_found.append(e.args[0]) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index b0fc94040a8..b3e49b67737 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -594,17 +594,31 @@ The Python API ``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 the template with the given name. If the template doesn't exist, it raises ``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 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 above :setting:`TEMPLATE_DIRS` setting, here are the files Django will look for, in order: diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 685efe04b30..bd22b5513f6 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -285,6 +285,14 @@ Templates * ``TypeError`` exceptions are not longer silenced when raised during the 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 ^^^^^ diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 8a80affb2a2..27e88178848 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[, 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 :class:`~django.http.HttpResponse` object with that rendered text. @@ -58,6 +58,13 @@ Optional arguments :ref:`namespaced URL resolution strategy ` 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 ------- @@ -83,11 +90,19 @@ This example is equivalent to:: return HttpResponse(t.render(c), 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`` ====================== -.. 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 :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 :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 ------- @@ -150,6 +172,15 @@ This example is equivalent to:: return HttpResponse(t.render(c), 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`` ============ diff --git a/tests/template_tests/other_templates/test_dirs.html b/tests/template_tests/other_templates/test_dirs.html new file mode 100644 index 00000000000..d99b954618a --- /dev/null +++ b/tests/template_tests/other_templates/test_dirs.html @@ -0,0 +1 @@ +spam eggs{{ obj }} diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 9d15b95d4be..41aa3b58265 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -174,8 +174,28 @@ class RenderToStringTest(unittest.TestCase): 'No template names provided$', loader.render_to_string, []) - def test_select_templates_from_empty_list(self): six.assertRaisesRegex(self, TemplateDoesNotExist, 'No template names provided$', 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') diff --git a/tests/view_tests/generic_urls.py b/tests/view_tests/generic_urls.py index 10e7601eb6a..a3af059e396 100644 --- a/tests/view_tests/generic_urls.py +++ b/tests/view_tests/generic_urls.py @@ -48,10 +48,12 @@ urlpatterns += patterns('view_tests.views', (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/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/base_context/$', 'render_view_with_base_context'), (r'^shortcuts/render/content_type/$', 'render_view_with_content_type'), (r'^shortcuts/render/status/$', 'render_view_with_status'), (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'), ) diff --git a/tests/view_tests/other_templates/render_dirs_test.html b/tests/view_tests/other_templates/render_dirs_test.html new file mode 100644 index 00000000000..52a6c1d3a7b --- /dev/null +++ b/tests/view_tests/other_templates/render_dirs_test.html @@ -0,0 +1 @@ +spam eggs diff --git a/tests/view_tests/tests/test_shortcuts.py b/tests/view_tests/tests/test_shortcuts.py index 74c556f41a9..678eb36e3d7 100644 --- a/tests/view_tests/tests/test_shortcuts.py +++ b/tests/view_tests/tests/test_shortcuts.py @@ -27,6 +27,12 @@ class ShortcutTests(TestCase): self.assertEqual(response.content, b'FOO.BAR..\n') 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): response = self.client.get('/shortcuts/render/') self.assertEqual(response.status_code, 200) @@ -55,5 +61,11 @@ class ShortcutTests(TestCase): response = self.client.get('/shortcuts/render/current_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): self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/') diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py index c21520eaa7f..814931a717a 100644 --- a/tests/view_tests/views.py +++ b/tests/view_tests/views.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import os import sys 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.decorators.debug import (sensitive_post_parameters, sensitive_variables) +from django.utils._os import upath from django.utils.log import getLogger from . import BrokenException, except_args +dirs = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),) def index_page(request): @@ -85,6 +88,9 @@ def render_to_response_view_with_content_type(request): 'bar': 'BAR', }, 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): return render(request, 'debug/render_test.html', { 'foo': 'FOO', @@ -123,6 +129,9 @@ def render_view_with_current_app_conflict(request): 'bar': 'BAR', }, 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'): # We need to inspect the HTML generated by the fancy 500 debug view but # the test client ignores it, so we send it explicitly.