From 83fe8d8888a038197d8dd85379c6a5f1d9b191c7 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 22 Jul 2025 20:46:57 +0200 Subject: [PATCH] Fixes #2159 -- Do not HTML-escape traces in the cache and profiling panel The panel data has to be explicitly reloaded from the store to hit this case. The new ``reload_stats`` utility helps with this. --- .../templates/debug_toolbar/panels/cache.html | 2 +- .../templates/debug_toolbar/panels/profiling.html | 2 +- tests/base.py | 4 ++++ tests/panels/test_cache.py | 3 +++ tests/panels/test_profiling.py | 3 +++ tests/test_store.py | 13 +++++++++++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/debug_toolbar/templates/debug_toolbar/panels/cache.html b/debug_toolbar/templates/debug_toolbar/panels/cache.html index fe882750b..8ffef5917 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/cache.html +++ b/debug_toolbar/templates/debug_toolbar/panels/cache.html @@ -61,7 +61,7 @@

{% translate "Calls" %}

-
{{ call.trace }}
+
{{ call.trace|safe }}
{% endfor %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/profiling.html b/debug_toolbar/templates/debug_toolbar/panels/profiling.html index 0c2206a13..422111f79 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/profiling.html +++ b/debug_toolbar/templates/debug_toolbar/panels/profiling.html @@ -20,7 +20,7 @@ {% else %} {% endif %} - {{ call.func_std_string }} + {{ call.func_std_string|safe }} {{ call.cumtime|floatformat:3 }} diff --git a/tests/base.py b/tests/base.py index c2a6f379e..c18d3d1ed 100644 --- a/tests/base.py +++ b/tests/base.py @@ -112,6 +112,10 @@ def assertValidHTML(self, content): msg_parts.append(f" {lines[position[0] - 1]}") raise self.failureException("\n".join(msg_parts)) + def reload_stats(self): + data = self.toolbar.store.panel(self.toolbar.request_id, self.panel_id) + self.panel.load_stats_from_store(data) + class BaseTestCase(BaseMixin, TestCase): pass diff --git a/tests/panels/test_cache.py b/tests/panels/test_cache.py index a016f81f0..05d9341e6 100644 --- a/tests/panels/test_cache.py +++ b/tests/panels/test_cache.py @@ -124,10 +124,13 @@ def test_insert_content(self): # ensure the panel does not have content yet. self.assertNotIn("café", self.panel.content) self.panel.generate_stats(self.request, response) + self.reload_stats() # ensure the panel renders correctly. content = self.panel.content self.assertIn("café", content) self.assertValidHTML(content) + # ensure traces aren't escaped + self.assertIn('', content) def test_generate_server_timing(self): self.assertEqual(len(self.panel.calls), 0) diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index 931a5dbf6..320c657ac 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -35,11 +35,14 @@ def test_insert_content(self): # ensure the panel does not have content yet. self.assertNotIn("regular_view", self.panel.content) self.panel.generate_stats(self.request, response) + self.reload_stats() # ensure the panel renders correctly. content = self.panel.content self.assertIn("regular_view", content) self.assertIn("render", content) self.assertValidHTML(content) + # ensure traces aren't escaped + self.assertIn('', content) @override_settings(DEBUG_TOOLBAR_CONFIG={"PROFILER_THRESHOLD_RATIO": 1}) def test_cum_time_threshold(self): diff --git a/tests/test_store.py b/tests/test_store.py index deb4fe267..db70377c9 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -2,6 +2,7 @@ from django.test import TestCase from django.test.utils import override_settings +from django.utils.safestring import SafeData, mark_safe from debug_toolbar import store @@ -97,6 +98,18 @@ def test_panel(self): self.store.save_panel("bar", "bar.panel", {"a": 1}) self.assertEqual(self.store.panel("bar", "bar.panel"), {"a": 1}) + def test_serialize_safestring(self): + before = {"string": mark_safe("safe")} + + self.store.save_panel("bar", "bar.panel", before) + after = self.store.panel("bar", "bar.panel") + + self.assertFalse(type(before["string"]) is str) + self.assertTrue(isinstance(before["string"], SafeData)) + + self.assertTrue(type(after["string"]) is str) + self.assertFalse(isinstance(after["string"], SafeData)) + class StubStore(store.BaseStore): pass