diff options
| author | Preston Timmons <prestontimmons@gmail.com> | 2015-04-24 14:33:03 -0500 |
|---|---|---|
| committer | Preston Timmons <prestontimmons@gmail.com> | 2015-05-06 17:33:47 -0500 |
| commit | adff499e47d99f6b40307acc1ace95508e3c5910 (patch) | |
| tree | bda2771135ba07ad45b5df1fa0ee92588044bbac | |
| parent | d1df1fd2bb274574fd895f6984892b3aba372f48 (diff) | |
Fixed #24119, #24120 -- Formalized debug integration for template backends.
| -rw-r--r-- | django/template/backends/django.py | 30 | ||||
| -rw-r--r-- | django/template/backends/dummy.py | 16 | ||||
| -rw-r--r-- | django/template/backends/jinja2.py | 52 | ||||
| -rw-r--r-- | django/template/base.py | 24 | ||||
| -rw-r--r-- | django/template/loader.py | 18 | ||||
| -rw-r--r-- | django/views/debug.py | 28 | ||||
| -rw-r--r-- | docs/releases/1.9.txt | 3 | ||||
| -rw-r--r-- | docs/topics/_images/postmortem.png | bin | 0 -> 37075 bytes | |||
| -rw-r--r-- | docs/topics/_images/template-lines.png | bin | 0 -> 21537 bytes | |||
| -rw-r--r-- | docs/topics/templates.txt | 142 | ||||
| -rw-r--r-- | tests/template_backends/jinja2/template_backends/syntax_error2.html | 31 | ||||
| -rw-r--r-- | tests/template_backends/test_dummy.py | 3 | ||||
| -rw-r--r-- | tests/template_backends/test_jinja2.py | 42 | ||||
| -rw-r--r-- | tests/template_loader/tests.py | 28 |
14 files changed, 354 insertions, 63 deletions
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 %} <p class="append-bottom">Django tried loading these templates, in this order:</p> {% for entry in postmortem %} - <p class="postmortem-section">Using engine <code>{{ entry.engine.name }}</code>:</p> + <p class="postmortem-section">Using engine <code>{{ entry.backend.name }}</code>:</p> <ul> {% 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 <template-debug-integration>` for custom + template engines was added. + Requests and Responses ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/topics/_images/postmortem.png b/docs/topics/_images/postmortem.png Binary files differnew file mode 100644 index 0000000000..164125ed9d --- /dev/null +++ b/docs/topics/_images/postmortem.png diff --git a/docs/topics/_images/template-lines.png b/docs/topics/_images/template-lines.png Binary files differnew file mode 100644 index 0000000000..c4934049f1 --- /dev/null +++ b/docs/topics/_images/template-lines.png 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 + <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 <template-origin-api>` 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 +<template-origin-api>` 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 <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, '<template>') + 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", |
