Improved display of template loader postmortem on debug page.

This now works for multiple Django engines and recursive loaders.
Support for non-Django engines is still pending.

Refs #15053.
This commit is contained in:
Preston Timmons 2015-03-05 09:52:22 -06:00
parent fc21471526
commit 65a7a0d9ee
2 changed files with 57 additions and 73 deletions

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import re import re
import sys import sys
import types import types
@ -10,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 from django.template import Context, Engine, TemplateDoesNotExist, engines
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
@ -266,61 +265,36 @@ class ExceptionReporter(object):
self.template_info = getattr(self.exc_value, 'template_debug', None) self.template_info = getattr(self.exc_value, 'template_debug', None)
self.template_does_not_exist = False self.template_does_not_exist = False
self.loader_debug_info = None self.postmortem = None
# Handle deprecated string exceptions # Handle deprecated string exceptions
if isinstance(self.exc_type, six.string_types): if isinstance(self.exc_type, six.string_types):
self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type)
self.exc_type = type(self.exc_value) self.exc_type = type(self.exc_value)
def format_path_status(self, path):
if not os.path.exists(path):
return "File does not exist"
return "File exists"
def get_traceback_data(self): def get_traceback_data(self):
"""Return a dictionary containing traceback information.""" """Return a dictionary containing traceback information."""
try:
default_template_engine = Engine.get_default()
except Exception:
# Since the debug view must never crash, catch all exceptions.
# If Django can't find a default template engine, get_default()
# raises ImproperlyConfigured. If some template engines fail to
# load, any exception may be raised.
default_template_engine = None
# TODO: add support for multiple template engines (#24120).
# TemplateDoesNotExist should carry all the information.
# Replaying the search process isn't a good design.
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist): if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
if default_template_engine is None: self.template_does_not_exist = True
template_loaders = [] postmortem = []
else:
self.template_does_not_exist = True
self.loader_debug_info = []
# If Django fails in get_template_loaders, provide an empty list
# for the following loop to not fail.
try:
template_loaders = default_template_engine.template_loaders
except Exception:
template_loaders = []
for loader in template_loaders: # TODO: add support for multiple template engines (#24120).
try: # TemplateDoesNotExist should carry all the information, including
source_list_func = loader.get_template_sources # the backend, rather than looping through engines.all.
# NOTE: This assumes exc_value is the name of the template that for engine in engines.all():
# the loader attempted to load. if hasattr(engine, 'engine'):
template_list = [{ e = engine.engine
'name': t, else:
'status': self.format_path_status(t), e = engine
} for t in source_list_func(str(self.exc_value))]
except AttributeError: postmortem.append(dict(
template_list = [] engine=engine,
loader_name = loader.__module__ + '.' + loader.__class__.__name__ tried=[
self.loader_debug_info.append({ entry for entry in self.exc_value.tried if
'loader': loader_name, entry[0].loader.engine == e
'templates': template_list, ],
}) ))
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):
@ -363,7 +337,7 @@ class ExceptionReporter(object):
'sys_path': sys.path, 'sys_path': sys.path,
'template_info': self.template_info, 'template_info': self.template_info,
'template_does_not_exist': self.template_does_not_exist, 'template_does_not_exist': self.template_does_not_exist,
'loader_debug_info': self.loader_debug_info, 'postmortem': self.postmortem,
} }
# Check whether exception info is available # Check whether exception info is available
if self.exc_type: if self.exc_type:
@ -634,7 +608,8 @@ TECHNICAL_500_TEMPLATE = ("""
#summary h2 { font-weight: normal; color: #666; } #summary h2 { font-weight: normal; color: #666; }
#explanation { background:#eee; } #explanation { background:#eee; }
#template, #template-not-exist { background:#f6f6f6; } #template, #template-not-exist { background:#f6f6f6; }
#template-not-exist ul { margin: 0 0 0 20px; } #template-not-exist ul { margin: 0 0 10px 20px; }
#template-not-exist .postmortem-section { margin-bottom: 3px; }
#unicode-hint { background:#eee; } #unicode-hint { background:#eee; }
#traceback { background:#eee; } #traceback { background:#eee; }
#requestinfo { background:#f6f6f6; padding-left:120px; } #requestinfo { background:#f6f6f6; padding-left:120px; }
@ -646,6 +621,7 @@ TECHNICAL_500_TEMPLATE = ("""
h2 span.commands { font-size:.7em;} h2 span.commands { font-size:.7em;}
span.commands a:link {color:#5E5694;} span.commands a:link {color:#5E5694;}
pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; } pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; }
.append-bottom { margin-bottom: 10px; }
</style> </style>
{% if not is_email %} {% if not is_email %}
<script type="text/javascript"> <script type="text/javascript">
@ -772,19 +748,23 @@ TECHNICAL_500_TEMPLATE = ("""
{% if template_does_not_exist %} {% if template_does_not_exist %}
<div id="template-not-exist"> <div id="template-not-exist">
<h2>Template-loader postmortem</h2> <h2>Template-loader postmortem</h2>
{% if loader_debug_info %} {% if postmortem %}
<p>Django tried loading these templates, in this order:</p> <p class="append-bottom">Django tried loading these templates, in this order:</p>
<ul> {% for entry in postmortem %}
{% for loader in loader_debug_info %} <p class="postmortem-section">Using engine <code>{{ entry.engine.name }}</code>:</p>
<li>Using loader <code>{{ loader.loader }}</code>: <ul>
<ul> {% if entry.tried %}
{% for t in loader.templates %}<li><code>{{ t.name }}</code> ({{ t.status }})</li>{% endfor %} {% for attempt in entry.tried %}
</ul> <li><code>{{ attempt.0.loader_name }}</code>: {{ attempt.0.name }} ({{ attempt.1 }})</li>
</li> {% endfor %}
</ul>
{% else %}
<li>This engine did not provide a list of tried templates.</li>
{% endif %}
</ul>
{% endfor %} {% endfor %}
</ul>
{% else %} {% else %}
<p>Django couldn't find any templates because your <code>'loaders'</code> option is empty!</p> <p>No templates were found because your 'TEMPLATES' setting is not configured.</p>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -907,12 +887,14 @@ Installed Applications:
Installed Middleware: Installed Middleware:
{{ settings.MIDDLEWARE_CLASSES|pprint }} {{ settings.MIDDLEWARE_CLASSES|pprint }}
{% if template_does_not_exist %}Template Loader Error: {% if template_does_not_exist %}Template loader postmortem
{% if loader_debug_info %}Django tried loading these templates, in this order: {% if postmortem %}Django tried loading these templates, in this order:
{% for loader in loader_debug_info %}Using loader {{ loader.loader }}: {% for entry in postmortem %}
{% for t in loader.templates %}{{ t.name }} ({{ t.status }}) Using engine {{ entry.engine.name }}:
{% endfor %}{% endfor %} {% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
{% else %}Django couldn't find any templates because your 'loaders' option is empty! {% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %}
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
{% endif %} {% endif %}
{% endif %}{% if template_info %} {% endif %}{% if template_info %}
Template error: Template error:
@ -1098,12 +1080,14 @@ Installed Applications:
{{ settings.INSTALLED_APPS|pprint }} {{ settings.INSTALLED_APPS|pprint }}
Installed Middleware: Installed Middleware:
{{ settings.MIDDLEWARE_CLASSES|pprint }} {{ settings.MIDDLEWARE_CLASSES|pprint }}
{% if template_does_not_exist %}Template loader Error: {% if template_does_not_exist %}Template loader postmortem
{% if loader_debug_info %}Django tried loading these templates, in this order: {% if postmortem %}Django tried loading these templates, in this order:
{% for loader in loader_debug_info %}Using loader {{ loader.loader }}: {% for entry in postmortem %}
{% for t in loader.templates %}{{ t.name }} ({{ t.status }}) Using engine {{ entry.engine.name }}:
{% endfor %}{% endfor %} {% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
{% else %}Django couldn't find any templates because your 'loaders' option is empty! {% endfor %}{% else %} This engine did not provide a list of tried templates.
{% endif %}{% endfor %}
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
{% endif %} {% endif %}
{% endif %}{% if template_info %} {% endif %}{% if template_info %}
Template error: Template error:

View File

@ -153,7 +153,7 @@ class DebugViewTests(TestCase):
'DIRS': [tempdir], 'DIRS': [tempdir],
}]): }]):
response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name})) response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
self.assertContains(response, "%s (File does not exist)" % template_path, status_code=500, count=1) self.assertContains(response, "%s (Source does not exist)" % template_path, status_code=500, count=2)
def test_no_template_source_loaders(self): def test_no_template_source_loaders(self):
""" """