Skip to content

Commit

Permalink
panels(templates): postpone context processing
Browse files Browse the repository at this point in the history
- this makes it show evaluated querysets which were used in the template
- removes completely context processing when SHOW_TEMPLATE_CONTEXT is
  disabled
  • Loading branch information
nijel committed Sep 19, 2023
1 parent 199c2b3 commit 464c4f6
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 56 deletions.
117 changes: 63 additions & 54 deletions debug_toolbar/panels/templates/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = "<<request>>"
# 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] = "<<sql_queries>>"
# Replace LANGUAGES, which is available in i18n context
# processor
elif key == "LANGUAGES" and isinstance(value, tuple):
temp_layer[key] = "<<languages>>"
# 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] = "<<triggers database query>>"
except UnicodeEncodeError:
temp_layer[key] = "<<Unicode encode error>>"
except Exception:
temp_layer[key] = "<<unhandled exception>>"
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)

Expand Down Expand Up @@ -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] = "<<request>>"
# 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] = "<<sql_queries>>"
# Replace LANGUAGES, which is available in i18n context
# processor
elif key == "LANGUAGES" and isinstance(value, tuple):
temp_layer[key] = "<<languages>>"
# 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] = "<<triggers database query>>"
except UnicodeEncodeError:
temp_layer[key] = "<<Unicode encode error>>"
except Exception:
temp_layer[key] = "<<unhandled exception>>"
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:
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
8 changes: 6 additions & 2 deletions tests/panels/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand All @@ -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': '<<triggers database query>>',\n"
Expand Down Expand Up @@ -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}"],
)

Expand Down

0 comments on commit 464c4f6

Please sign in to comment.