Commit 65a7a0d9 authored by Preston Timmons's avatar Preston Timmons
Browse files

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.
parent fc214715
Loading
Loading
Loading
Loading
+57 −73
Original line number Diff line number Diff line
from __future__ import unicode_literals

import os
import re
import sys
import types
@@ -10,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
from django.template import Context, Engine, TemplateDoesNotExist, engines
from django.template.defaultfilters import force_escape, pprint
from django.utils import lru_cache, six, timezone
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_does_not_exist = False
        self.loader_debug_info = None
        self.postmortem = None

        # Handle deprecated string exceptions
        if isinstance(self.exc_type, six.string_types):
            self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type)
            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):
        """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
        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.
        # Replaying the search process isn't a good design.
        if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
            if default_template_engine is None:
                template_loaders = []
            # 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:
                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 = []
                    e = engine

            for loader in template_loaders:
                try:
                    source_list_func = loader.get_template_sources
                    # NOTE: This assumes exc_value is the name of the template that
                    # the loader attempted to load.
                    template_list = [{
                        'name': t,
                        'status': self.format_path_status(t),
                    } for t in source_list_func(str(self.exc_value))]
                except AttributeError:
                    template_list = []
                loader_name = loader.__module__ + '.' + loader.__class__.__name__
                self.loader_debug_info.append({
                    'loader': loader_name,
                    'templates': template_list,
                })
                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()
        for i, frame in enumerate(frames):
@@ -363,7 +337,7 @@ class ExceptionReporter(object):
            'sys_path': sys.path,
            'template_info': self.template_info,
            '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
        if self.exc_type:
@@ -634,7 +608,8 @@ TECHNICAL_500_TEMPLATE = ("""
    #summary h2 { font-weight: normal; color: #666; }
    #explanation { background:#eee; }
    #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; }
    #traceback { background:#eee; }
    #requestinfo { background:#f6f6f6; padding-left:120px; }
@@ -646,6 +621,7 @@ TECHNICAL_500_TEMPLATE = ("""
    h2 span.commands { font-size:.7em;}
    span.commands a:link {color:#5E5694;}
    pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; }
    .append-bottom { margin-bottom: 10px; }
  </style>
  {% if not is_email %}
  <script type="text/javascript">
@@ -772,19 +748,23 @@ TECHNICAL_500_TEMPLATE = ("""
{% if template_does_not_exist %}
<div id="template-not-exist">
    <h2>Template-loader postmortem</h2>
    {% if loader_debug_info %}
        <p>Django tried loading these templates, in this order:</p>
        <ul>
        {% for loader in loader_debug_info %}
            <li>Using loader <code>{{ loader.loader }}</code>:
    {% 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>
            <ul>
                {% for t in loader.templates %}<li><code>{{ t.name }}</code> ({{ t.status }})</li>{% endfor %}
                </ul>
            </li>
                {% if entry.tried %}
                    {% for attempt in entry.tried %}
                        <li><code>{{ attempt.0.loader_name }}</code>: {{ attempt.0.name }} ({{ attempt.1 }})</li>
                    {% endfor %}
                    </ul>
                {% else %}
        <p>Django couldn't find any templates because your <code>'loaders'</code> option is empty!</p>
                    <li>This engine did not provide a list of tried templates.</li>
                {% endif %}
            </ul>
        {% endfor %}
    {% else %}
        <p>No templates were found because your 'TEMPLATES' setting is not configured.</p>
    {% endif %}
</div>
{% endif %}
@@ -907,12 +887,14 @@ Installed Applications:
Installed Middleware:
{{ settings.MIDDLEWARE_CLASSES|pprint }}

{% if template_does_not_exist %}Template Loader Error:
{% if loader_debug_info %}Django tried loading these templates, in this order:
{% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
{% for t in loader.templates %}{{ t.name }} ({{ t.status }})
{% endfor %}{% endfor %}
{% else %}Django couldn't find any templates because your 'loaders' option is empty!
{% 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 }}:
{% 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 %}
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
{% endif %}
{% endif %}{% if template_info %}
Template error:
@@ -1098,12 +1080,14 @@ Installed Applications:
{{ settings.INSTALLED_APPS|pprint }}
Installed Middleware:
{{ settings.MIDDLEWARE_CLASSES|pprint }}
{% if template_does_not_exist %}Template loader Error:
{% if loader_debug_info %}Django tried loading these templates, in this order:
{% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
{% for t in loader.templates %}{{ t.name }} ({{ t.status }})
{% endfor %}{% endfor %}
{% else %}Django couldn't find any templates because your 'loaders' option is empty!
{% 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 }}:
{% 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 %}
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
{% endif %}
{% endif %}{% if template_info %}
Template error:
+1 −1
Original line number Diff line number Diff line
@@ -153,7 +153,7 @@ class DebugViewTests(TestCase):
                'DIRS': [tempdir],
            }]):
                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):
        """