From 464c4f606ce6923328e0a4050491f92f0353a932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 19 Sep 2023 12:55:16 +0200 Subject: [PATCH 1/4] panels(templates): postpone context processing - this makes it show evaluated querysets which were used in the template - removes completely context processing when SHOW_TEMPLATE_CONTEXT is disabled --- debug_toolbar/panels/templates/panel.py | 117 +++++++++++++----------- docs/changes.rst | 2 + tests/panels/test_template.py | 8 +- 3 files changed, 71 insertions(+), 56 deletions(-) diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index 72565f016..598c782f9 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -83,58 +83,11 @@ def _store_template_info(self, sender, **kwargs): if is_debug_toolbar_template: return - context_list = [] - for context_layer in context.dicts: - if hasattr(context_layer, "items") and context_layer: - # Check if the layer is in the cache. - pformatted = None - for key_values, _pformatted in self.pformat_layers: - if key_values == context_layer: - pformatted = _pformatted - break - - if pformatted is None: - temp_layer = {} - for key, value in context_layer.items(): - # Replace any request elements - they have a large - # Unicode representation and the request data is - # already made available from the Request panel. - if isinstance(value, http.HttpRequest): - temp_layer[key] = "<>" - # Replace the debugging sql_queries element. The SQL - # data is already made available from the SQL panel. - elif key == "sql_queries" and isinstance(value, list): - temp_layer[key] = "<>" - # Replace LANGUAGES, which is available in i18n context - # processor - elif key == "LANGUAGES" and isinstance(value, tuple): - temp_layer[key] = "<>" - # QuerySet would trigger the database: user can run the - # query from SQL Panel - elif isinstance(value, (QuerySet, RawQuerySet)): - temp_layer[key] = "<<{} of {}>>".format( - value.__class__.__name__.lower(), - value.model._meta.label, - ) - else: - token = allow_sql.set(False) # noqa: FBT003 - try: - saferepr(value) # this MAY trigger a db query - except SQLQueryTriggered: - temp_layer[key] = "<>" - except UnicodeEncodeError: - temp_layer[key] = "<>" - except Exception: - temp_layer[key] = "<>" - else: - temp_layer[key] = value - finally: - allow_sql.reset(token) - pformatted = pformat(temp_layer) - self.pformat_layers.append((context_layer, pformatted)) - context_list.append(pformatted) - - kwargs["context"] = context_list + kwargs["context"] = [ + context_layer + for context_layer in context.dicts + if hasattr(context_layer, "items") and context_layer + ] kwargs["context_processors"] = getattr(context, "context_processors", None) self.templates.append(kwargs) @@ -167,6 +120,59 @@ def enable_instrumentation(self): def disable_instrumentation(self): template_rendered.disconnect(self._store_template_info) + def process_context_list(self, context_layers): + context_list = [] + for context_layer in context_layers: + # Check if the layer is in the cache. + pformatted = None + for key_values, _pformatted in self.pformat_layers: + if key_values == context_layer: + pformatted = _pformatted + break + + if pformatted is None: + temp_layer = {} + for key, value in context_layer.items(): + # Replace any request elements - they have a large + # Unicode representation and the request data is + # already made available from the Request panel. + if isinstance(value, http.HttpRequest): + temp_layer[key] = "<>" + # Replace the debugging sql_queries element. The SQL + # data is already made available from the SQL panel. + elif key == "sql_queries" and isinstance(value, list): + temp_layer[key] = "<>" + # Replace LANGUAGES, which is available in i18n context + # processor + elif key == "LANGUAGES" and isinstance(value, tuple): + temp_layer[key] = "<>" + # QuerySet would trigger the database: user can run the + # query from SQL Panel + elif isinstance(value, (QuerySet, RawQuerySet)): + temp_layer[key] = "<<{} of {}>>".format( + value.__class__.__name__.lower(), + value.model._meta.label, + ) + else: + token = allow_sql.set(False) # noqa: FBT003 + try: + saferepr(value) # this MAY trigger a db query + except SQLQueryTriggered: + temp_layer[key] = "<>" + except UnicodeEncodeError: + temp_layer[key] = "<>" + except Exception: + temp_layer[key] = "<>" + else: + temp_layer[key] = value + finally: + allow_sql.reset(token) + pformatted = pformat(temp_layer) + self.pformat_layers.append((context_layer, pformatted)) + context_list.append(pformatted) + + return context_list + def generate_stats(self, request, response): template_context = [] for template_data in self.templates: @@ -182,8 +188,11 @@ def generate_stats(self, request, response): info["template"] = template # Clean up context for better readability if self.toolbar.config["SHOW_TEMPLATE_CONTEXT"]: - context_list = template_data.get("context", []) - info["context"] = "\n".join(context_list) + if "context_list" not in template_data: + template_data["context_list"] = self.process_context_list( + template_data.get("context", []) + ) + info["context"] = "\n".join(template_data["context_list"]) template_context.append(info) # Fetch context_processors/template_dirs from any template diff --git a/docs/changes.rst b/docs/changes.rst index ad3cab34c..62ccdcc6b 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,6 +5,8 @@ Pending ------- * Removed outdated third-party panels from the list. +* Postponed context process in templates panel to include lazy evaluated + content. 4.2.0 (2023-08-10) ------------------ diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index 37e70cfa5..d9a9b46ef 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -21,6 +21,7 @@ def tearDown(self): super().tearDown() def test_queryset_hook(self): + response = self.panel.process_request(self.request) t = Template("No context variables here!") c = Context( { @@ -29,12 +30,13 @@ def test_queryset_hook(self): } ) t.render(c) + self.panel.generate_stats(self.request, response) # ensure the query was NOT logged self.assertEqual(len(self.sql_panel._queries), 0) self.assertEqual( - self.panel.templates[0]["context"], + self.panel.templates[0]["context_list"], [ "{'False': False, 'None': None, 'True': True}", "{'deep_queryset': '<>',\n" @@ -99,13 +101,15 @@ def test_disabled(self): self.assertFalse(self.panel.enabled) def test_empty_context(self): + response = self.panel.process_request(self.request) t = Template("") c = Context({}) t.render(c) + self.panel.generate_stats(self.request, response) # Includes the builtin context but not the empty one. self.assertEqual( - self.panel.templates[0]["context"], + self.panel.templates[0]["context_list"], ["{'False': False, 'None': None, 'True': True}"], ) From 39f3b6fa9100816a533e478c0c3d5b7f471ec934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 19 Sep 2023 08:12:55 +0200 Subject: [PATCH 2/4] panels(templates): avoid evaluating LazyObject LazyObject is typically used for something expensive to evaluate, so avoid evaluating it just for showing it in the debug toolbar. --- debug_toolbar/panels/templates/panel.py | 7 ++++++- docs/changes.rst | 1 + tests/panels/test_template.py | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index 598c782f9..e63417fa9 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -133,10 +133,15 @@ def process_context_list(self, context_layers): if pformatted is None: temp_layer = {} for key, value in context_layer.items(): + # Do not force evaluating LazyObject + if hasattr(value, "_wrapped"): + # SimpleLazyObject has __repr__ which includes actual value + # if it has been already evaluated + temp_layer[key] = repr(value) # Replace any request elements - they have a large # Unicode representation and the request data is # already made available from the Request panel. - if isinstance(value, http.HttpRequest): + elif isinstance(value, http.HttpRequest): temp_layer[key] = "<>" # Replace the debugging sql_queries element. The SQL # data is already made available from the SQL panel. diff --git a/docs/changes.rst b/docs/changes.rst index 62ccdcc6b..4bfa4e5fc 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -7,6 +7,7 @@ Pending * Removed outdated third-party panels from the list. * Postponed context process in templates panel to include lazy evaluated content. +* Fixed template panel to avoid evaluating ``LazyObject`` when not already evaluated. 4.2.0 (2023-08-10) ------------------ diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index d9a9b46ef..eb23cde31 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User from django.template import Context, RequestContext, Template from django.test import override_settings +from django.utils.functional import SimpleLazyObject from ..base import BaseTestCase, IntegrationTestCase from ..forms import TemplateReprForm @@ -113,6 +114,22 @@ def test_empty_context(self): ["{'False': False, 'None': None, 'True': True}"], ) + def test_lazyobject(self): + response = self.panel.process_request(self.request) + t = Template("") + c = Context({"lazy": SimpleLazyObject(lambda: "lazy_value")}) + t.render(c) + self.panel.generate_stats(self.request, response) + self.assertNotIn("lazy_value", self.panel.content) + + def test_lazyobject_eval(self): + response = self.panel.process_request(self.request) + t = Template("{{lazy}}") + c = Context({"lazy": SimpleLazyObject(lambda: "lazy_value")}) + self.assertEqual(t.render(c), "lazy_value") + self.panel.generate_stats(self.request, response) + self.assertIn("lazy_value", self.panel.content) + @override_settings( DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"] From 5f7b56130bb25afbcbcfd2d3abc65c8ab894e151 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 26 Sep 2023 07:20:22 +0200 Subject: [PATCH 3/4] Update docs/changes.rst --- docs/changes.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 20b43299b..129d3ea06 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -8,7 +8,8 @@ Pending * Avoided the unnecessary work of recursively quoting SQL parameters. * Postponed context process in templates panel to include lazy evaluated content. -* Fixed template panel to avoid evaluating ``LazyObject`` when not already evaluated. +* Fixed template panel to avoid evaluating ``LazyObject`` when not already + evaluated. 4.2.0 (2023-08-10) ------------------ From 2fe9c5d13d11891dfe357a0893449a5da5ee5d1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 05:20:30 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 129d3ea06..ab774136d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -8,7 +8,7 @@ Pending * Avoided the unnecessary work of recursively quoting SQL parameters. * Postponed context process in templates panel to include lazy evaluated content. -* Fixed template panel to avoid evaluating ``LazyObject`` when not already +* Fixed template panel to avoid evaluating ``LazyObject`` when not already evaluated. 4.2.0 (2023-08-10)