@@ -70,10 +70,11 @@ def __init__(self, *args, **kwargs):
7070 super (TemplatesPanel , self ).__init__ (* args , ** kwargs )
7171 self .templates = []
7272 # Refs GitHub issue #910
73- # Holds a collection of unique context layers, keyed by a string
74- # version of them, with the value holding the `pformat` output
75- # of the original dictionary. See _store_template_info.
76- self .pformats = {}
73+ # Holds a collection of unique contexts, keyed by the id()
74+ # of them, with the value holding a list of `pformat` output
75+ # of the original layers. See _store_template_info.
76+ self .seen_contexts = {}
77+
7778
7879 def _store_template_info (self , sender , ** kwargs ):
7980 template , context = kwargs ['template' ], kwargs ['context' ]
@@ -83,63 +84,65 @@ def _store_template_info(self, sender, **kwargs):
8384 template .name .startswith ('debug_toolbar/' )):
8485 return
8586
86- context_list = []
87- for context_layer in context .dicts :
88- temp_layer = {}
89- if hasattr (context_layer , 'items' ):
90- for key , value in context_layer .items ():
91- # Replace any request elements - they have a large
92- # unicode representation and the request data is
93- # already made available from the Request panel.
94- if isinstance (value , http .HttpRequest ):
95- temp_layer [key ] = '<<request>>'
96- # Replace the debugging sql_queries element. The SQL
97- # data is already made available from the SQL panel.
98- elif key == 'sql_queries' and isinstance (value , list ):
99- temp_layer [key ] = '<<sql_queries>>'
100- # Replace LANGUAGES, which is available in i18n context processor
101- elif key == 'LANGUAGES' and isinstance (value , tuple ):
102- temp_layer [key ] = '<<languages>>'
103- # QuerySet would trigger the database: user can run the query from SQL Panel
104- elif isinstance (value , (QuerySet , RawQuerySet )):
105- model_name = "%s.%s" % (
106- value .model ._meta .app_label , value .model .__name__ )
107- temp_layer [key ] = '<<%s of %s>>' % (
108- value .__class__ .__name__ .lower (), model_name )
109- else :
110- try :
111- recording (False )
112- force_text (value ) # this MAY trigger a db query
113- except SQLQueryTriggered :
114- temp_layer [key ] = '<<triggers database query>>'
115- except UnicodeEncodeError :
116- temp_layer [key ] = '<<unicode encode error>>'
117- except Exception :
118- temp_layer [key ] = '<<unhandled exception>>'
87+ # Refs #910
88+ # The same Context instance may be passed around a lot, so if we've
89+ # seen it before, just re-use the previous output.
90+ context_id = id (context )
91+ if context_id in self .seen_contexts :
92+ context_list = self .seen_contexts [context_id ]
93+ else :
94+ context_list = []
95+ for context_layer in context .dicts :
96+ temp_layer = {}
97+ if hasattr (context_layer , 'items' ):
98+ for key , value in context_layer .items ():
99+ # Replace any request elements - they have a large
100+ # unicode representation and the request data is
101+ # already made available from the Request panel.
102+ if isinstance (value , http .HttpRequest ):
103+ temp_layer [key ] = '<<request>>'
104+ # Replace the debugging sql_queries element. The SQL
105+ # data is already made available from the SQL panel.
106+ elif key == 'sql_queries' and isinstance (value , list ):
107+ temp_layer [key ] = '<<sql_queries>>'
108+ # Replace LANGUAGES, which is available in i18n context processor
109+ elif key == 'LANGUAGES' and isinstance (value , tuple ):
110+ temp_layer [key ] = '<<languages>>'
111+ # QuerySet would trigger the database: user can run the query from SQL Panel
112+ elif isinstance (value , (QuerySet , RawQuerySet )):
113+ model_name = "%s.%s" % (
114+ value .model ._meta .app_label , value .model .__name__ )
115+ temp_layer [key ] = '<<%s of %s>>' % (
116+ value .__class__ .__name__ .lower (), model_name )
119117 else :
120- temp_layer [key ] = value
121- finally :
122- recording (True )
118+ try :
119+ recording (False )
120+ force_text (value ) # this MAY trigger a db query
121+ except SQLQueryTriggered :
122+ temp_layer [key ] = '<<triggers database query>>'
123+ except UnicodeEncodeError :
124+ temp_layer [key ] = '<<unicode encode error>>'
125+ except Exception :
126+ temp_layer [key ] = '<<unhandled exception>>'
127+ else :
128+ temp_layer [key ] = value
129+ finally :
130+ recording (True )
131+ try :
132+ prettified_layer = force_text (pformat (temp_layer ))
133+ except UnicodeEncodeError :
134+ pass
135+ else :
136+ context_list .append (prettified_layer )
123137 # Refs GitHub issue #910
124- # After Django introduced template based form widget rendering,
138+ # After Django introduced template based form widget rendering,
125139 # djdt has to collect and format far more contexts, many of which
126140 # are duplicates, and don't need formatting if we've already seen
127141 # the exact same context.
128- # We sort the `temp_layer` dictionary as a 2-tuple to ensure that
129- # ordering is consistent, before simply converting it to a string
130- # representation to ensure UnicodeEncodeError can be raised as it
131- # was previously.
132- # If the stringification succeeded, and we've not seen the key
133- # before, pformat it. If we've seen it before, we should be able
134- # to just re-use it.
135- try :
136- forced = force_text (sorted (temp_layer .items ()))
137- except UnicodeEncodeError :
138- continue
139- else :
140- if forced not in self .pformats :
141- self .pformats [forced ] = force_text (pformat (temp_layer ))
142- context_list .append (self .pformats [forced ])
142+ # At this point, we know this is the first time we've seen and
143+ # collected the layers of this context, so we store the value into
144+ # a dictionary whose key is the id() of the Context instance.
145+ self .seen_contexts [context_id ] = context_list
143146
144147 kwargs ['context' ] = context_list
145148 kwargs ['context_processors' ] = getattr (context , 'context_processors' , None )
0 commit comments