Fixed #32105 -- Added template paths as ExceptionReporter class attributes.

This allows replacement of the debugging templates without having to
copy-paste the `get_traceback_html` and `get_traceback_text` functions
into a subclass.

Thanks to Nick Pope for review.
This commit is contained in:
Aarni Koskela 2020-10-15 11:44:02 +02:00 committed by Carlton Gibson
parent 411cc0ae18
commit 68e33b347d
7 changed files with 50 additions and 3 deletions

View File

@ -245,6 +245,9 @@ class SafeExceptionReporterFilter:
class ExceptionReporter: class ExceptionReporter:
"""Organize and coordinate reporting on exceptions.""" """Organize and coordinate reporting on exceptions."""
html_template_path = CURRENT_DIR / 'templates' / 'technical_500.html'
text_template_path = CURRENT_DIR / 'templates' / 'technical_500.txt'
def __init__(self, request, exc_type, exc_value, tb, is_email=False): def __init__(self, request, exc_type, exc_value, tb, is_email=False):
self.request = request self.request = request
self.filter = get_exception_reporter_filter(self.request) self.filter = get_exception_reporter_filter(self.request)
@ -331,14 +334,14 @@ class ExceptionReporter:
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."""
with Path(CURRENT_DIR, 'templates', 'technical_500.html').open(encoding='utf-8') as fh: with self.html_template_path.open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read()) t = DEBUG_ENGINE.from_string(fh.read())
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."""
with Path(CURRENT_DIR, 'templates', 'technical_500.txt').open(encoding='utf-8') as fh: with self.text_template_path.open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read()) t = DEBUG_ENGINE.from_string(fh.read())
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)

View File

@ -325,6 +325,22 @@ Your custom reporter class needs to inherit from
.. class:: ExceptionReporter .. class:: ExceptionReporter
.. attribute:: html_template_path
.. versionadded:: 3.2
A :class:`pathlib.Path` representing the absolute filesystem path to a
template for rendering the HTML representation of the exception.
Defaults to the Django provided template.
.. attribute:: text_template_path
.. versionadded:: 3.2
A :class:`pathlib.Path` representing the absolute filesystem path to a
template for rendering the plain-text representation of the exception.
Defaults to the Django provided template.
.. method:: get_traceback_data() .. method:: get_traceback_data()
Return a dictionary containing traceback information. Return a dictionary containing traceback information.

View File

@ -188,7 +188,10 @@ Email
Error Reporting Error Reporting
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
* ... * Custom :class:`~django.views.debug.ExceptionReporter` subclasses can now set
the :attr:`~django.views.debug.ExceptionReporter.html_template_path` and
:attr:`~django.views.debug.ExceptionReporter.text_template_path` class
attributes to override the templates used to render exception reports.
File Storage File Storage
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -0,0 +1 @@
<h1>Oh no, an error occurred!</h1>

View File

@ -0,0 +1 @@
Oh dear, an error occurred!

View File

@ -293,6 +293,21 @@ class DebugViewTests(SimpleTestCase):
response = self.client.get('/raises500/') response = self.client.get('/raises500/')
self.assertContains(response, 'custom traceback text', status_code=500) self.assertContains(response, 'custom traceback text', status_code=500)
@override_settings(DEFAULT_EXCEPTION_REPORTER='view_tests.views.TemplateOverrideExceptionReporter')
def test_template_override_exception_reporter(self):
with self.assertLogs('django.request', 'ERROR'):
response = self.client.get('/raises500/')
self.assertContains(
response,
'<h1>Oh no, an error occurred!</h1>',
status_code=500,
html=True,
)
with self.assertLogs('django.request', 'ERROR'):
response = self.client.get('/raises500/', HTTP_ACCEPT='text/plain')
self.assertContains(response, 'Oh dear, an error occurred!', status_code=500)
class DebugViewQueriesAllowedTests(SimpleTestCase): class DebugViewQueriesAllowedTests(SimpleTestCase):
# May need a query to initialize MySQL connection # May need a query to initialize MySQL connection

View File

@ -2,6 +2,7 @@ import datetime
import decimal import decimal
import logging import logging
import sys import sys
from pathlib import Path
from django.core.exceptions import ( from django.core.exceptions import (
BadRequest, PermissionDenied, SuspiciousOperation, BadRequest, PermissionDenied, SuspiciousOperation,
@ -18,6 +19,8 @@ from django.views.decorators.debug import (
sensitive_post_parameters, sensitive_variables, sensitive_post_parameters, sensitive_variables,
) )
TEMPLATES_PATH = Path(__file__).resolve().parent / 'templates'
def index_page(request): def index_page(request):
"""Dummy index page""" """Dummy index page"""
@ -240,6 +243,11 @@ class CustomExceptionReporter(ExceptionReporter):
return self.custom_traceback_text return self.custom_traceback_text
class TemplateOverrideExceptionReporter(ExceptionReporter):
html_template_path = TEMPLATES_PATH / 'my_technical_500.html'
text_template_path = TEMPLATES_PATH / 'my_technical_500.txt'
def custom_reporter_class_view(request): def custom_reporter_class_view(request):
request.exception_reporter_class = CustomExceptionReporter request.exception_reporter_class = CustomExceptionReporter
try: try: