diff --git a/django/template/backends/django.py b/django/template/backends/django.py
index 5940c0b457..9c2d24af13 100644
--- a/django/template/backends/django.py
+++ b/django/template/backends/django.py
@@ -1,11 +1,14 @@
# Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import
+import sys
import warnings
from django.conf import settings
+from django.template import TemplateDoesNotExist
from django.template.context import Context, RequestContext, make_context
from django.template.engine import Engine, _dirs_undefined
+from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
from .base import BaseEngine
@@ -24,21 +27,23 @@ class DjangoTemplates(BaseEngine):
self.engine = Engine(self.dirs, self.app_dirs, **options)
def from_string(self, template_code):
- return Template(self.engine.from_string(template_code))
+ return Template(self.engine.from_string(template_code), self)
def get_template(self, template_name, dirs=_dirs_undefined):
- return Template(self.engine.get_template(template_name, dirs))
+ try:
+ return Template(self.engine.get_template(template_name, dirs), self)
+ except TemplateDoesNotExist as exc:
+ reraise(exc, self)
class Template(object):
- def __init__(self, template):
+ def __init__(self, template, backend):
self.template = template
+ self.backend = backend
@property
def origin(self):
- # TODO: define the Origin API. For now simply forwarding to the
- # underlying Template preserves backwards-compatibility.
return self.template.origin
def render(self, context=None, request=None):
@@ -71,4 +76,17 @@ class Template(object):
else:
context = make_context(context, request)
- return self.template.render(context)
+ try:
+ return self.template.render(context)
+ except TemplateDoesNotExist as exc:
+ reraise(exc, self.backend)
+
+
+def reraise(exc, backend):
+ """
+ Reraise TemplateDoesNotExist while maintaining template debug information.
+ """
+ new = exc.__class__(*exc.args, tried=exc.tried, backend=backend)
+ if hasattr(exc, 'template_debug'):
+ new.template_debug = exc.template_debug
+ six.reraise(exc.__class__, new, sys.exc_info()[2])
diff --git a/django/template/backends/dummy.py b/django/template/backends/dummy.py
index 7df095baa6..1d6f446dec 100644
--- a/django/template/backends/dummy.py
+++ b/django/template/backends/dummy.py
@@ -1,12 +1,13 @@
# Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import
+import errno
import io
import string
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
-from django.template import TemplateDoesNotExist
+from django.template import Origin, TemplateDoesNotExist
from django.utils.html import conditional_escape
from .base import BaseEngine
@@ -29,17 +30,24 @@ class TemplateStrings(BaseEngine):
return Template(template_code)
def get_template(self, template_name):
+ tried = []
for template_file in self.iter_template_filenames(template_name):
try:
with io.open(template_file, encoding=settings.FILE_CHARSET) as fp:
template_code = fp.read()
- except IOError:
- continue
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ tried.append((
+ Origin(template_file, template_name, self),
+ 'Source does not exist',
+ ))
+ continue
+ raise
return Template(template_code)
else:
- raise TemplateDoesNotExist(template_name)
+ raise TemplateDoesNotExist(template_name, tried=tried, backend=self)
class Template(string.Template):
diff --git a/django/template/backends/jinja2.py b/django/template/backends/jinja2.py
index 0863773ddf..2d5190122a 100644
--- a/django/template/backends/jinja2.py
+++ b/django/template/backends/jinja2.py
@@ -41,17 +41,24 @@ class Jinja2(BaseEngine):
try:
return Template(self.env.get_template(template_name))
except jinja2.TemplateNotFound as exc:
- six.reraise(TemplateDoesNotExist, TemplateDoesNotExist(exc.args),
- sys.exc_info()[2])
+ six.reraise(
+ TemplateDoesNotExist,
+ TemplateDoesNotExist(exc.name, backend=self),
+ sys.exc_info()[2],
+ )
except jinja2.TemplateSyntaxError as exc:
- six.reraise(TemplateSyntaxError, TemplateSyntaxError(exc.args),
- sys.exc_info()[2])
+ new = TemplateSyntaxError(exc.args)
+ new.template_debug = get_exception_info(exc)
+ six.reraise(TemplateSyntaxError, new, sys.exc_info()[2])
class Template(object):
def __init__(self, template):
self.template = template
+ self.origin = Origin(
+ name=template.filename, template_name=template.name,
+ )
def render(self, context=None, request=None):
if context is None:
@@ -61,3 +68,40 @@ class Template(object):
context['csrf_input'] = csrf_input_lazy(request)
context['csrf_token'] = csrf_token_lazy(request)
return self.template.render(context)
+
+
+class Origin(object):
+ """
+ A container to hold debug information as described in the template API
+ documentation.
+ """
+ def __init__(self, name, template_name):
+ self.name = name
+ self.template_name = template_name
+
+
+def get_exception_info(exception):
+ """
+ Formats exception information for display on the debug page using the
+ structure described in the template API documentation.
+ """
+ context_lines = 10
+ lineno = exception.lineno
+ lines = list(enumerate(exception.source.strip().split("\n"), start=1))
+ during = lines[lineno - 1][1]
+ total = len(lines)
+ top = max(0, lineno - context_lines - 1)
+ bottom = min(total, lineno + context_lines)
+
+ return {
+ 'name': exception.filename,
+ 'message': exception.message,
+ 'source_lines': lines[top:bottom],
+ 'line': lineno,
+ 'before': '',
+ 'during': during,
+ 'after': '',
+ 'total': total,
+ 'top': top,
+ 'bottom': bottom,
+ }
diff --git a/django/template/base.py b/django/template/base.py
index e1849999f2..3f1e1c2d72 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -135,13 +135,27 @@ class TemplateSyntaxError(Exception):
class TemplateDoesNotExist(Exception):
"""
- This exception is used when template loaders are unable to find a
- template. The tried argument is an optional list of tuples containing
- (origin, status), where origin is an Origin object and status is a string
- with the reason the template wasn't found.
+ The exception used by backends when a template does not exist. Accepts the
+ following optional arguments:
+
+ backend
+ The template backend class used when raising this exception.
+
+ tried
+ A list of sources that were tried when finding the template. This
+ is formatted as a list of tuples containing (origin, status), where
+ origin is an Origin object and status is a string with the reason the
+ template wasn't found.
+
+ chain
+ A list of intermediate TemplateDoesNotExist exceptions. This is used to
+ encapsulate multiple exceptions when loading templates from multiple
+ engines.
"""
- def __init__(self, msg, tried=None):
+ def __init__(self, msg, tried=None, backend=None, chain=None):
+ self.backend = backend
self.tried = tried or []
+ self.chain = chain or []
super(TemplateDoesNotExist, self).__init__(msg)
diff --git a/django/template/loader.py b/django/template/loader.py
index 2f9afd3071..43146e42b6 100644
--- a/django/template/loader.py
+++ b/django/template/loader.py
@@ -17,7 +17,7 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
Raises TemplateDoesNotExist if no such template exists.
"""
- tried = []
+ chain = []
engines = _engine_list(using)
for engine in engines:
try:
@@ -33,9 +33,9 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
else:
return engine.get_template(template_name)
except TemplateDoesNotExist as e:
- tried.extend(e.tried)
+ chain.append(e)
- raise TemplateDoesNotExist(template_name, tried=tried)
+ raise TemplateDoesNotExist(template_name, chain=chain)
def select_template(template_name_list, dirs=_dirs_undefined, using=None):
@@ -46,7 +46,7 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
Raises TemplateDoesNotExist if no such template exists.
"""
- tried = []
+ chain = []
engines = _engine_list(using)
for template_name in template_name_list:
for engine in engines:
@@ -63,10 +63,10 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
else:
return engine.get_template(template_name)
except TemplateDoesNotExist as e:
- tried.extend(e.tried)
+ chain.append(e)
if template_name_list:
- raise TemplateDoesNotExist(', '.join(template_name_list), tried=tried)
+ raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
else:
raise TemplateDoesNotExist("No template names provided")
@@ -92,7 +92,7 @@ def render_to_string(template_name, context=None,
return template.render(context, request)
else:
- tried = []
+ chain = []
# Some deprecated arguments were passed - use the legacy code path
for engine in _engine_list(using):
try:
@@ -124,13 +124,13 @@ def render_to_string(template_name, context=None,
"method doesn't support the dictionary argument." %
engine.name, stacklevel=2)
except TemplateDoesNotExist as e:
- tried.extend(e.tried)
+ chain.append(e)
continue
if template_name:
if isinstance(template_name, (list, tuple)):
template_name = ', '.join(template_name)
- raise TemplateDoesNotExist(template_name, tried=tried)
+ raise TemplateDoesNotExist(template_name, chain=chain)
else:
raise TemplateDoesNotExist("No template names provided")
diff --git a/django/views/debug.py b/django/views/debug.py
index 9ae31d2dff..ae1352106a 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -9,7 +9,7 @@ from django.core.urlresolvers import Resolver404, resolve
from django.http import (
HttpRequest, HttpResponse, HttpResponseNotFound, build_request_repr,
)
-from django.template import Context, Engine, TemplateDoesNotExist, engines
+from django.template import Context, Engine, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint
from django.utils import lru_cache, six, timezone
from django.utils.datastructures import MultiValueDict
@@ -276,25 +276,7 @@ class ExceptionReporter(object):
"""Return a dictionary containing traceback information."""
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
self.template_does_not_exist = True
- postmortem = []
-
- # TODO: add support for multiple template engines (#24120).
- # TemplateDoesNotExist should carry all the information, including
- # the backend, rather than looping through engines.all.
- for engine in engines.all():
- if hasattr(engine, 'engine'):
- e = engine.engine
- else:
- e = engine
-
- postmortem.append(dict(
- engine=engine,
- tried=[
- entry for entry in self.exc_value.tried if
- entry[0].loader.engine == e
- ],
- ))
- self.postmortem = postmortem
+ self.postmortem = self.exc_value.chain or [self.exc_value]
frames = self.get_traceback_frames()
for i, frame in enumerate(frames):
@@ -751,7 +733,7 @@ TECHNICAL_500_TEMPLATE = ("""
{% if postmortem %}
Django tried loading these templates, in this order:
{% for entry in postmortem %}
- Using engine {{ entry.engine.name }}
:
+ Using engine {{ entry.backend.name }}
:
{% if entry.tried %}
{% for attempt in entry.tried %}
@@ -890,7 +872,7 @@ Installed Middleware:
{% if template_does_not_exist %}Template loader postmortem
{% if postmortem %}Django tried loading these templates, in this order:
{% for entry in postmortem %}
-Using engine {{ entry.engine.name }}:
+Using engine {{ entry.backend.name }}:
{% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
{% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %}
@@ -1083,7 +1065,7 @@ Installed Middleware:
{% if template_does_not_exist %}Template loader postmortem
{% if postmortem %}Django tried loading these templates, in this order:
{% for entry in postmortem %}
-Using engine {{ entry.engine.name }}:
+Using engine {{ entry.backend.name }}:
{% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
{% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %}
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index 513177fff2..99daac6a8f 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -249,6 +249,9 @@ Templates
* The debug page template postmortem now include output from each engine that
is installed.
+* :ref:`Debug page integration ` for custom
+ template engines was added.
+
Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/_images/postmortem.png b/docs/topics/_images/postmortem.png
new file mode 100644
index 0000000000..164125ed9d
Binary files /dev/null and b/docs/topics/_images/postmortem.png differ
diff --git a/docs/topics/_images/template-lines.png b/docs/topics/_images/template-lines.png
new file mode 100644
index 0000000000..c4934049f1
Binary files /dev/null and b/docs/topics/_images/template-lines.png differ
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index d3b951a7ac..3810ea7483 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -152,11 +152,32 @@ The ``django.template.loader`` module defines two functions to load templates.
If loading a template fails, the following two exceptions, defined in
``django.template``, may be raised:
-.. exception:: TemplateDoesNotExist
+.. exception:: TemplateDoesNotExist(msg, tried=None, backend=None, chain=None)
- This exception is raised when a template cannot be found.
+ This exception is raised when a template cannot be found. It accepts the
+ following optional arguments for populating the :ref:`template postmortem
+ ` on the debug page:
-.. exception:: TemplateSyntaxError
+ ``backend``
+ The template backend instance from which the exception originated.
+
+ ``tried``
+ A list of sources that were tried when finding the template. This is
+ formatted as a list of tuples containing ``(origin, status)``, where
+ ``origin`` is an :ref:`origin-like ` object and
+ ``status`` is a string with the reason the template wasn't found.
+
+ ``chain``
+ A list of intermediate :exc:`~django.template.TemplateDoesNotExist`
+ exceptions raised when trying to load a template. This is used by
+ functions, such as :func:`~django.template.loader.get_template`, that
+ try to load a given template from multiple engines.
+
+ .. versionadded:: 1.9
+
+ The ``backend``, ``tried``, and ``chain`` arguments were added.
+
+.. exception:: TemplateSyntaxError(msg)
This exception is raised when a template was found but contains errors.
@@ -478,7 +499,6 @@ fictional ``foobar`` template library::
self.engine = foobar.Engine(**options)
-
def from_string(self, template_code):
try:
return Template(self.engine.from_string(template_code))
@@ -489,7 +509,7 @@ fictional ``foobar`` template library::
try:
return Template(self.engine.get_template(template_name))
except foobar.TemplateNotFound as exc:
- raise TemplateDoesNotExist(exc.args)
+ raise TemplateDoesNotExist(exc.args, backend=self)
except foobar.TemplateCompilationFailed as exc:
raise TemplateSyntaxError(exc.args)
@@ -510,6 +530,117 @@ fictional ``foobar`` template library::
See `DEP 182`_ for more information.
+.. _template-debug-integration:
+
+Debug integration for custom engines
+------------------------------------
+
+.. versionadded:: 1.9
+
+ Debug page integration for non-Django template engines was added.
+
+The Django debug page has hooks to provide detailed information when a template
+error arises. Custom template engines can use these hooks to enhance the
+traceback information that appears to users. The following hooks are available:
+
+.. _template-postmortem:
+
+Template postmortem
+~~~~~~~~~~~~~~~~~~~
+
+The postmortem appears when :exc:`~django.template.TemplateDoesNotExist` is
+raised. It lists the template engines and loaders that were used when trying
+to find a given template. For example, if two Django engines are configured,
+the postmortem will appear like:
+
+.. image:: _images/postmortem.png
+
+Custom engines can populate the postmortem by passing the ``backend`` and
+``tried`` arguments when raising :exc:`~django.template.TemplateDoesNotExist`.
+Backends that use the postmortem :ref:`should specify an origin
+` on the template object.
+
+Contextual line information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If an error happens during template parsing or rendering, Django can display
+the line the error happened on. For example:
+
+.. image:: _images/template-lines.png
+
+Custom engines can populate this information by setting a ``template_debug``
+attribute on exceptions raised during parsing and rendering. This attribute
+is a :class:`dict` with the following values:
+
+* ``'name'``: The name of the template in which the exception occurred.
+
+* ``'message'``: The exception message.
+
+* ``'source_lines'``: The lines before, after, and including the line the
+ exception occurred on. This is for context, so it shouldn't contain more than
+ 20 lines or so.
+
+* ``'line'``: The line number on which the exception occurred.
+
+* ``'before'``: The content on the error line before the token that raised the
+ error.
+
+* ``'during'``: The token that raised the error.
+
+* ``'after'``: The content on the error line after the token that raised the
+ error.
+
+* ``'total'``: The number of lines in ``source_lines``.
+
+* ``'top'``: The line number where ``source_lines`` starts.
+
+* ``'bottom'``: The line number where ``source_lines`` ends.
+
+Given the above template error, ``template_debug`` would look like::
+
+ {
+ 'name': '/path/to/template.html',
+ 'message': "Invalid block tag: 'syntax'",
+ 'source_lines': [
+ (1, 'some\n'),
+ (2, 'lines\n'),
+ (3, 'before\n'),
+ (4, 'Hello {% syntax error %} {{ world }}\n'),
+ (5, 'some\n'),
+ (6, 'lines\n'),
+ (7, 'after\n'),
+ (8, ''),
+ ],
+ 'line': 4,
+ 'before': 'Hello ',
+ 'during': '{% syntax error %}',
+ 'after': ' {{ world }}\n',
+ 'total': 9,
+ 'bottom': 9,
+ 'top': 1,
+ }
+
+.. _template-origin-api:
+
+Origin API and 3rd-party integration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Django templates have an :class:`~django.template.base.Origin` object available
+through the ``template.origin`` attribute. This enables debug information to be
+displayed in the :ref:`template postmortem `, as well as
+in 3rd-party libraries, like the `Django Debug Toolbar`_.
+
+Custom engines can provide their own ``template.origin`` information by
+creating an object that specifies the following attributes:
+
+* ``'name'``: The full path to the template.
+
+* ``'template_name'``: The relative path to the template as passed into the
+ the template loading methods.
+
+* ``'loader_name'``: An optional string identifying the function or class used
+ to load the template, e.g. ``django.template.loaders.filesystem.Loader``.
+
.. currentmodule:: django.template
.. _template-language-intro:
@@ -687,3 +818,4 @@ Implementing a custom context processor is as simple as defining a function.
.. _Jinja2: http://jinja.pocoo.org/
.. _DEP 182: https://github.com/django/deps/blob/master/accepted/0182-multiple-template-engines.rst
+.. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar
diff --git a/tests/template_backends/jinja2/template_backends/syntax_error2.html b/tests/template_backends/jinja2/template_backends/syntax_error2.html
new file mode 100644
index 0000000000..7b268bd30c
--- /dev/null
+++ b/tests/template_backends/jinja2/template_backends/syntax_error2.html
@@ -0,0 +1,31 @@
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+{% block %}
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
diff --git a/tests/template_backends/test_dummy.py b/tests/template_backends/test_dummy.py
index b529b70756..f168bede1e 100644
--- a/tests/template_backends/test_dummy.py
+++ b/tests/template_backends/test_dummy.py
@@ -37,8 +37,9 @@ class TemplateStringsTests(SimpleTestCase):
self.assertEqual(content, "Hello world!\n")
def test_get_template_non_existing(self):
- with self.assertRaises(TemplateDoesNotExist):
+ with self.assertRaises(TemplateDoesNotExist) as e:
self.engine.get_template('template_backends/non_existing.html')
+ self.assertEqual(e.exception.backend, self.engine)
def test_get_template_syntax_error(self):
# There's no way to trigger a syntax error with the dummy backend.
diff --git a/tests/template_backends/test_jinja2.py b/tests/template_backends/test_jinja2.py
index d2a52d900c..6ab49d2b8a 100644
--- a/tests/template_backends/test_jinja2.py
+++ b/tests/template_backends/test_jinja2.py
@@ -4,6 +4,8 @@ from __future__ import absolute_import
from unittest import skipIf
+from django.template import TemplateSyntaxError
+
from .test_dummy import TemplateStringsTests
try:
@@ -22,6 +24,16 @@ class Jinja2Tests(TemplateStringsTests):
backend_name = 'jinja2'
options = {'keep_trailing_newline': True}
+ def test_origin(self):
+ template = self.engine.get_template('template_backends/hello.html')
+ self.assertTrue(template.origin.name.endswith('hello.html'))
+ self.assertEqual(template.origin.template_name, 'template_backends/hello.html')
+
+ def test_origin_from_string(self):
+ template = self.engine.from_string('Hello!\n')
+ self.assertEqual(template.origin.name, '')
+ self.assertEqual(template.origin.template_name, None)
+
def test_self_context(self):
"""
Using 'self' in the context should not throw errors (#24538).
@@ -32,3 +44,33 @@ class Jinja2Tests(TemplateStringsTests):
template = self.engine.from_string('hello {{ foo }}!')
content = template.render(context={'self': 'self', 'foo': 'world'})
self.assertEqual(content, 'hello world!')
+
+ def test_exception_debug_info_min_context(self):
+ with self.assertRaises(TemplateSyntaxError) as e:
+ self.engine.get_template('template_backends/syntax_error.html')
+ debug = e.exception.template_debug
+ self.assertEqual(debug['after'], '')
+ self.assertEqual(debug['before'], '')
+ self.assertEqual(debug['during'], '{% block %}')
+ self.assertEqual(debug['bottom'], 1)
+ self.assertEqual(debug['top'], 0)
+ self.assertEqual(debug['line'], 1)
+ self.assertEqual(debug['total'], 1)
+ self.assertEqual(len(debug['source_lines']), 1)
+ self.assertTrue(debug['name'].endswith('syntax_error.html'))
+ self.assertTrue('message' in debug)
+
+ def test_exception_debug_info_max_context(self):
+ with self.assertRaises(TemplateSyntaxError) as e:
+ self.engine.get_template('template_backends/syntax_error2.html')
+ debug = e.exception.template_debug
+ self.assertEqual(debug['after'], '')
+ self.assertEqual(debug['before'], '')
+ self.assertEqual(debug['during'], '{% block %}')
+ self.assertEqual(debug['bottom'], 26)
+ self.assertEqual(debug['top'], 5)
+ self.assertEqual(debug['line'], 16)
+ self.assertEqual(debug['total'], 31)
+ self.assertEqual(len(debug['source_lines']), 21)
+ self.assertTrue(debug['name'].endswith('syntax_error2.html'))
+ self.assertTrue('message' in debug)
diff --git a/tests/template_loader/tests.py b/tests/template_loader/tests.py
index c698c0a398..ba712e521b 100644
--- a/tests/template_loader/tests.py
+++ b/tests/template_loader/tests.py
@@ -36,9 +36,10 @@ class TemplateLoaderTests(SimpleTestCase):
with self.assertRaises(TemplateDoesNotExist) as e:
get_template("template_loader/unknown.html")
self.assertEqual(
- e.exception.tried[-1][0].template_name,
+ e.exception.chain[-1].tried[0][0].template_name,
'template_loader/unknown.html',
)
+ self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_select_template_first_engine(self):
template = select_template(["template_loader/unknown.html",
@@ -64,13 +65,15 @@ class TemplateLoaderTests(SimpleTestCase):
select_template(["template_loader/unknown.html",
"template_loader/missing.html"])
self.assertEqual(
- e.exception.tried[0][0].template_name,
+ e.exception.chain[0].tried[0][0].template_name,
'template_loader/unknown.html',
)
+ self.assertEqual(e.exception.chain[0].backend.name, 'dummy')
self.assertEqual(
- e.exception.tried[-1][0].template_name,
+ e.exception.chain[-1].tried[0][0].template_name,
'template_loader/missing.html',
)
+ self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_select_template_tries_all_engines_before_names(self):
template = select_template(["template_loader/goodbye.html",
@@ -98,9 +101,10 @@ class TemplateLoaderTests(SimpleTestCase):
with self.assertRaises(TemplateDoesNotExist) as e:
render_to_string("template_loader/unknown.html")
self.assertEqual(
- e.exception.tried[-1][0].template_name,
+ e.exception.chain[-1].tried[0][0].template_name,
'template_loader/unknown.html',
)
+ self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_render_to_string_with_list_first_engine(self):
content = render_to_string(["template_loader/unknown.html",
@@ -126,13 +130,25 @@ class TemplateLoaderTests(SimpleTestCase):
render_to_string(["template_loader/unknown.html",
"template_loader/missing.html"])
self.assertEqual(
- e.exception.tried[0][0].template_name,
+ e.exception.chain[0].tried[0][0].template_name,
'template_loader/unknown.html',
)
+ self.assertEqual(e.exception.chain[0].backend.name, 'dummy')
self.assertEqual(
- e.exception.tried[-1][0].template_name,
+ e.exception.chain[1].tried[0][0].template_name,
+ 'template_loader/unknown.html',
+ )
+ self.assertEqual(e.exception.chain[1].backend.name, 'django')
+ self.assertEqual(
+ e.exception.chain[2].tried[0][0].template_name,
'template_loader/missing.html',
)
+ self.assertEqual(e.exception.chain[2].backend.name, 'dummy')
+ self.assertEqual(
+ e.exception.chain[3].tried[0][0].template_name,
+ 'template_loader/missing.html',
+ )
+ self.assertEqual(e.exception.chain[3].backend.name, 'django')
def test_render_to_string_with_list_tries_all_engines_before_names(self):
content = render_to_string(["template_loader/goodbye.html",