Skip to content

Commit

Permalink
Fixed #4278 -- Added a dirs parameter to a few functions to override …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
berkerpeksag authored and timgraham committed Sep 18, 2013
1 parent 8931985 commit 2f0566f
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 12 deletions.
15 changes: 8 additions & 7 deletions django/template/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -157,24 +158,24 @@ 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
# to keep the context_instance in the same state it started in.
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])
Expand Down
18 changes: 16 additions & 2 deletions docs/ref/templates/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions docs/releases/1.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^

Expand Down
35 changes: 33 additions & 2 deletions docs/topics/http/shortcuts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -58,6 +58,13 @@ Optional arguments
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`
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
-------

Expand All @@ -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.
Expand Down Expand Up @@ -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
-------

Expand All @@ -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``
============

Expand Down
1 change: 1 addition & 0 deletions tests/template_tests/other_templates/test_dirs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spam eggs{{ obj }}
22 changes: 21 additions & 1 deletion tests/template_tests/test_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,28 @@ def test_empty_list(self):
'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')
2 changes: 2 additions & 0 deletions tests/view_tests/generic_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@
(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'),
)
1 change: 1 addition & 0 deletions tests/view_tests/other_templates/render_dirs_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spam eggs
12 changes: 12 additions & 0 deletions tests/view_tests/tests/test_shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ def test_render_to_response_with_content_type(self):
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)
Expand Down Expand Up @@ -55,5 +61,11 @@ def test_render_with_current_app(self):
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/')
9 changes: 9 additions & 0 deletions tests/view_tests/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import unicode_literals

import os
import sys

from django.core.exceptions import PermissionDenied, SuspiciousOperation
Expand All @@ -10,10 +11,12 @@
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):
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 2f0566f

Please sign in to comment.