diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py index 24e41ddbf..7fe9abeb9 100644 --- a/debug_toolbar/apps.py +++ b/debug_toolbar/apps.py @@ -23,14 +23,8 @@ def check_middleware(app_configs, **kwargs): gzip_index = None debug_toolbar_indexes = [] - setting = getattr(settings, "MIDDLEWARE", None) - setting_name = "MIDDLEWARE" - if setting is None: - setting = settings.MIDDLEWARE_CLASSES - setting_name = "MIDDLEWARE_CLASSES" - # Determine the indexes which gzip and/or the toolbar are installed at - for i, middleware in enumerate(setting): + for i, middleware in enumerate(settings.MIDDLEWARE): if is_middleware_class(GZipMiddleware, middleware): gzip_index = i elif is_middleware_class(DebugToolbarMiddleware, middleware): @@ -41,9 +35,9 @@ def check_middleware(app_configs, **kwargs): errors.append( Warning( "debug_toolbar.middleware.DebugToolbarMiddleware is missing " - "from %s." % setting_name, + "from MIDDLEWARE.", hint="Add debug_toolbar.middleware.DebugToolbarMiddleware to " - "%s." % setting_name, + "MIDDLEWARE.", id="debug_toolbar.W001", ) ) @@ -52,9 +46,9 @@ def check_middleware(app_configs, **kwargs): errors.append( Warning( "debug_toolbar.middleware.DebugToolbarMiddleware occurs " - "multiple times in %s." % setting_name, + "multiple times in MIDDLEWARE.", hint="Load debug_toolbar.middleware.DebugToolbarMiddleware only " - "once in %s." % setting_name, + "once in MIDDLEWARE.", id="debug_toolbar.W002", ) ) @@ -63,9 +57,9 @@ def check_middleware(app_configs, **kwargs): errors.append( Warning( "debug_toolbar.middleware.DebugToolbarMiddleware occurs before " - "django.middleware.gzip.GZipMiddleware in %s." % setting_name, + "django.middleware.gzip.GZipMiddleware in MIDDLEWARE.", hint="Move debug_toolbar.middleware.DebugToolbarMiddleware to " - "after django.middleware.gzip.GZipMiddleware in %s." % setting_name, + "after django.middleware.gzip.GZipMiddleware in MIDDLEWARE.", id="debug_toolbar.W003", ) ) diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 0c2087112..acb1af267 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -5,11 +5,9 @@ from __future__ import absolute_import, unicode_literals import re -import threading from django.conf import settings from django.utils import six -from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_text from django.utils.lru_cache import lru_cache from django.utils.module_loading import import_string @@ -41,70 +39,35 @@ def get_show_toolbar(): return func_or_path -class DebugToolbarMiddleware(MiddlewareMixin): +class DebugToolbarMiddleware(object): """ Middleware to set up Debug Toolbar on incoming request and render toolbar on outgoing response. """ - debug_toolbars = {} + def __init__(self, get_response): + self.get_response = get_response - def process_request(self, request): - # Decide whether the toolbar is active for this request. + def __call__(self, request): + # Decide whether the toolbar is active for this request. Don't render + # the toolbar during AJAX requests. show_toolbar = get_show_toolbar() - if not show_toolbar(request): - return + if not show_toolbar(request) or request.is_ajax(): + return self.get_response(request) - # Don't render the toolbar during AJAX requests. - if request.is_ajax(): - return - - toolbar = DebugToolbar(request) - self.__class__.debug_toolbars[threading.current_thread().ident] = toolbar + toolbar = DebugToolbar(request, self.get_response) # Activate instrumentation ie. monkey-patch. for panel in toolbar.enabled_panels: panel.enable_instrumentation() - - # Run process_request methods of panels like Django middleware. - response = None - for panel in toolbar.enabled_panels: - response = panel.process_request(request) - if response: - break - return response - - def process_view(self, request, view_func, view_args, view_kwargs): - toolbar = self.__class__.debug_toolbars.get(threading.current_thread().ident) - if not toolbar: - return - - # Run process_view methods of panels like Django middleware. - response = None - for panel in toolbar.enabled_panels: - response = panel.process_view(request, view_func, view_args, view_kwargs) - if response: - break - return response - - def process_response(self, request, response): - toolbar = self.__class__.debug_toolbars.pop( - threading.current_thread().ident, None - ) - if not toolbar: - return response - - # Run process_response methods of panels like Django middleware. - for panel in reversed(toolbar.enabled_panels): - new_response = panel.process_response(request, response) - if new_response: - response = new_response - - # Deactivate instrumentation ie. monkey-unpatch. This must run - # regardless of the response. Keep 'return' clauses below. - # (NB: Django's model for middleware doesn't guarantee anything.) - for panel in reversed(toolbar.enabled_panels): - panel.disable_instrumentation() + try: + # Run panels like Django middleware. + response = toolbar.process_request(request) + finally: + # Deactivate instrumentation ie. monkey-unpatch. This must run + # regardless of the response. Keep 'return' clauses below. + for panel in reversed(toolbar.enabled_panels): + panel.disable_instrumentation() # Check for responses where the toolbar can't be inserted. content_encoding = response.get("Content-Encoding", "") diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index ecead3cb9..f0da95972 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -13,8 +13,9 @@ class Panel(object): Base class for panels. """ - def __init__(self, toolbar): + def __init__(self, toolbar, get_response): self.toolbar = toolbar + self.get_response = get_response # Private panel properties @@ -129,8 +130,7 @@ def disable_instrumentation(self): This is the opposite of :meth:`enable_instrumentation`. Unless the toolbar or this panel is disabled, this method will be - called late in :class:`DebugToolbarMiddleware.process_response`. It - should be idempotent. + called late in the middleware. It should be idempotent. """ # Store and retrieve stats (shared between panels for no good reason) @@ -168,40 +168,21 @@ def get_server_timing_stats(self): def process_request(self, request): """ - Like process_request in Django's middleware. + Like __call__ in Django's middleware. Write panel logic related to the request there. Save data with :meth:`record_stats`. - """ - - def process_view(self, request, view_func, view_args, view_kwargs): - """ - Like process_view in Django's middleware. - - Write panel logic related to the view there. Save data with - :meth:`record_stats`. - """ - - def process_response(self, request, response): - """ - Like process_response in Django's middleware. This is similar to - :meth:`generate_stats `, - but will be executed on every request. It should be used when either - the logic needs to be executed on every request or it needs to change - the response entirely, such as :class:`RedirectsPanel`. - - Write panel logic related to the response there. Post-process data - gathered while the view executed. Save data with :meth:`record_stats`. - Return a response to overwrite the existing response. + Return the existing response or overwrite it. """ + return self.get_response(request) def generate_stats(self, request, response): """ - Similar to :meth:`process_response - `, - but may not be executed on every request. This will only be called if - the toolbar will be inserted into the request. + Called after :meth:`process_request + `, but may not be executed + on every request. This will only be called if the toolbar will be + inserted into the request. Write panel logic related to the response there. Post-process data gathered while the view executed. Save data with :meth:`record_stats`. diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index b6a493f2e..46def756b 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -51,6 +51,7 @@ def process_request(self, request): self.record_stats( {"request_headers": self.request_headers, "environ": self.environ} ) + return super(HeadersPanel, self).process_request(request) def generate_stats(self, request, response): self.response_headers = OrderedDict(sorted(response.items())) diff --git a/debug_toolbar/panels/logging.py b/debug_toolbar/panels/logging.py index bb594a8a7..e6c7f7a73 100644 --- a/debug_toolbar/panels/logging.py +++ b/debug_toolbar/panels/logging.py @@ -76,6 +76,7 @@ def nav_subtitle(self): def process_request(self, request): collector.clear_collection() + return super(LoggingPanel, self).process_request(request) def generate_stats(self, request, response): records = collector.get_collection() diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 387518edd..85de11cdb 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -154,10 +154,11 @@ class ProfilingPanel(Panel): template = "debug_toolbar/panels/profiling.html" - def process_view(self, request, view_func, view_args, view_kwargs): + def process_request(self, request): self.profiler = cProfile.Profile() - args = (request,) + view_args - return self.profiler.runcall(view_func, *args, **view_kwargs) + return self.profiler.runcall( + super(ProfilingPanel, self).process_request, request + ) def add_node(self, func_list, func, max_depth, cum_time=0.1): func_list.append(func) diff --git a/debug_toolbar/panels/redirects.py b/debug_toolbar/panels/redirects.py index eafb6c95b..d692a495c 100644 --- a/debug_toolbar/panels/redirects.py +++ b/debug_toolbar/panels/redirects.py @@ -15,7 +15,8 @@ class RedirectsPanel(Panel): nav_title = _("Intercept redirects") - def process_response(self, request, response): + def process_request(self, request): + response = super(RedirectsPanel, self).process_request(request) if 300 <= int(response.status_code) < 400: redirect_to = response.get("Location", None) if redirect_to: diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index b99b45791..e285905b4 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -116,6 +116,7 @@ def nav_subtitle(self): def process_request(self, request): collector.clear_collection() + return super(StaticFilesPanel, self).process_request(request) def generate_stats(self, request, response): used_paths = collector.get_collection() diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 8b73cf308..65862df57 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -57,6 +57,7 @@ def process_request(self, request): self._start_time = time.time() if self.has_content: self._start_rusage = resource.getrusage(resource.RUSAGE_SELF) + return super(TimerPanel, self).process_request(request) def generate_stats(self, request, response): stats = {} diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index e93d54127..1db0ecdb3 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -18,13 +18,20 @@ class DebugToolbar(object): - def __init__(self, request): + def __init__(self, request, get_response): self.request = request self.config = dt_settings.get_config().copy() + panels = [] + for panel_class in reversed(self.get_panel_classes()): + panel = panel_class(self, get_response) + panels.append(panel) + if panel.enabled: + get_response = panel.process_request + self.process_request = get_response self._panels = OrderedDict() - for panel_class in self.get_panel_classes(): - panel_instance = panel_class(self) - self._panels[panel_instance.panel_id] = panel_instance + while panels: + panel = panels.pop() + self._panels[panel.panel_id] = panel self.stats = {} self.server_timing_stats = {} self.store_id = None diff --git a/docs/changes.rst b/docs/changes.rst index b23948fc8..b2b5d84e1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -7,6 +7,19 @@ UNRELEASED * Updated ``StaticFilesPanel`` to be compatible with Django 3.0. * The ``ProfilingPanel`` is now enabled but inactive by default. * Fixed toggling of table rows in the profiling panel UI. +* The ``ProfilingPanel`` no longer skips remaining panels or middlewares. + +**Backwards incompatible changes** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* Removed support for Django's deprecated ``MIDDLEWARE_CLASSES`` setting. +* Restructured ``Panel`` to execute more like the new-style Django MIDDLEWARE. + The ``Panel.__init__()`` method is now passed ``get_response`` as the first + positional argument. The ``Panel.process_request()`` method must now always + return a response. Usually this is the response returned by + ``get_response()`` but the panel may also return a different response as is + the case in the ``RedirectsPanel``. Third party panels must adjust to this + new architecture. ``Panel.process_response()`` and ``Panel.process_view()`` + have been removed as a result of this change. 1.11 (2018-12-03) ----------------- diff --git a/docs/installation.rst b/docs/installation.rst index 6b338f6bd..79aa3dc56 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -75,20 +75,11 @@ settings module as follows:: # ... ] -Old-style middleware:: - - MIDDLEWARE_CLASSES = [ - # ... - 'debug_toolbar.middleware.DebugToolbarMiddleware', - # ... - ] - .. warning:: - The order of ``MIDDLEWARE`` and ``MIDDLEWARE_CLASSES`` is important. You - should include the Debug Toolbar middleware as early as possible in the - list. However, it must come after any other middleware that encodes the - response's content, such as + The order of ``MIDDLEWARE`` is important. You should include the Debug + Toolbar middleware as early as possible in the list. However, it must come + after any other middleware that encodes the response's content, such as :class:`~django.middleware.gzip.GZipMiddleware`. Configuring Internal IPs diff --git a/docs/panels.rst b/docs/panels.rst index ded4c06d3..143407935 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -117,17 +117,6 @@ Profiling information for the processing of the request. This panel is included but inactive by default. You can activate it by default with the ``DISABLE_PANELS`` configuration option. -If the ``debug_toolbar.middleware.DebugToolbarMiddleware`` is first in -``MIDDLEWARE`` then the other middlewares' ``process_view`` methods will not be -executed. This is because ``ProfilingPanel.process_view`` will return a -``HttpResponse`` which causes the other middlewares' ``process_view`` methods -to be skipped. - -If you run into this issues, then you should either deactivate the -``ProfilingPanel`` or move ``DebugToolbarMiddleware`` to the end of -``MIDDLEWARE``. If you do the latter, then the debug toolbar won't track the -execution of other middleware. - Third-party panels ------------------ @@ -342,10 +331,6 @@ unauthorized access. There is no public CSS API at this time. .. automethod:: debug_toolbar.panels.Panel.process_request - .. automethod:: debug_toolbar.panels.Panel.process_view - - .. automethod:: debug_toolbar.panels.Panel.process_response - .. automethod:: debug_toolbar.panels.Panel.generate_stats .. _javascript-api: diff --git a/docs/tips.rst b/docs/tips.rst index 8e8933139..16ad71ba8 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -18,16 +18,6 @@ Browsers have become more aggressive with caching static assets, such as JavaScript and CSS files. Check your browser's development console, and if you see errors, try a hard browser refresh or clearing your cache. -Middleware isn't working correctly ----------------------------------- - -Using the Debug Toolbar in its default configuration with the profiling panel -active will cause middlewares after -``debug_toolbar.middleware.DebugToolbarMiddleware`` to not execute their -``process_view`` functions. This can be resolved by disabling the profiling -panel or moving the ``DebugToolbarMiddleware`` to the end of ``MIDDLEWARE``. -Read more about it at :ref:`ProfilingPanel ` - Performance considerations -------------------------- diff --git a/tests/base.py b/tests/base.py index 76682fe5c..dcd470659 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,12 +1,9 @@ from __future__ import absolute_import, unicode_literals -import threading - import html5lib from django.http import HttpResponse from django.test import RequestFactory, TestCase -from debug_toolbar.middleware import DebugToolbarMiddleware from debug_toolbar.toolbar import DebugToolbar rf = RequestFactory() @@ -17,17 +14,9 @@ class BaseTestCase(TestCase): def setUp(self): super(BaseTestCase, self).setUp() - request = rf.get("/") - response = HttpResponse() - toolbar = DebugToolbar(request) - - DebugToolbarMiddleware.debug_toolbars[ - threading.current_thread().ident - ] = toolbar - - self.request = request - self.response = response - self.toolbar = toolbar + self._get_response = lambda request: HttpResponse() + self.request = rf.get("/") + self.toolbar = DebugToolbar(self.request, self.get_response) self.toolbar.stats = {} if self.panel_id: @@ -41,6 +30,9 @@ def tearDown(self): self.panel.disable_instrumentation() super(BaseTestCase, self).tearDown() + def get_response(self, request): + return self._get_response(request) + def assertValidHTML(self, content, msg=None): parser = html5lib.HTMLParser() parser.parseFragment(self.panel.content) diff --git a/tests/panels/test_cache.py b/tests/panels/test_cache.py index b1867c8ab..4d642b2db 100644 --- a/tests/panels/test_cache.py +++ b/tests/panels/test_cache.py @@ -30,13 +30,13 @@ def test_recording_caches(self): def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ cache.cache.get("café") - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("café", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) @@ -49,8 +49,9 @@ def test_generate_server_timing(self): self.assertEqual(len(self.panel.calls), 3) - self.panel.generate_stats(self.request, self.response) - self.panel.generate_server_timing(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + self.panel.generate_server_timing(self.request, response) stats = self.panel.get_stats() diff --git a/tests/panels/test_logging.py b/tests/panels/test_logging.py index c4d57d86c..d35d2cbf9 100644 --- a/tests/panels/test_logging.py +++ b/tests/panels/test_logging.py @@ -10,6 +10,7 @@ ) from ..base import BaseTestCase +from ..views import regular_view class LoggingPanelTestCase(BaseTestCase): @@ -25,20 +26,26 @@ def setUp(self): logging.root.setLevel(logging.DEBUG) def test_happy_case(self): - self.logger.info("Nothing to see here, move along!") + def view(request): + self.logger.info("Nothing to see here, move along!") + return regular_view(request, "logging") - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self._get_response = view + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) self.assertEqual("Nothing to see here, move along!", records[0]["message"]) def test_formatting(self): - self.logger.info("There are %d %s", 5, "apples") + def view(request): + self.logger.info("There are %d %s", 5, "apples") + return regular_view(request, "logging") - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self._get_response = view + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) @@ -47,13 +54,18 @@ def test_formatting(self): def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ - self.logger.info("café") - self.panel.process_response(self.request, self.response) + + def view(request): + self.logger.info("café") + return regular_view(request, "logging") + + self._get_response = view + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("café", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) @@ -63,11 +75,14 @@ class BadClass(object): def __str__(self): raise Exception("Please not stringify me!") - # should not raise exception, but fail silently - self.logger.debug("This class is misbehaving: %s", BadClass()) + def view(request): + # should not raise exception, but fail silently + self.logger.debug("This class is misbehaving: %s", BadClass()) + return regular_view(request, "logging") - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self._get_response = view + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) records = self.panel.get_stats()["records"] self.assertEqual(1, len(records)) diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index 0cc5e6900..5dff5572c 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User from django.db import IntegrityError, transaction +from django.http import HttpResponse from django.test import TestCase from django.test.utils import override_settings from django.utils import six @@ -19,30 +20,31 @@ class ProfilingPanelTestCase(BaseTestCase): panel_id = "ProfilingPanel" def test_regular_view(self): - self.panel.process_view(self.request, regular_view, ("profiling",), {}) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self._get_response = lambda request: regular_view(request, "profiling") + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) self.assertIn("func_list", self.panel.get_stats()) self.assertIn("regular_view", self.panel.content) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ - self.panel.process_view(self.request, regular_view, ("profiling",), {}) - self.panel.process_response(self.request, self.response) + self._get_response = lambda request: regular_view(request, "profiling") + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("regular_view", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("regular_view", self.panel.content) self.assertValidHTML(self.panel.content) @unittest.skipIf(six.PY2, "list comprehension not listed on Python 2") def test_listcomp_escaped(self): - self.panel.process_view(self.request, listcomp_view, (), {}) - self.panel.generate_stats(self.request, self.response) + self._get_response = lambda request: listcomp_view(request) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) self.assertNotIn( '', self.panel.content ) @@ -54,18 +56,18 @@ def test_generate_stats_no_profiler(self): """ Test generating stats with no profiler. """ - self.assertIsNone(self.panel.generate_stats(self.request, self.response)) + response = HttpResponse() + self.assertIsNone(self.panel.generate_stats(self.request, response)) def test_generate_stats_no_root_func(self): """ Test generating stats using profiler without root function. """ - self.panel.process_view(self.request, regular_view, ("profiling",), {}) - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) self.panel.profiler.clear() self.panel.profiler.enable() self.panel.profiler.disable() - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) self.assertNotIn("func_list", self.panel.get_stats()) diff --git a/tests/panels/test_redirects.py b/tests/panels/test_redirects.py index 87c95c724..a5a0d1116 100644 --- a/tests/panels/test_redirects.py +++ b/tests/panels/test_redirects.py @@ -12,18 +12,22 @@ class RedirectsPanelTestCase(BaseTestCase): panel_id = "RedirectsPanel" def test_regular_response(self): - response = self.panel.process_response(self.request, self.response) - self.assertTrue(response is self.response) + not_redirect = HttpResponse() + self._get_response = lambda request: not_redirect + response = self.panel.process_request(self.request) + self.assertTrue(response is not_redirect) def test_not_a_redirect(self): - redirect = HttpResponse(status=304) # not modified - response = self.panel.process_response(self.request, redirect) + redirect = HttpResponse(status=304) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertTrue(response is redirect) def test_redirect(self): redirect = HttpResponse(status=302) redirect["Location"] = "http://somewhere/else/" - response = self.panel.process_response(self.request, redirect) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertFalse(response is redirect) self.assertContains(response, "302 Found") self.assertContains(response, "http://somewhere/else/") @@ -37,7 +41,8 @@ def test_redirect_with_broken_context_processor(self): with self.settings(TEMPLATES=TEMPLATES): redirect = HttpResponse(status=302) redirect["Location"] = "http://somewhere/else/" - response = self.panel.process_response(self.request, redirect) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertFalse(response is redirect) self.assertContains(response, "302 Found") self.assertContains(response, "http://somewhere/else/") @@ -45,22 +50,25 @@ def test_redirect_with_broken_context_processor(self): def test_unknown_status_code(self): redirect = HttpResponse(status=369) redirect["Location"] = "http://somewhere/else/" - response = self.panel.process_response(self.request, redirect) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertContains(response, "369 Unknown Status Code") def test_unknown_status_code_with_reason(self): redirect = HttpResponse(status=369, reason="Look Ma!") redirect["Location"] = "http://somewhere/else/" - response = self.panel.process_response(self.request, redirect) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertContains(response, "369 Look Ma!") def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ redirect = HttpResponse(status=304) - response = self.panel.process_response(self.request, redirect) + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) self.assertIsNotNone(response) response = self.panel.generate_stats(self.request, redirect) self.assertIsNone(response) diff --git a/tests/panels/test_request.py b/tests/panels/test_request.py index 9683d698e..576031fd0 100644 --- a/tests/panels/test_request.py +++ b/tests/panels/test_request.py @@ -14,9 +14,8 @@ def test_non_ascii_session(self): self.request.session = {"où": "où"} if not six.PY3: self.request.session["là".encode("utf-8")] = "là".encode("utf-8") - self.panel.process_request(self.request) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) content = self.panel.content if six.PY3: self.assertIn("où", content) @@ -26,21 +25,20 @@ def test_non_ascii_session(self): def test_object_with_non_ascii_repr_in_request_params(self): self.request.path = "/non_ascii_request/" - self.panel.process_request(self.request) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) self.assertIn("nôt åscíì", self.panel.content) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ self.request.path = "/non_ascii_request/" - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("nôt åscíì", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("nôt åscíì", self.panel.content) self.assertValidHTML(self.panel.content) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 1942f9950..30a1875ff 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -45,9 +45,9 @@ def test_generate_server_timing(self): list(User.objects.all()) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) - self.panel.generate_server_timing(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + self.panel.generate_server_timing(self.request, response) # ensure query was logged self.assertEqual(len(self.panel._queries), 1) @@ -74,8 +74,8 @@ def test_non_ascii_query(self): list(User.objects.filter(username="café".encode("utf-8"))) self.assertEqual(len(self.panel._queries), 3) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly self.assertIn("café", self.panel.content) @@ -95,8 +95,8 @@ def test_param_conversion(self): ) list(User.objects.filter(date_joined=datetime.datetime(2017, 12, 22, 16, 7, 1))) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) # ensure query was logged self.assertEqual(len(self.panel._queries), 3) @@ -115,8 +115,8 @@ def test_binary_param_force_text(self): [connection.Database.Binary(b"\xff")], ) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) self.assertEqual(len(self.panel._queries), 1) self.assertIn( @@ -166,8 +166,8 @@ def test_raw_query_param_conversion(self): ) ) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) # ensure query was logged self.assertEqual(len(self.panel._queries), 2) @@ -190,13 +190,13 @@ def test_raw_query_param_conversion(self): def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ list(User.objects.filter(username="café".encode("utf-8"))) - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("café", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("café", self.panel.content) self.assertValidHTML(self.panel.content) diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index 6c1e43a1e..d7ea4f3c8 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -11,9 +11,8 @@ class StaticFilesPanelTestCase(BaseTestCase): panel_id = "StaticFilesPanel" def test_default_case(self): - self.panel.process_request(self.request) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) self.assertIn( "django.contrib.staticfiles.finders." "AppDirectoriesFinder", self.panel.content, @@ -34,16 +33,15 @@ def test_default_case(self): def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ - self.panel.process_request(self.request) - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn( "django.contrib.staticfiles.finders." "AppDirectoriesFinder", self.panel.content, ) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn( "django.contrib.staticfiles.finders." "AppDirectoriesFinder", diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index fd5b383c2..9e9582e07 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -40,37 +40,35 @@ def test_queryset_hook(self): self.assertIn("<>", ctx) def test_object_with_non_ascii_repr_in_context(self): - self.panel.process_request(self.request) + response = self.panel.process_request(self.request) t = Template("{{ object }}") c = Context({"object": NonAsciiRepr()}) t.render(c) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) self.assertIn("nôt åscíì", self.panel.content) def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and - not the process_response. + not the process_request. """ t = Template("{{ object }}") c = Context({"object": NonAsciiRepr()}) t.render(c) - self.panel.process_response(self.request, self.response) + response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("nôt åscíì", self.panel.content) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) # ensure the panel renders correctly. self.assertIn("nôt åscíì", self.panel.content) self.assertValidHTML(self.panel.content) def test_custom_context_processor(self): - self.panel.process_request(self.request) + response = self.panel.process_request(self.request) t = Template("{{ content }}") c = RequestContext(self.request, processors=[context_processor]) t.render(c) - self.panel.process_response(self.request, self.response) - self.panel.generate_stats(self.request, self.response) + self.panel.generate_stats(self.request, response) self.assertIn( "tests.panels.test_template.context_processor", self.panel.content ) diff --git a/tests/test_integration.py b/tests/test_integration.py index d8e4cc904..e519b425b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -9,6 +9,7 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.core import signing from django.core.checks import Warning, run_checks +from django.http import HttpResponse from django.template.loader import get_template from django.test import RequestFactory, SimpleTestCase, TestCase from django.test.utils import override_settings @@ -47,9 +48,8 @@ def _resolve_stats(self, path): # takes stats from Request panel self.request.path = path panel = self.toolbar.get_panel_by_id("RequestPanel") - panel.process_request(self.request) - panel.process_response(self.request, self.response) - panel.generate_stats(self.request, self.response) + response = panel.process_request(self.request) + panel.generate_stats(self.request, response) return panel.get_stats() def test_url_resolving_positional(self): @@ -76,22 +76,13 @@ def test_url_resolving_bad(self): self.assertEqual(stats["view_kwargs"], "None") self.assertEqual(stats["view_func"], "") - # Django doesn't guarantee that process_request, process_view and - # process_response always get called in this order. - - def test_middleware_view_only(self): - DebugToolbarMiddleware().process_view( - self.request, regular_view, ("title",), {} - ) - - def test_middleware_response_only(self): - DebugToolbarMiddleware().process_response(self.request, self.response) - def test_middleware_response_insertion(self): - resp = regular_view(self.request, "İ") - DebugToolbarMiddleware().process_response(self.request, resp) + def get_response(request): + return regular_view(request, "İ") + + response = DebugToolbarMiddleware(get_response)(self.request) # check toolbar insertion before "" - self.assertContains(resp, "\n") + self.assertContains(response, "\n") def test_cache_page(self): self.client.get("/cached_view/") @@ -131,7 +122,10 @@ def test_html5_validation(self): raise self.failureException(msg) def test_render_panel_checks_show_toolbar(self): - toolbar = DebugToolbar(None) + def get_response(request): + return HttpResponse() + + toolbar = DebugToolbar(rf.get("/"), get_response) toolbar.store() url = "/__debug__/render_panel/" data = {"store_id": toolbar.store_id, "panel_id": "VersionsPanel"}