Repository: django/django
Issue category: Bug Report

Issue:
Template system: escape() calls in get_exception_info() should be removed
Description
	 
​Here there are some calls to escape()
They shouldn't be there: escaping happens in templates for non-safe strings anyway, so there's no need.
And there _is_ a drawback: as an example, the Python Sentry SDK ​copies this info, but because it gets sent over the wire (as a JSON string) the information that this has already been escaped is lost, and on the receiving end it is escaped again.
This means that on the server-side ​the Error-tracking, in my case Bugsink will show doubly escaped html code snippets. This looks something like this:
&lt;p class=&quot;relative text-slate-600 text-base md:text-xl mb-4 md:mb-5&quot;&gt; 
Removing the calls to escape simply solves this. Which makes sense: calling escape is simply not the responsibility of this piece of code, it should just stay marked as unsafe and be escape at the edges (on rendering).


Hints:
Given #33461 added force_escape to message, we might need to move that escaping into the template
1731045354

Patch:
diff --git a/django/template/base.py b/django/template/base.py
index b974495c9c92..eaca428b10c4 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -57,7 +57,7 @@
 
 from django.template.context import BaseContext
 from django.utils.formats import localize
-from django.utils.html import conditional_escape, escape
+from django.utils.html import conditional_escape
 from django.utils.regex_helper import _lazy_re_compile
 from django.utils.safestring import SafeData, SafeString, mark_safe
 from django.utils.text import get_text_list, smart_split, unescape_string_literal
@@ -247,10 +247,10 @@ def get_exception_info(self, exception, token):
         for num, next in enumerate(linebreak_iter(self.source)):
             if start >= upto and end <= next:
                 line = num
-                before = escape(self.source[upto:start])
-                during = escape(self.source[start:end])
-                after = escape(self.source[end:next])
-            source_lines.append((num, escape(self.source[upto:next])))
+                before = self.source[upto:start]
+                during = self.source[start:end]
+                after = self.source[end:next]
+            source_lines.append((num, self.source[upto:next]))
             upto = next
         total = len(source_lines)
 


Test patch:
diff --git a/tests/template_tests/templates/test_extends_block_error.html b/tests/template_tests/templates/test_extends_block_error.html
index c4733747a237..8133c93ccd31 100644
--- a/tests/template_tests/templates/test_extends_block_error.html
+++ b/tests/template_tests/templates/test_extends_block_error.html
@@ -1,2 +1,2 @@
 {% extends "test_extends_block_error_parent.html" %}
-{% block content %}{% include "missing.html" %}{% endblock %}
+{% block content %}{% include "index.html" %}{% include "missing.html" %}{% include "index.html" %}{% endblock %}
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index 14df81669b12..7364c7ca6462 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -5,7 +5,6 @@
 from django.test import SimpleTestCase, override_settings
 from django.urls import NoReverseMatch
 from django.utils import translation
-from django.utils.html import escape
 
 
 class TemplateTestMixin:
@@ -157,10 +156,32 @@ def test_render_tag_error_in_extended_block(self):
         with self.assertRaises(TemplateDoesNotExist) as cm:
             template.render(context)
         if self.debug_engine:
+            self.assertEqual(
+                cm.exception.template_debug["before"],
+                '{% block content %}{% include "index.html" %}',
+            )
             self.assertEqual(
                 cm.exception.template_debug["during"],
-                escape('{% include "missing.html" %}'),
+                '{% include "missing.html" %}',
+            )
+            self.assertEqual(
+                cm.exception.template_debug["after"],
+                '{% include "index.html" %}{% endblock %}\n',
+            )
+            self.assertEqual(
+                cm.exception.template_debug["source_lines"][0],
+                (1, '{% extends "test_extends_block_error_parent.html" %}\n'),
+            )
+            self.assertEqual(
+                cm.exception.template_debug["source_lines"][1],
+                (
+                    2,
+                    '{% block content %}{% include "index.html" %}'
+                    '{% include "missing.html" %}'
+                    '{% include "index.html" %}{% endblock %}\n',
+                ),
             )
+            self.assertEqual(cm.exception.template_debug["source_lines"][2], (3, ""))
 
     def test_super_errors(self):
         """