Fixed #24119, #24120 -- Formalized debug integration for template backends.

This commit is contained in:
Preston Timmons 2015-04-24 14:33:03 -05:00
parent d1df1fd2bb
commit adff499e47
14 changed files with 354 additions and 63 deletions

View File

@ -1,11 +1,14 @@
# Since this package contains a "django" module, this is required on Python 2. # Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import from __future__ import absolute_import
import sys
import warnings import warnings
from django.conf import settings from django.conf import settings
from django.template import TemplateDoesNotExist
from django.template.context import Context, RequestContext, make_context from django.template.context import Context, RequestContext, make_context
from django.template.engine import Engine, _dirs_undefined from django.template.engine import Engine, _dirs_undefined
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from .base import BaseEngine from .base import BaseEngine
@ -24,21 +27,23 @@ class DjangoTemplates(BaseEngine):
self.engine = Engine(self.dirs, self.app_dirs, **options) self.engine = Engine(self.dirs, self.app_dirs, **options)
def from_string(self, template_code): 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): 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): class Template(object):
def __init__(self, template): def __init__(self, template, backend):
self.template = template self.template = template
self.backend = backend
@property @property
def origin(self): def origin(self):
# TODO: define the Origin API. For now simply forwarding to the
# underlying Template preserves backwards-compatibility.
return self.template.origin return self.template.origin
def render(self, context=None, request=None): def render(self, context=None, request=None):
@ -71,4 +76,17 @@ class Template(object):
else: else:
context = make_context(context, request) 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])

View File

@ -1,12 +1,13 @@
# Since this package contains a "django" module, this is required on Python 2. # Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import from __future__ import absolute_import
import errno
import io import io
import string import string
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured 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 django.utils.html import conditional_escape
from .base import BaseEngine from .base import BaseEngine
@ -29,17 +30,24 @@ class TemplateStrings(BaseEngine):
return Template(template_code) return Template(template_code)
def get_template(self, template_name): def get_template(self, template_name):
tried = []
for template_file in self.iter_template_filenames(template_name): for template_file in self.iter_template_filenames(template_name):
try: try:
with io.open(template_file, encoding=settings.FILE_CHARSET) as fp: with io.open(template_file, encoding=settings.FILE_CHARSET) as fp:
template_code = fp.read() template_code = fp.read()
except IOError: except IOError as e:
continue if e.errno == errno.ENOENT:
tried.append((
Origin(template_file, template_name, self),
'Source does not exist',
))
continue
raise
return Template(template_code) return Template(template_code)
else: else:
raise TemplateDoesNotExist(template_name) raise TemplateDoesNotExist(template_name, tried=tried, backend=self)
class Template(string.Template): class Template(string.Template):

View File

@ -41,17 +41,24 @@ class Jinja2(BaseEngine):
try: try:
return Template(self.env.get_template(template_name)) return Template(self.env.get_template(template_name))
except jinja2.TemplateNotFound as exc: except jinja2.TemplateNotFound as exc:
six.reraise(TemplateDoesNotExist, TemplateDoesNotExist(exc.args), six.reraise(
sys.exc_info()[2]) TemplateDoesNotExist,
TemplateDoesNotExist(exc.name, backend=self),
sys.exc_info()[2],
)
except jinja2.TemplateSyntaxError as exc: except jinja2.TemplateSyntaxError as exc:
six.reraise(TemplateSyntaxError, TemplateSyntaxError(exc.args), new = TemplateSyntaxError(exc.args)
sys.exc_info()[2]) new.template_debug = get_exception_info(exc)
six.reraise(TemplateSyntaxError, new, sys.exc_info()[2])
class Template(object): class Template(object):
def __init__(self, template): def __init__(self, template):
self.template = template self.template = template
self.origin = Origin(
name=template.filename, template_name=template.name,
)
def render(self, context=None, request=None): def render(self, context=None, request=None):
if context is None: if context is None:
@ -61,3 +68,40 @@ class Template(object):
context['csrf_input'] = csrf_input_lazy(request) context['csrf_input'] = csrf_input_lazy(request)
context['csrf_token'] = csrf_token_lazy(request) context['csrf_token'] = csrf_token_lazy(request)
return self.template.render(context) 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,
}

View File

@ -135,13 +135,27 @@ class TemplateSyntaxError(Exception):
class TemplateDoesNotExist(Exception): class TemplateDoesNotExist(Exception):
""" """
This exception is used when template loaders are unable to find a The exception used by backends when a template does not exist. Accepts the
template. The tried argument is an optional list of tuples containing following optional arguments:
(origin, status), where origin is an Origin object and status is a string
with the reason the template wasn't found. 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.tried = tried or []
self.chain = chain or []
super(TemplateDoesNotExist, self).__init__(msg) super(TemplateDoesNotExist, self).__init__(msg)

View File

@ -17,7 +17,7 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
Raises TemplateDoesNotExist if no such template exists. Raises TemplateDoesNotExist if no such template exists.
""" """
tried = [] chain = []
engines = _engine_list(using) engines = _engine_list(using)
for engine in engines: for engine in engines:
try: try:
@ -33,9 +33,9 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
else: else:
return engine.get_template(template_name) return engine.get_template(template_name)
except TemplateDoesNotExist as e: 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): 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. Raises TemplateDoesNotExist if no such template exists.
""" """
tried = [] chain = []
engines = _engine_list(using) engines = _engine_list(using)
for template_name in template_name_list: for template_name in template_name_list:
for engine in engines: for engine in engines:
@ -63,10 +63,10 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
else: else:
return engine.get_template(template_name) return engine.get_template(template_name)
except TemplateDoesNotExist as e: except TemplateDoesNotExist as e:
tried.extend(e.tried) chain.append(e)
if template_name_list: if template_name_list:
raise TemplateDoesNotExist(', '.join(template_name_list), tried=tried) raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
else: else:
raise TemplateDoesNotExist("No template names provided") raise TemplateDoesNotExist("No template names provided")
@ -92,7 +92,7 @@ def render_to_string(template_name, context=None,
return template.render(context, request) return template.render(context, request)
else: else:
tried = [] chain = []
# Some deprecated arguments were passed - use the legacy code path # Some deprecated arguments were passed - use the legacy code path
for engine in _engine_list(using): for engine in _engine_list(using):
try: try:
@ -124,13 +124,13 @@ def render_to_string(template_name, context=None,
"method doesn't support the dictionary argument." % "method doesn't support the dictionary argument." %
engine.name, stacklevel=2) engine.name, stacklevel=2)
except TemplateDoesNotExist as e: except TemplateDoesNotExist as e:
tried.extend(e.tried) chain.append(e)
continue continue
if template_name: if template_name:
if isinstance(template_name, (list, tuple)): if isinstance(template_name, (list, tuple)):
template_name = ', '.join(template_name) template_name = ', '.join(template_name)
raise TemplateDoesNotExist(template_name, tried=tried) raise TemplateDoesNotExist(template_name, chain=chain)
else: else:
raise TemplateDoesNotExist("No template names provided") raise TemplateDoesNotExist("No template names provided")

View File

@ -9,7 +9,7 @@ from django.core.urlresolvers import Resolver404, resolve
from django.http import ( from django.http import (
HttpRequest, HttpResponse, HttpResponseNotFound, build_request_repr, 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.template.defaultfilters import force_escape, pprint
from django.utils import lru_cache, six, timezone from django.utils import lru_cache, six, timezone
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -276,25 +276,7 @@ class ExceptionReporter(object):
"""Return a dictionary containing traceback information.""" """Return a dictionary containing traceback information."""
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist): if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
self.template_does_not_exist = True self.template_does_not_exist = True
postmortem = [] self.postmortem = self.exc_value.chain or [self.exc_value]
# 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
frames = self.get_traceback_frames() frames = self.get_traceback_frames()
for i, frame in enumerate(frames): for i, frame in enumerate(frames):
@ -751,7 +733,7 @@ TECHNICAL_500_TEMPLATE = ("""
{% if postmortem %} {% if postmortem %}
<p class="append-bottom">Django tried loading these templates, in this order:</p> <p class="append-bottom">Django tried loading these templates, in this order:</p>
{% for entry in postmortem %} {% 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> <ul>
{% if entry.tried %} {% if entry.tried %}
{% for attempt in entry.tried %} {% for attempt in entry.tried %}
@ -890,7 +872,7 @@ Installed Middleware:
{% if template_does_not_exist %}Template loader postmortem {% if template_does_not_exist %}Template loader postmortem
{% if postmortem %}Django tried loading these templates, in this order: {% if postmortem %}Django tried loading these templates, in this order:
{% for entry in postmortem %} {% 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 }}) {% 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. {% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %} {% endif %}{% endfor %}
@ -1083,7 +1065,7 @@ Installed Middleware:
{% if template_does_not_exist %}Template loader postmortem {% if template_does_not_exist %}Template loader postmortem
{% if postmortem %}Django tried loading these templates, in this order: {% if postmortem %}Django tried loading these templates, in this order:
{% for entry in postmortem %} {% 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 }}) {% 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. {% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %} {% endif %}{% endfor %}

View File

@ -249,6 +249,9 @@ Templates
* The debug page template postmortem now include output from each engine that * The debug page template postmortem now include output from each engine that
is installed. is installed.
* :ref:`Debug page integration <template-debug-integration>` for custom
template engines was added.
Requests and Responses Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -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 If loading a template fails, the following two exceptions, defined in
``django.template``, may be raised: ``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. 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) self.engine = foobar.Engine(**options)
def from_string(self, template_code): def from_string(self, template_code):
try: try:
return Template(self.engine.from_string(template_code)) return Template(self.engine.from_string(template_code))
@ -489,7 +509,7 @@ fictional ``foobar`` template library::
try: try:
return Template(self.engine.get_template(template_name)) return Template(self.engine.get_template(template_name))
except foobar.TemplateNotFound as exc: except foobar.TemplateNotFound as exc:
raise TemplateDoesNotExist(exc.args) raise TemplateDoesNotExist(exc.args, backend=self)
except foobar.TemplateCompilationFailed as exc: except foobar.TemplateCompilationFailed as exc:
raise TemplateSyntaxError(exc.args) raise TemplateSyntaxError(exc.args)
@ -510,6 +530,117 @@ fictional ``foobar`` template library::
See `DEP 182`_ for more information. 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 .. currentmodule:: django.template
.. _template-language-intro: .. _template-language-intro:
@ -687,3 +818,4 @@ Implementing a custom context processor is as simple as defining a function.
.. _Jinja2: http://jinja.pocoo.org/ .. _Jinja2: http://jinja.pocoo.org/
.. _DEP 182: https://github.com/django/deps/blob/master/accepted/0182-multiple-template-engines.rst .. _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

View File

@ -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

View File

@ -37,8 +37,9 @@ class TemplateStringsTests(SimpleTestCase):
self.assertEqual(content, "Hello world!\n") self.assertEqual(content, "Hello world!\n")
def test_get_template_non_existing(self): 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.engine.get_template('template_backends/non_existing.html')
self.assertEqual(e.exception.backend, self.engine)
def test_get_template_syntax_error(self): def test_get_template_syntax_error(self):
# There's no way to trigger a syntax error with the dummy backend. # There's no way to trigger a syntax error with the dummy backend.

View File

@ -4,6 +4,8 @@ from __future__ import absolute_import
from unittest import skipIf from unittest import skipIf
from django.template import TemplateSyntaxError
from .test_dummy import TemplateStringsTests from .test_dummy import TemplateStringsTests
try: try:
@ -22,6 +24,16 @@ class Jinja2Tests(TemplateStringsTests):
backend_name = 'jinja2' backend_name = 'jinja2'
options = {'keep_trailing_newline': True} 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): def test_self_context(self):
""" """
Using 'self' in the context should not throw errors (#24538). Using 'self' in the context should not throw errors (#24538).
@ -32,3 +44,33 @@ class Jinja2Tests(TemplateStringsTests):
template = self.engine.from_string('hello {{ foo }}!') template = self.engine.from_string('hello {{ foo }}!')
content = template.render(context={'self': 'self', 'foo': 'world'}) content = template.render(context={'self': 'self', 'foo': 'world'})
self.assertEqual(content, 'hello 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)

View File

@ -36,9 +36,10 @@ class TemplateLoaderTests(SimpleTestCase):
with self.assertRaises(TemplateDoesNotExist) as e: with self.assertRaises(TemplateDoesNotExist) as e:
get_template("template_loader/unknown.html") get_template("template_loader/unknown.html")
self.assertEqual( self.assertEqual(
e.exception.tried[-1][0].template_name, e.exception.chain[-1].tried[0][0].template_name,
'template_loader/unknown.html', 'template_loader/unknown.html',
) )
self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_select_template_first_engine(self): def test_select_template_first_engine(self):
template = select_template(["template_loader/unknown.html", template = select_template(["template_loader/unknown.html",
@ -64,13 +65,15 @@ class TemplateLoaderTests(SimpleTestCase):
select_template(["template_loader/unknown.html", select_template(["template_loader/unknown.html",
"template_loader/missing.html"]) "template_loader/missing.html"])
self.assertEqual( self.assertEqual(
e.exception.tried[0][0].template_name, e.exception.chain[0].tried[0][0].template_name,
'template_loader/unknown.html', 'template_loader/unknown.html',
) )
self.assertEqual(e.exception.chain[0].backend.name, 'dummy')
self.assertEqual( self.assertEqual(
e.exception.tried[-1][0].template_name, e.exception.chain[-1].tried[0][0].template_name,
'template_loader/missing.html', 'template_loader/missing.html',
) )
self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_select_template_tries_all_engines_before_names(self): def test_select_template_tries_all_engines_before_names(self):
template = select_template(["template_loader/goodbye.html", template = select_template(["template_loader/goodbye.html",
@ -98,9 +101,10 @@ class TemplateLoaderTests(SimpleTestCase):
with self.assertRaises(TemplateDoesNotExist) as e: with self.assertRaises(TemplateDoesNotExist) as e:
render_to_string("template_loader/unknown.html") render_to_string("template_loader/unknown.html")
self.assertEqual( self.assertEqual(
e.exception.tried[-1][0].template_name, e.exception.chain[-1].tried[0][0].template_name,
'template_loader/unknown.html', 'template_loader/unknown.html',
) )
self.assertEqual(e.exception.chain[-1].backend.name, 'django')
def test_render_to_string_with_list_first_engine(self): def test_render_to_string_with_list_first_engine(self):
content = render_to_string(["template_loader/unknown.html", content = render_to_string(["template_loader/unknown.html",
@ -126,13 +130,25 @@ class TemplateLoaderTests(SimpleTestCase):
render_to_string(["template_loader/unknown.html", render_to_string(["template_loader/unknown.html",
"template_loader/missing.html"]) "template_loader/missing.html"])
self.assertEqual( self.assertEqual(
e.exception.tried[0][0].template_name, e.exception.chain[0].tried[0][0].template_name,
'template_loader/unknown.html', 'template_loader/unknown.html',
) )
self.assertEqual(e.exception.chain[0].backend.name, 'dummy')
self.assertEqual( 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', '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): def test_render_to_string_with_list_tries_all_engines_before_names(self):
content = render_to_string(["template_loader/goodbye.html", content = render_to_string(["template_loader/goodbye.html",