Loading django/views/debug.py +52 −3 Original line number Diff line number Diff line Loading @@ -479,8 +479,29 @@ class ExceptionReporter(object): return lower_bound, pre_context, context_line, post_context 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 = [] 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: # Support for __traceback_hide__ which is used by a few libraries # to hide internal frames. Loading @@ -497,6 +518,8 @@ class ExceptionReporter(object): ) if pre_context_lineno is not None: frames.append({ 'exc_cause': explicit_or_implicit_cause(exc_value), 'exc_cause_explicit': getattr(exc_value, '__cause__', True), 'tb': tb, 'type': 'django' if module_name.startswith('django.') else 'user', 'filename': filename, Loading @@ -509,6 +532,13 @@ class ExceptionReporter(object): 'post_context': post_context, 'pre_context_lineno': pre_context_lineno + 1, }) # 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 Loading Loading @@ -838,6 +868,15 @@ TECHNICAL_500_TEMPLATE = (""" <div id="browserTraceback"> <ul class="traceback"> {% 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 }}"> <code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code> Loading Loading @@ -1123,7 +1162,17 @@ In template {{ template_info.name }}, error at line {{ template_info.line }} {{ source_line.0 }} : {{ source_line.1 }} {% endifequal %}{% endfor %}{% endif %}{% if frames %} 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 %} {% endfor %} {% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %} Loading docs/releases/1.9.txt +2 −0 Original line number Diff line number Diff line Loading @@ -172,6 +172,8 @@ Requests and Responses ``status_code`` outside of the constructor will also modify the value of ``reason_phrase``. * The debug view now shows details of chained exceptions on Python 3. Tests ^^^^^ Loading setup.cfg +1 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst install-script = scripts/rpm-install.sh [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 max-line-length = 119 Loading tests/view_tests/tests/py3_test_debug.py 0 → 100644 +42 −0 Original line number Diff line number Diff line """ 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) tests/view_tests/tests/test_debug.py +3 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,9 @@ from ..views import ( sensitive_kwargs_function_caller, sensitive_method_view, sensitive_view, ) if six.PY3: from .py3_test_debug import Py3ExceptionReporterTests # NOQA class CallableSettingWrapperTests(TestCase): """ Unittests for CallableSettingWrapper Loading Loading
django/views/debug.py +52 −3 Original line number Diff line number Diff line Loading @@ -479,8 +479,29 @@ class ExceptionReporter(object): return lower_bound, pre_context, context_line, post_context 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 = [] 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: # Support for __traceback_hide__ which is used by a few libraries # to hide internal frames. Loading @@ -497,6 +518,8 @@ class ExceptionReporter(object): ) if pre_context_lineno is not None: frames.append({ 'exc_cause': explicit_or_implicit_cause(exc_value), 'exc_cause_explicit': getattr(exc_value, '__cause__', True), 'tb': tb, 'type': 'django' if module_name.startswith('django.') else 'user', 'filename': filename, Loading @@ -509,6 +532,13 @@ class ExceptionReporter(object): 'post_context': post_context, 'pre_context_lineno': pre_context_lineno + 1, }) # 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 Loading Loading @@ -838,6 +868,15 @@ TECHNICAL_500_TEMPLATE = (""" <div id="browserTraceback"> <ul class="traceback"> {% 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 }}"> <code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code> Loading Loading @@ -1123,7 +1162,17 @@ In template {{ template_info.name }}, error at line {{ template_info.line }} {{ source_line.0 }} : {{ source_line.1 }} {% endifequal %}{% endfor %}{% endif %}{% if frames %} 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 %} {% endfor %} {% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %} Loading
docs/releases/1.9.txt +2 −0 Original line number Diff line number Diff line Loading @@ -172,6 +172,8 @@ Requests and Responses ``status_code`` outside of the constructor will also modify the value of ``reason_phrase``. * The debug view now shows details of chained exceptions on Python 3. Tests ^^^^^ Loading
setup.cfg +1 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst install-script = scripts/rpm-install.sh [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 max-line-length = 119 Loading
tests/view_tests/tests/py3_test_debug.py 0 → 100644 +42 −0 Original line number Diff line number Diff line """ 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)
tests/view_tests/tests/test_debug.py +3 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,9 @@ from ..views import ( sensitive_kwargs_function_caller, sensitive_method_view, sensitive_view, ) if six.PY3: from .py3_test_debug import Py3ExceptionReporterTests # NOQA class CallableSettingWrapperTests(TestCase): """ Unittests for CallableSettingWrapper Loading