@@ -70,10 +70,11 @@ def __init__(self, *args, **kwargs):
70
70
super (TemplatesPanel , self ).__init__ (* args , ** kwargs )
71
71
self .templates = []
72
72
# 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
+
77
78
78
79
def _store_template_info (self , sender , ** kwargs ):
79
80
template , context = kwargs ['template' ], kwargs ['context' ]
@@ -83,63 +84,65 @@ def _store_template_info(self, sender, **kwargs):
83
84
template .name .startswith ('debug_toolbar/' )):
84
85
return
85
86
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 )
119
117
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 )
123
137
# Refs GitHub issue #910
124
- # After Django introduced template based form widget rendering,
138
+ # After Django introduced template based form widget rendering,
125
139
# djdt has to collect and format far more contexts, many of which
126
140
# are duplicates, and don't need formatting if we've already seen
127
141
# 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
143
146
144
147
kwargs ['context' ] = context_list
145
148
kwargs ['context_processors' ] = getattr (context , 'context_processors' , None )
0 commit comments