mirror of
https://github.com/django/django.git
synced 2025-01-03 15:06:09 +00:00
Fixes #23643 -- Added chained exception details to debug view.
This commit is contained in:
parent
ae87ad005f
commit
8414fcf16b
@ -479,8 +479,29 @@ class ExceptionReporter(object):
|
|||||||
return lower_bound, pre_context, context_line, post_context
|
return lower_bound, pre_context, context_line, post_context
|
||||||
|
|
||||||
def get_traceback_frames(self):
|
def get_traceback_frames(self):
|
||||||
|
def explicit_or_implicit_cause(exc_value):
|
||||||
|
explicit = getattr(exc_value, '__cause__', None)
|
||||||
|
implicit = getattr(exc_value, '__context__', None)
|
||||||
|
return explicit or implicit
|
||||||
|
|
||||||
|
# Get the exception and all its causes
|
||||||
|
exceptions = []
|
||||||
|
exc_value = self.exc_value
|
||||||
|
while exc_value:
|
||||||
|
exceptions.append(exc_value)
|
||||||
|
exc_value = explicit_or_implicit_cause(exc_value)
|
||||||
|
|
||||||
frames = []
|
frames = []
|
||||||
tb = self.tb
|
# No exceptions were supplied to ExceptionReporter
|
||||||
|
if not exceptions:
|
||||||
|
return frames
|
||||||
|
|
||||||
|
# In case there's just one exception (always in Python 2,
|
||||||
|
# sometimes in Python 3), take the traceback from self.tb (Python 2
|
||||||
|
# doesn't have a __traceback__ attribute on Exception)
|
||||||
|
exc_value = exceptions.pop()
|
||||||
|
tb = self.tb if not exceptions else exc_value.__traceback__
|
||||||
|
|
||||||
while tb is not None:
|
while tb is not None:
|
||||||
# Support for __traceback_hide__ which is used by a few libraries
|
# Support for __traceback_hide__ which is used by a few libraries
|
||||||
# to hide internal frames.
|
# to hide internal frames.
|
||||||
@ -497,6 +518,8 @@ class ExceptionReporter(object):
|
|||||||
)
|
)
|
||||||
if pre_context_lineno is not None:
|
if pre_context_lineno is not None:
|
||||||
frames.append({
|
frames.append({
|
||||||
|
'exc_cause': explicit_or_implicit_cause(exc_value),
|
||||||
|
'exc_cause_explicit': getattr(exc_value, '__cause__', True),
|
||||||
'tb': tb,
|
'tb': tb,
|
||||||
'type': 'django' if module_name.startswith('django.') else 'user',
|
'type': 'django' if module_name.startswith('django.') else 'user',
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
@ -509,7 +532,14 @@ class ExceptionReporter(object):
|
|||||||
'post_context': post_context,
|
'post_context': post_context,
|
||||||
'pre_context_lineno': pre_context_lineno + 1,
|
'pre_context_lineno': pre_context_lineno + 1,
|
||||||
})
|
})
|
||||||
tb = tb.tb_next
|
|
||||||
|
# If the traceback for current exception is consumed, try the
|
||||||
|
# other exception.
|
||||||
|
if not tb.tb_next and exceptions:
|
||||||
|
exc_value = exceptions.pop()
|
||||||
|
tb = exc_value.__traceback__
|
||||||
|
else:
|
||||||
|
tb = tb.tb_next
|
||||||
|
|
||||||
return frames
|
return frames
|
||||||
|
|
||||||
@ -838,6 +868,15 @@ TECHNICAL_500_TEMPLATE = ("""
|
|||||||
<div id="browserTraceback">
|
<div id="browserTraceback">
|
||||||
<ul class="traceback">
|
<ul class="traceback">
|
||||||
{% for frame in frames %}
|
{% for frame in frames %}
|
||||||
|
{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
|
||||||
|
<li><h3>
|
||||||
|
{% if frame.exc_cause_explicit %}
|
||||||
|
The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception:
|
||||||
|
{% else %}
|
||||||
|
During handling of the above exception ({{ frame.exc_cause }}), another exception occurred:
|
||||||
|
{% endif %}
|
||||||
|
</h3></li>
|
||||||
|
{% endif %}{% endifchanged %}
|
||||||
<li class="frame {{ frame.type }}">
|
<li class="frame {{ frame.type }}">
|
||||||
<code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code>
|
<code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code>
|
||||||
|
|
||||||
@ -1123,7 +1162,17 @@ In template {{ template_info.name }}, error at line {{ template_info.line }}
|
|||||||
{{ source_line.0 }} : {{ source_line.1 }}
|
{{ source_line.0 }} : {{ source_line.1 }}
|
||||||
{% endifequal %}{% endfor %}{% endif %}{% if frames %}
|
{% endifequal %}{% endfor %}{% endif %}{% if frames %}
|
||||||
Traceback:
|
Traceback:
|
||||||
{% for frame in frames %}File "{{ frame.filename }}" in {{ frame.function }}
|
{% for frame in frames %}
|
||||||
|
{% ifchanged frame.exc_cause %}
|
||||||
|
{% if frame.exc_cause %}
|
||||||
|
{% if frame.exc_cause_explicit %}
|
||||||
|
The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception:
|
||||||
|
{% else %}
|
||||||
|
During handling of the above exception ({{ frame.exc_cause }}), another exception occurred:
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endifchanged %}
|
||||||
|
File "{{ frame.filename }}" in {{ frame.function }}
|
||||||
{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %}
|
{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
|
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
|
||||||
|
@ -172,6 +172,8 @@ Requests and Responses
|
|||||||
``status_code`` outside of the constructor will also modify the value of
|
``status_code`` outside of the constructor will also modify the value of
|
||||||
``reason_phrase``.
|
``reason_phrase``.
|
||||||
|
|
||||||
|
* The debug view now shows details of chained exceptions on Python 3.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst
|
|||||||
install-script = scripts/rpm-install.sh
|
install-script = scripts/rpm-install.sh
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = build,.git,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner
|
exclude = build,.git,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner,tests/view_tests/tests/py3_test_debug.py
|
||||||
ignore = E123,E128,E402,E501,W503,E731,W601
|
ignore = E123,E128,E402,E501,W503,E731,W601
|
||||||
max-line-length = 119
|
max-line-length = 119
|
||||||
|
|
||||||
|
42
tests/view_tests/tests/py3_test_debug.py
Normal file
42
tests/view_tests/tests/py3_test_debug.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Since this file contains Python 3 specific syntax, it's named without a test_
|
||||||
|
prefix so the test runner won't try to import it. Instead, the test class is
|
||||||
|
imported in test_debug.py, but only on Python 3.
|
||||||
|
|
||||||
|
This filename is also in setup.cfg flake8 exclude since the Python 2 syntax
|
||||||
|
error (raise ... from ...) can't be silenced using NOQA.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from django.views.debug import ExceptionReporter
|
||||||
|
|
||||||
|
|
||||||
|
class Py3ExceptionReporterTests(TestCase):
|
||||||
|
|
||||||
|
rf = RequestFactory()
|
||||||
|
|
||||||
|
def test_reporting_of_nested_exceptions(self):
|
||||||
|
request = self.rf.get('/test_view/')
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise AttributeError('Top level')
|
||||||
|
except AttributeError as explicit:
|
||||||
|
try:
|
||||||
|
raise ValueError('Second exception') from explicit
|
||||||
|
except ValueError:
|
||||||
|
raise IndexError('Final exception')
|
||||||
|
except Exception:
|
||||||
|
# Custom exception handler, just pass it into ExceptionReporter
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
|
||||||
|
explicit_exc = 'The above exception ({0}) was the direct cause of the following exception:'
|
||||||
|
implicit_exc = 'During handling of the above exception ({0}), another exception occurred:'
|
||||||
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn(explicit_exc.format("Top level"), html)
|
||||||
|
self.assertIn(implicit_exc.format("Second exception"), html)
|
||||||
|
|
||||||
|
text = reporter.get_traceback_text()
|
||||||
|
self.assertIn(explicit_exc.format("Top level"), text)
|
||||||
|
self.assertIn(implicit_exc.format("Second exception"), text)
|
@ -28,6 +28,9 @@ from ..views import (
|
|||||||
sensitive_kwargs_function_caller, sensitive_method_view, sensitive_view,
|
sensitive_kwargs_function_caller, sensitive_method_view, sensitive_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
from .py3_test_debug import Py3ExceptionReporterTests # NOQA
|
||||||
|
|
||||||
|
|
||||||
class CallableSettingWrapperTests(TestCase):
|
class CallableSettingWrapperTests(TestCase):
|
||||||
""" Unittests for CallableSettingWrapper
|
""" Unittests for CallableSettingWrapper
|
||||||
|
Loading…
Reference in New Issue
Block a user