Skip to content

Commit

Permalink
Refs #910 - After Django introduced template based widget rendering, …
Browse files Browse the repository at this point in the history
…the number of context values which need prettifying has increased drastically, which can (given enough template contexts to render) cause pages using the template panel to become essentially unresponsive due to the sheer amount of data.

Instead, when first seeing a new context layer, keep a copy of it + the formatted version, and when subsequently seeing the exact same layer (by
relying on the implicit __eq__ calls it is expected the lists's __contains__ will do) re-use the existing formatted version.

This cuts the number of pformat calls at a minimum by half, and ultimately (far) more than that due to re-use of the same one where possible.
  • Loading branch information
kezabelle committed Apr 24, 2017
1 parent e02f132 commit e0b6f88
Showing 1 changed file with 69 additions and 40 deletions.
109 changes: 69 additions & 40 deletions debug_toolbar/panels/templates/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ class TemplatesPanel(Panel):
def __init__(self, *args, **kwargs):
super(TemplatesPanel, self).__init__(*args, **kwargs)
self.templates = []
# Refs GitHub issue #910
# Hold a series of seen dictionaries within Contexts. A dictionary is
# considered seen if it is `in` this list, requiring that the __eq__
# for the dictionary matches. If *anything* in the dictionary is
# different it is counted as a new layer.
self.seen_layers = []
# Holds all dictionaries which have been prettified for output.
# This should align with the seen_layers such that an index here is
# the same as the index there.
self.pformat_layers = []

def _store_template_info(self, sender, **kwargs):
template, context = kwargs['template'], kwargs['context']
Expand All @@ -80,47 +90,66 @@ def _store_template_info(self, sender, **kwargs):

context_list = []
for context_layer in context.dicts:
temp_layer = {}
if hasattr(context_layer, 'items'):
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)):
model_name = "%s.%s" % (
value.model._meta.app_label, value.model.__name__)
temp_layer[key] = '<<%s of %s>>' % (
value.__class__.__name__.lower(), model_name)
else:
try:
recording(False)
pformat(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>>'
if hasattr(context_layer, 'items') and context_layer:
# Refs GitHub issue #910
# If we can find this layer in our pseudo-cache then find the
# matching prettified version in the associated list.
key_values = sorted(context_layer.items())
if key_values in self.seen_layers:
index = self.seen_layers.index(key_values)
pformatted = self.pformat_layers[index]
context_list.append(pformatted)
else:
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)):
model_name = "%s.%s" % (
value.model._meta.app_label, value.model.__name__)
temp_layer[key] = '<<%s of %s>>' % (
value.__class__.__name__.lower(), model_name)
else:
temp_layer[key] = value
finally:
recording(True)
try:
context_list.append(pformat(temp_layer))
except UnicodeEncodeError:
pass

kwargs['context'] = [force_text(item) for item in context_list]
try:
recording(False)
force_text(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:
recording(True)
# Refs GitHub issue #910
# If we've not seen the layer before then we will add it
# so that if we see it again we can skip formatting it.
self.seen_layers.append(key_values)
# Note: this *ought* to be len(...) - 1 but let's be safe.
index = self.seen_layers.index(key_values)
try:
pformatted = force_text(pformat(temp_layer))
except UnicodeEncodeError:
pass
else:
# Note: this *ought* to be len(...) - 1 but let's be safe.
self.pformat_layers.insert(index, pformatted)
context_list.append(pformatted)

kwargs['context'] = context_list
kwargs['context_processors'] = getattr(context, 'context_processors', None)
self.templates.append(kwargs)

Expand Down

0 comments on commit e0b6f88

Please sign in to comment.