Made debug views not crash when there isn't a default template engine.

This commit is contained in:
Aymeric Augustin 2015-01-11 10:06:42 +01:00
parent 79deb6a071
commit 6b5113ec94
2 changed files with 64 additions and 19 deletions

View File

@ -7,12 +7,12 @@ import sys
import types import types
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import resolve, Resolver404 from django.core.urlresolvers import resolve, Resolver404
from django.http import (HttpResponse, HttpResponseNotFound, HttpRequest, from django.http import (HttpResponse, HttpResponseNotFound, HttpRequest,
build_request_repr) 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.defaultfilters import force_escape, pprint
from django.template.engine import Engine
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.html import escape from django.utils.html import escape
from django.utils.encoding import force_bytes, smart_text 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 import six
from django.utils.translation import ugettext as _ 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') HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE')
CLEANSED_SUBSTITUTE = '********************' CLEANSED_SUBSTITUTE = '********************'
@ -275,19 +280,27 @@ class ExceptionReporter(object):
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 ImproperlyConfigured:
default_template_engine = None
# TODO: handle multiple template engines. # TODO: add support for multiple template engines (#24120).
template_engine = Engine.get_default() # 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:
template_loaders = []
else:
self.template_does_not_exist = True self.template_does_not_exist = True
self.loader_debug_info = [] self.loader_debug_info = []
# If Django fails in get_template_loaders, provide an empty list # If Django fails in get_template_loaders, provide an empty list
# for the following loop to not fail. # for the following loop to not fail.
try: try:
template_loaders = template_engine.template_loaders template_loaders = default_template_engine.template_loaders
except Exception: except Exception:
template_loaders = [] template_loaders = []
for loader in template_loaders: for loader in template_loaders:
try: try:
source_list_func = loader.get_template_sources source_list_func = loader.get_template_sources
@ -304,8 +317,11 @@ class ExceptionReporter(object):
'loader': loader_name, 'loader': loader_name,
'templates': template_list, '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() self.get_template_exception_info()
frames = self.get_traceback_frames() frames = self.get_traceback_frames()
@ -362,13 +378,13 @@ class ExceptionReporter(object):
def get_traceback_html(self): def get_traceback_html(self):
"Return HTML version of debug 500 HTTP error page." "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) c = Context(self.get_traceback_data(), use_l10n=False)
return t.render(c) return t.render(c)
def get_traceback_text(self): def get_traceback_text(self):
"Return plain text version of debug 500 HTTP error page." "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) c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
return t.render(c) return t.render(c)
@ -545,7 +561,7 @@ def technical_404_response(request, exception):
module = obj.__module__ module = obj.__module__
caller = '%s.%s' % (module, caller) caller = '%s.%s' % (module, caller)
t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 template') t = DEBUG_ENGINE.from_string(TECHNICAL_404_TEMPLATE)
c = Context({ c = Context({
'urlconf': urlconf, 'urlconf': urlconf,
'root_urlconf': settings.ROOT_URLCONF, 'root_urlconf': settings.ROOT_URLCONF,
@ -561,8 +577,7 @@ def technical_404_response(request, exception):
def default_urlconf(request): def default_urlconf(request):
"Create an empty URLconf 404 error response." "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({ c = Context({
"title": _("Welcome to Django"), "title": _("Welcome to Django"),
"heading": _("It worked!"), "heading": _("It worked!"),

View File

@ -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, '<div class="context" id="', status_code=400)
def test_403(self):
response = self.client.get('/raises403/')
self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
def test_404(self):
response = self.client.get('/raises404/')
self.assertEqual(response.status_code, 404)
def test_template_not_found_error(self):
# Raises a TemplateDoesNotExist exception and shows the debug view.
url = reverse('raises_template_does_not_exist', kwargs={"path": "notfound.html"})
response = self.client.get(url)
self.assertContains(response, '<div class="context" id="', status_code=500)
class ExceptionReporterTests(TestCase): class ExceptionReporterTests(TestCase):
rf = RequestFactory() rf = RequestFactory()