Skip to content

Commit 85b52d2

Browse files
Giebischfelixxm
authored andcommitted
Fixed #33701 -- Added fine-grained error locations to the technical 500 debug page.
1 parent 9d726c7 commit 85b52d2

File tree

6 files changed

+101
-5
lines changed

6 files changed

+101
-5
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ answer newbie questions, and generally made Django that much better:
802802
Rachel Tobin <[email protected]>
803803
Rachel Willmer <http://www.willmer.com/kb/>
804804
Radek Švarz <https://www.svarz.cz/translate/>
805+
Rafael Giebisch <[email protected]>
805806
Raffaele Salmaso <[email protected]>
806807
Rajesh Dhawan <[email protected]>
807808
Ramez Ashraf <[email protected]>

django/views/debug.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import itertools
23
import re
34
import sys
45
import types
@@ -15,7 +16,7 @@
1516
from django.utils.encoding import force_str
1617
from django.utils.module_loading import import_string
1718
from django.utils.regex_helper import _lazy_re_compile
18-
from django.utils.version import get_docs_version
19+
from django.utils.version import PY311, get_docs_version
1920

2021
# Minimal Django templates engine to render the error templates
2122
# regardless of the project's TEMPLATES setting. Templates are
@@ -546,6 +547,24 @@ def get_exception_traceback_frames(self, exc_value, tb):
546547
pre_context = []
547548
context_line = "<source code not available>"
548549
post_context = []
550+
551+
colno = tb_area_colno = ""
552+
if PY311:
553+
_, _, start_column, end_column = next(
554+
itertools.islice(
555+
tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None
556+
)
557+
)
558+
if start_column and end_column:
559+
underline = "^" * (end_column - start_column)
560+
spaces = " " * (start_column + len(str(lineno + 1)) + 2)
561+
colno = f"\n{spaces}{underline}"
562+
tb_area_spaces = " " * (
563+
4
564+
+ start_column
565+
- (len(context_line) - len(context_line.lstrip()))
566+
)
567+
tb_area_colno = f"\n{tb_area_spaces}{underline}"
549568
yield {
550569
"exc_cause": exc_cause,
551570
"exc_cause_explicit": exc_cause_explicit,
@@ -562,6 +581,8 @@ def get_exception_traceback_frames(self, exc_value, tb):
562581
"context_line": context_line,
563582
"post_context": post_context,
564583
"pre_context_lineno": pre_context_lineno + 1,
584+
"colno": colno,
585+
"tb_area_colno": tb_area_colno,
565586
}
566587
tb = tb.tb_next
567588

django/views/templates/technical_500.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ <h2>Traceback{% if not is_email %} <span class="commands"><a href="#" onclick="r
242242
</ol>
243243
{% endif %}
244244
<ol start="{{ frame.lineno }}" class="context-line">
245-
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}</pre>{% if not is_email %} <span></span>{% endif %}</li>
245+
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}{{ frame.colno }}</pre>{% if not is_email %} <span></span>{% endif %}</li>
246246
</ol>
247247
{% if frame.post_context and not is_email %}
248248
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">
@@ -327,7 +327,7 @@ <h2>Local Vars</h2>
327327
{% else %}
328328
During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred:
329329
{% endif %}{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
330-
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}{% endfor %}
330+
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}{% endfor %}
331331

332332
Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
333333
Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ exception_notes }}{% endif %}

django/views/templates/technical_500.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Traceback (most recent call last):
3131
{% for frame in frames %}{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
3232
{% 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 %}
3333
{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
34-
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}
34+
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}
3535
{% endfor %}
3636
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
3737
{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% if exception_notes %}{{ exception_notes }}{% endif %}{% endif %}{% endif %}

docs/releases/4.2.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ Email
161161
Error Reporting
162162
~~~~~~~~~~~~~~~
163163

164-
* The debug page now shows :pep:`exception notes <678>` on Python 3.11+.
164+
* The debug page now shows :pep:`exception notes <678>` and
165+
:pep:`fine-grained error locations <657>` on Python 3.11+.
165166

166167
File Storage
167168
~~~~~~~~~~~~

tests/view_tests/tests/test_debug.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,79 @@ def test_reporting_of_nested_exceptions(self):
766766
self.assertIn(implicit_exc.format("<p>Second exception</p>"), text)
767767
self.assertEqual(3, text.count("<p>Final exception</p>"))
768768

769+
@skipIf(
770+
sys._xoptions.get("no_debug_ranges", False)
771+
or os.environ.get("PYTHONNODEBUGRANGES", False),
772+
"Fine-grained error locations are disabled.",
773+
)
774+
@skipUnless(PY311, "Fine-grained error locations were added in Python 3.11.")
775+
def test_highlight_error_position(self):
776+
request = self.rf.get("/test_view/")
777+
try:
778+
try:
779+
raise AttributeError("Top level")
780+
except AttributeError as explicit:
781+
try:
782+
raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit
783+
except ValueError:
784+
raise IndexError("Final exception")
785+
except Exception:
786+
exc_type, exc_value, tb = sys.exc_info()
787+
788+
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
789+
html = reporter.get_traceback_html()
790+
self.assertIn(
791+
"<pre> raise AttributeError(&quot;Top level&quot;)\n"
792+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
793+
html,
794+
)
795+
self.assertIn(
796+
"<pre> raise ValueError(mark_safe("
797+
"&quot;&lt;p&gt;2nd exception&lt;/p&gt;&quot;)) from explicit\n"
798+
" "
799+
"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
800+
html,
801+
)
802+
self.assertIn(
803+
"<pre> raise IndexError(&quot;Final exception&quot;)\n"
804+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
805+
html,
806+
)
807+
# Pastebin.
808+
self.assertIn(
809+
" raise AttributeError(&quot;Top level&quot;)\n"
810+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
811+
html,
812+
)
813+
self.assertIn(
814+
" raise ValueError(mark_safe("
815+
"&quot;&lt;p&gt;2nd exception&lt;/p&gt;&quot;)) from explicit\n"
816+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
817+
html,
818+
)
819+
self.assertIn(
820+
" raise IndexError(&quot;Final exception&quot;)\n"
821+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
822+
html,
823+
)
824+
# Text traceback.
825+
text = reporter.get_traceback_text()
826+
self.assertIn(
827+
' raise AttributeError("Top level")\n'
828+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
829+
text,
830+
)
831+
self.assertIn(
832+
' raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit\n'
833+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
834+
text,
835+
)
836+
self.assertIn(
837+
' raise IndexError("Final exception")\n'
838+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
839+
text,
840+
)
841+
769842
def test_reporting_frames_without_source(self):
770843
try:
771844
source = "def funcName():\n raise Error('Whoops')\nfuncName()"

0 commit comments

Comments
 (0)