Skip to content

Commit 5299cf8

Browse files
committed
Restructure the Panel class to execute more like the MIDDLEWARE
This allows the ProfilingPanel to no longer skip remaining panels or middlewares. This change is backwards incompatible. Third party panels will need to adjust to use this new architecture. It removes support for Django 1.11's deprecated MIDDLEWARE_CLASSES setting. The Panel.__init__() method is now passed a get_response argument. The Panel.process_request() method must now always return a response. Usually this is the response returned by get_response() but may also return a different response as is the case in the ``RedirectsPanel``. Panel.process_response() and Panel.process_view() have been removed. Fixes #1135
1 parent 335bc36 commit 5299cf8

24 files changed

+190
-254
lines changed

debug_toolbar/apps.py

+7-13
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,8 @@ def check_middleware(app_configs, **kwargs):
2323
gzip_index = None
2424
debug_toolbar_indexes = []
2525

26-
setting = getattr(settings, "MIDDLEWARE", None)
27-
setting_name = "MIDDLEWARE"
28-
if setting is None:
29-
setting = settings.MIDDLEWARE_CLASSES
30-
setting_name = "MIDDLEWARE_CLASSES"
31-
3226
# Determine the indexes which gzip and/or the toolbar are installed at
33-
for i, middleware in enumerate(setting):
27+
for i, middleware in enumerate(settings.MIDDLEWARE):
3428
if is_middleware_class(GZipMiddleware, middleware):
3529
gzip_index = i
3630
elif is_middleware_class(DebugToolbarMiddleware, middleware):
@@ -41,9 +35,9 @@ def check_middleware(app_configs, **kwargs):
4135
errors.append(
4236
Warning(
4337
"debug_toolbar.middleware.DebugToolbarMiddleware is missing "
44-
"from %s." % setting_name,
38+
"from MIDDLEWARE.",
4539
hint="Add debug_toolbar.middleware.DebugToolbarMiddleware to "
46-
"%s." % setting_name,
40+
"MIDDLEWARE.",
4741
id="debug_toolbar.W001",
4842
)
4943
)
@@ -52,9 +46,9 @@ def check_middleware(app_configs, **kwargs):
5246
errors.append(
5347
Warning(
5448
"debug_toolbar.middleware.DebugToolbarMiddleware occurs "
55-
"multiple times in %s." % setting_name,
49+
"multiple times in MIDDLEWARE.",
5650
hint="Load debug_toolbar.middleware.DebugToolbarMiddleware only "
57-
"once in %s." % setting_name,
51+
"once in MIDDLEWARE.",
5852
id="debug_toolbar.W002",
5953
)
6054
)
@@ -63,9 +57,9 @@ def check_middleware(app_configs, **kwargs):
6357
errors.append(
6458
Warning(
6559
"debug_toolbar.middleware.DebugToolbarMiddleware occurs before "
66-
"django.middleware.gzip.GZipMiddleware in %s." % setting_name,
60+
"django.middleware.gzip.GZipMiddleware in MIDDLEWARE.",
6761
hint="Move debug_toolbar.middleware.DebugToolbarMiddleware to "
68-
"after django.middleware.gzip.GZipMiddleware in %s." % setting_name,
62+
"after django.middleware.gzip.GZipMiddleware in MIDDLEWARE.",
6963
id="debug_toolbar.W003",
7064
)
7165
)

debug_toolbar/middleware.py

+17-54
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
from __future__ import absolute_import, unicode_literals
66

77
import re
8-
import threading
98

109
from django.conf import settings
1110
from django.utils import six
12-
from django.utils.deprecation import MiddlewareMixin
1311
from django.utils.encoding import force_text
1412
from django.utils.lru_cache import lru_cache
1513
from django.utils.module_loading import import_string
@@ -41,70 +39,35 @@ def get_show_toolbar():
4139
return func_or_path
4240

4341

44-
class DebugToolbarMiddleware(MiddlewareMixin):
42+
class DebugToolbarMiddleware(object):
4543
"""
4644
Middleware to set up Debug Toolbar on incoming request and render toolbar
4745
on outgoing response.
4846
"""
4947

50-
debug_toolbars = {}
48+
def __init__(self, get_response):
49+
self.get_response = get_response
5150

52-
def process_request(self, request):
53-
# Decide whether the toolbar is active for this request.
51+
def __call__(self, request):
52+
# Decide whether the toolbar is active for this request. Don't render
53+
# the toolbar during AJAX requests.
5454
show_toolbar = get_show_toolbar()
55-
if not show_toolbar(request):
56-
return
55+
if not show_toolbar(request) or request.is_ajax():
56+
return self.get_response(request)
5757

58-
# Don't render the toolbar during AJAX requests.
59-
if request.is_ajax():
60-
return
61-
62-
toolbar = DebugToolbar(request)
63-
self.__class__.debug_toolbars[threading.current_thread().ident] = toolbar
58+
toolbar = DebugToolbar(request, self.get_response)
6459

6560
# Activate instrumentation ie. monkey-patch.
6661
for panel in toolbar.enabled_panels:
6762
panel.enable_instrumentation()
68-
69-
# Run process_request methods of panels like Django middleware.
70-
response = None
71-
for panel in toolbar.enabled_panels:
72-
response = panel.process_request(request)
73-
if response:
74-
break
75-
return response
76-
77-
def process_view(self, request, view_func, view_args, view_kwargs):
78-
toolbar = self.__class__.debug_toolbars.get(threading.current_thread().ident)
79-
if not toolbar:
80-
return
81-
82-
# Run process_view methods of panels like Django middleware.
83-
response = None
84-
for panel in toolbar.enabled_panels:
85-
response = panel.process_view(request, view_func, view_args, view_kwargs)
86-
if response:
87-
break
88-
return response
89-
90-
def process_response(self, request, response):
91-
toolbar = self.__class__.debug_toolbars.pop(
92-
threading.current_thread().ident, None
93-
)
94-
if not toolbar:
95-
return response
96-
97-
# Run process_response methods of panels like Django middleware.
98-
for panel in reversed(toolbar.enabled_panels):
99-
new_response = panel.process_response(request, response)
100-
if new_response:
101-
response = new_response
102-
103-
# Deactivate instrumentation ie. monkey-unpatch. This must run
104-
# regardless of the response. Keep 'return' clauses below.
105-
# (NB: Django's model for middleware doesn't guarantee anything.)
106-
for panel in reversed(toolbar.enabled_panels):
107-
panel.disable_instrumentation()
63+
try:
64+
# Run panels like Django middleware.
65+
response = toolbar.process_request(request)
66+
finally:
67+
# Deactivate instrumentation ie. monkey-unpatch. This must run
68+
# regardless of the response. Keep 'return' clauses below.
69+
for panel in reversed(toolbar.enabled_panels):
70+
panel.disable_instrumentation()
10871

10972
# Check for responses where the toolbar can't be inserted.
11073
content_encoding = response.get("Content-Encoding", "")

debug_toolbar/panels/__init__.py

+10-29
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ class Panel(object):
1313
Base class for panels.
1414
"""
1515

16-
def __init__(self, toolbar):
16+
def __init__(self, toolbar, get_response):
1717
self.toolbar = toolbar
18+
self.get_response = get_response
1819

1920
# Private panel properties
2021

@@ -129,8 +130,7 @@ def disable_instrumentation(self):
129130
This is the opposite of :meth:`enable_instrumentation`.
130131
131132
Unless the toolbar or this panel is disabled, this method will be
132-
called late in :class:`DebugToolbarMiddleware.process_response`. It
133-
should be idempotent.
133+
called late in the middleware. It should be idempotent.
134134
"""
135135

136136
# Store and retrieve stats (shared between panels for no good reason)
@@ -168,40 +168,21 @@ def get_server_timing_stats(self):
168168

169169
def process_request(self, request):
170170
"""
171-
Like process_request in Django's middleware.
171+
Like __call__ in Django's middleware.
172172
173173
Write panel logic related to the request there. Save data with
174174
:meth:`record_stats`.
175-
"""
176-
177-
def process_view(self, request, view_func, view_args, view_kwargs):
178-
"""
179-
Like process_view in Django's middleware.
180-
181-
Write panel logic related to the view there. Save data with
182-
:meth:`record_stats`.
183-
"""
184-
185-
def process_response(self, request, response):
186-
"""
187-
Like process_response in Django's middleware. This is similar to
188-
:meth:`generate_stats <debug_toolbar.panels.Panel.generate_stats>`,
189-
but will be executed on every request. It should be used when either
190-
the logic needs to be executed on every request or it needs to change
191-
the response entirely, such as :class:`RedirectsPanel`.
192-
193-
Write panel logic related to the response there. Post-process data
194-
gathered while the view executed. Save data with :meth:`record_stats`.
195175
196-
Return a response to overwrite the existing response.
176+
Return the existing response or overwrite it.
197177
"""
178+
return self.get_response(request)
198179

199180
def generate_stats(self, request, response):
200181
"""
201-
Similar to :meth:`process_response
202-
<debug_toolbar.panels.Panel.process_response>`,
203-
but may not be executed on every request. This will only be called if
204-
the toolbar will be inserted into the request.
182+
Called after :meth:`process_request
183+
<debug_toolbar.panels.Panel.process_request>`, but may not be executed
184+
on every request. This will only be called if the toolbar will be
185+
inserted into the request.
205186
206187
Write panel logic related to the response there. Post-process data
207188
gathered while the view executed. Save data with :meth:`record_stats`.

debug_toolbar/panels/headers.py

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def process_request(self, request):
5151
self.record_stats(
5252
{"request_headers": self.request_headers, "environ": self.environ}
5353
)
54+
return super(HeadersPanel, self).process_request(request)
5455

5556
def generate_stats(self, request, response):
5657
self.response_headers = OrderedDict(sorted(response.items()))

debug_toolbar/panels/logging.py

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def nav_subtitle(self):
7676

7777
def process_request(self, request):
7878
collector.clear_collection()
79+
return super(LoggingPanel, self).process_request(request)
7980

8081
def generate_stats(self, request, response):
8182
records = collector.get_collection()

debug_toolbar/panels/profiling.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,11 @@ class ProfilingPanel(Panel):
154154

155155
template = "debug_toolbar/panels/profiling.html"
156156

157-
def process_view(self, request, view_func, view_args, view_kwargs):
157+
def process_request(self, request):
158158
self.profiler = cProfile.Profile()
159-
args = (request,) + view_args
160-
return self.profiler.runcall(view_func, *args, **view_kwargs)
159+
return self.profiler.runcall(
160+
super(ProfilingPanel, self).process_request, request
161+
)
161162

162163
def add_node(self, func_list, func, max_depth, cum_time=0.1):
163164
func_list.append(func)

debug_toolbar/panels/redirects.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class RedirectsPanel(Panel):
1515

1616
nav_title = _("Intercept redirects")
1717

18-
def process_response(self, request, response):
18+
def process_request(self, request):
19+
response = super(RedirectsPanel, self).process_request(request)
1920
if 300 <= int(response.status_code) < 400:
2021
redirect_to = response.get("Location", None)
2122
if redirect_to:

debug_toolbar/panels/staticfiles.py

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def nav_subtitle(self):
116116

117117
def process_request(self, request):
118118
collector.clear_collection()
119+
return super(StaticFilesPanel, self).process_request(request)
119120

120121
def generate_stats(self, request, response):
121122
used_paths = collector.get_collection()

debug_toolbar/panels/timer.py

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def process_request(self, request):
5757
self._start_time = time.time()
5858
if self.has_content:
5959
self._start_rusage = resource.getrusage(resource.RUSAGE_SELF)
60+
return super(TimerPanel, self).process_request(request)
6061

6162
def generate_stats(self, request, response):
6263
stats = {}

debug_toolbar/toolbar.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@
1818

1919

2020
class DebugToolbar(object):
21-
def __init__(self, request):
21+
def __init__(self, request, get_response):
2222
self.request = request
2323
self.config = dt_settings.get_config().copy()
24+
panels = []
25+
for panel_class in reversed(self.get_panel_classes()):
26+
panel = panel_class(self, get_response)
27+
panels.append(panel)
28+
if panel.enabled:
29+
get_response = panel.process_request
30+
self.process_request = get_response
2431
self._panels = OrderedDict()
25-
for panel_class in self.get_panel_classes():
26-
panel_instance = panel_class(self)
27-
self._panels[panel_instance.panel_id] = panel_instance
32+
while panels:
33+
panel = panels.pop()
34+
self._panels[panel.panel_id] = panel
2835
self.stats = {}
2936
self.server_timing_stats = {}
3037
self.store_id = None

docs/changes.rst

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ UNRELEASED
77
* Updated ``StaticFilesPanel`` to be compatible with Django 3.0.
88
* The ``ProfilingPanel`` is now enabled but inactive by default.
99
* Fixed toggling of table rows in the profiling panel UI.
10+
* The ``ProfilingPanel`` no longer skips remaining panels or middlewares.
11+
12+
**Backwards incompatible changes**
13+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
* Removed support for Django's deprecated ``MIDDLEWARE_CLASSES`` setting.
15+
* Restructured ``Panel`` to execute more like the new-style Django MIDDLEWARE.
16+
The ``Panel.__init__()`` method is now passed ``get_response`` as the first
17+
positional argument. The ``Panel.process_request()`` method must now always
18+
return a response. Usually this is the response returned by
19+
``get_response()`` but the panel may also return a different response as is
20+
the case in the ``RedirectsPanel``. Third party panels must adjust to this
21+
new architecture. ``Panel.process_response()`` and ``Panel.process_view()``
22+
have been removed as a result of this change.
1023

1124
1.11 (2018-12-03)
1225
-----------------

docs/installation.rst

+3-12
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,11 @@ settings module as follows::
7575
# ...
7676
]
7777

78-
Old-style middleware::
79-
80-
MIDDLEWARE_CLASSES = [
81-
# ...
82-
'debug_toolbar.middleware.DebugToolbarMiddleware',
83-
# ...
84-
]
85-
8678
.. warning::
8779

88-
The order of ``MIDDLEWARE`` and ``MIDDLEWARE_CLASSES`` is important. You
89-
should include the Debug Toolbar middleware as early as possible in the
90-
list. However, it must come after any other middleware that encodes the
91-
response's content, such as
80+
The order of ``MIDDLEWARE`` is important. You should include the Debug
81+
Toolbar middleware as early as possible in the list. However, it must come
82+
after any other middleware that encodes the response's content, such as
9283
:class:`~django.middleware.gzip.GZipMiddleware`.
9384

9485
Configuring Internal IPs

docs/panels.rst

-15
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,6 @@ Profiling information for the processing of the request.
117117
This panel is included but inactive by default. You can activate it by default
118118
with the ``DISABLE_PANELS`` configuration option.
119119

120-
If the ``debug_toolbar.middleware.DebugToolbarMiddleware`` is first in
121-
``MIDDLEWARE`` then the other middlewares' ``process_view`` methods will not be
122-
executed. This is because ``ProfilingPanel.process_view`` will return a
123-
``HttpResponse`` which causes the other middlewares' ``process_view`` methods
124-
to be skipped.
125-
126-
If you run into this issues, then you should either deactivate the
127-
``ProfilingPanel`` or move ``DebugToolbarMiddleware`` to the end of
128-
``MIDDLEWARE``. If you do the latter, then the debug toolbar won't track the
129-
execution of other middleware.
130-
131120
Third-party panels
132121
------------------
133122

@@ -342,10 +331,6 @@ unauthorized access. There is no public CSS API at this time.
342331

343332
.. automethod:: debug_toolbar.panels.Panel.process_request
344333

345-
.. automethod:: debug_toolbar.panels.Panel.process_view
346-
347-
.. automethod:: debug_toolbar.panels.Panel.process_response
348-
349334
.. automethod:: debug_toolbar.panels.Panel.generate_stats
350335

351336
.. _javascript-api:

docs/tips.rst

-10
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,6 @@ Browsers have become more aggressive with caching static assets, such as
1818
JavaScript and CSS files. Check your browser's development console, and if
1919
you see errors, try a hard browser refresh or clearing your cache.
2020

21-
Middleware isn't working correctly
22-
----------------------------------
23-
24-
Using the Debug Toolbar in its default configuration with the profiling panel
25-
active will cause middlewares after
26-
``debug_toolbar.middleware.DebugToolbarMiddleware`` to not execute their
27-
``process_view`` functions. This can be resolved by disabling the profiling
28-
panel or moving the ``DebugToolbarMiddleware`` to the end of ``MIDDLEWARE``.
29-
Read more about it at :ref:`ProfilingPanel <profiling-panel>`
30-
3121
Performance considerations
3222
--------------------------
3323

0 commit comments

Comments
 (0)