From e0b6f888a677336586dc33f091a04e2e8d8adba6 Mon Sep 17 00:00:00 2001 From: Keryn Knight Date: Mon, 24 Apr 2017 16:31:34 +0100 Subject: [PATCH] Refs #910 - After Django introduced template based widget rendering, 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. --- debug_toolbar/panels/templates/panel.py | 109 +++++++++++++++--------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index b288f020e..1f96e97ee 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -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'] @@ -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] = '<>' - # 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)): - 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] = '<>' - except UnicodeEncodeError: - temp_layer[key] = '<>' - except Exception: - temp_layer[key] = '<>' + 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] = '<>' + # 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)): + 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] = '<>' + except UnicodeEncodeError: + temp_layer[key] = '<>' + except Exception: + temp_layer[key] = '<>' + 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)