1
0
mirror of https://github.com/django/django.git synced 2025-10-24 22:26:08 +00:00

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.
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])

View File

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

View File

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

View File

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

View File

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

View File

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