From 6b5113ec94e6b9c81e3e4d9a68b0703ccb14c22d Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 11 Jan 2015 10:06:42 +0100 Subject: [PATCH] Made debug views not crash when there isn't a default template engine. --- django/views/debug.py | 53 ++++++++++++++++++---------- tests/view_tests/tests/test_debug.py | 30 ++++++++++++++++ 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index b0392c1a6d..03387efaaf 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -7,12 +7,12 @@ import sys import types from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import resolve, Resolver404 from django.http import (HttpResponse, HttpResponseNotFound, HttpRequest, build_request_repr) -from django.template import Template, Context, TemplateDoesNotExist +from django.template import Context, Engine, TemplateDoesNotExist from django.template.defaultfilters import force_escape, pprint -from django.template.engine import Engine from django.utils.datastructures import MultiValueDict from django.utils.html import escape from django.utils.encoding import force_bytes, smart_text @@ -21,6 +21,11 @@ from django.utils.module_loading import import_string from django.utils import six from django.utils.translation import ugettext as _ + +# Minimal Django templates engine to render the error templates +# regardless of the project's TEMPLATES setting. +DEBUG_ENGINE = Engine(debug=True) + HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE') CLEANSED_SUBSTITUTE = '********************' @@ -275,19 +280,27 @@ class ExceptionReporter(object): def get_traceback_data(self): """Return a dictionary containing traceback information.""" + try: + default_template_engine = Engine.get_default() + except ImproperlyConfigured: + default_template_engine = None - # TODO: handle multiple template engines. - template_engine = Engine.get_default() - + # 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): - 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 = template_engine.template_loaders - except Exception: + if default_template_engine is None: template_loaders = [] + 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: try: source_list_func = loader.get_template_sources @@ -304,8 +317,11 @@ class ExceptionReporter(object): 'loader': loader_name, 'templates': template_list, }) - if (template_engine.debug and - hasattr(self.exc_value, 'django_template_source')): + + # TODO: add support for multiple template engines (#24119). + if (default_template_engine is not None + and default_template_engine.debug + and hasattr(self.exc_value, 'django_template_source')): self.get_template_exception_info() frames = self.get_traceback_frames() @@ -362,13 +378,13 @@ class ExceptionReporter(object): def get_traceback_html(self): "Return HTML version of debug 500 HTTP error page." - t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') + t = DEBUG_ENGINE.from_string(TECHNICAL_500_TEMPLATE) c = Context(self.get_traceback_data(), use_l10n=False) return t.render(c) def get_traceback_text(self): "Return plain text version of debug 500 HTTP error page." - t = Template(TECHNICAL_500_TEXT_TEMPLATE, name='Technical 500 template') + t = DEBUG_ENGINE.from_string(TECHNICAL_500_TEXT_TEMPLATE) c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False) return t.render(c) @@ -545,7 +561,7 @@ def technical_404_response(request, exception): module = obj.__module__ caller = '%s.%s' % (module, caller) - t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 template') + t = DEBUG_ENGINE.from_string(TECHNICAL_404_TEMPLATE) c = Context({ 'urlconf': urlconf, 'root_urlconf': settings.ROOT_URLCONF, @@ -561,8 +577,7 @@ def technical_404_response(request, exception): def default_urlconf(request): "Create an empty URLconf 404 error response." - t = Template(DEFAULT_URLCONF_TEMPLATE, name='Default URLconf template') - + t = DEBUG_ENGINE.from_string(DEFAULT_URLCONF_TEMPLATE) c = Context({ "title": _("Welcome to Django"), "heading": _("It worked!"), diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index c0026fa758..be6ceff1b5 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -219,6 +219,36 @@ class DebugViewTests(TestCase): ) +@override_settings( + DEBUG=True, + ROOT_URLCONF="view_tests.urls", + # No template directories are configured, so no templates will be found. + TEMPLATES=[{ + 'BACKEND': 'django.template.backends.dummy.TemplateStrings', + }], +) +class NonDjangoTemplatesDebugViewTests(TestCase): + + def test_400(self): + # Ensure that when DEBUG=True, technical_500_template() is called. + response = self.client.get('/raises400/') + self.assertContains(response, '