diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 508a60577..56a891848 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -16,6 +16,7 @@ class HistoryPanel(Panel): """A panel to display History""" + is_async = True title = _("History") nav_title = _("History") template = "debug_toolbar/panels/history.html" diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 35d789a53..eb8789b7f 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -12,6 +12,7 @@ from django.apps import apps from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.core.handlers.asgi import ASGIRequest from django.dispatch import Signal from django.template import TemplateSyntaxError from django.template.loader import render_to_string @@ -101,12 +102,18 @@ def should_render_panels(self): If False, the panels will be loaded via Ajax. """ if (render_panels := self.config["RENDER_PANELS"]) is None: - # If wsgi.multiprocess isn't in the headers, then it's likely - # being served by ASGI. This type of set up is most likely - # incompatible with the toolbar until - # https://github.com/jazzband/django-debug-toolbar/issues/1430 - # is resolved. - render_panels = self.request.META.get("wsgi.multiprocess", True) + # If wsgi.multiprocess is true then it is either being served + # from ASGI or multithreaded third-party WSGI server eg gunicorn. + # we need to make special check for ASGI for supporting + # async context based requests. + if isinstance(self.request, ASGIRequest): + render_panels = False + else: + # The wsgi.multiprocess case of being True isn't supported until the + # toolbar has resolved the following issue: + # This type of set up is most likely + # https://github.com/jazzband/django-debug-toolbar/issues/1430 + render_panels = self.request.META.get("wsgi.multiprocess", True) return render_panels # Handle storing toolbars in memory and fetching them later on diff --git a/docs/architecture.rst b/docs/architecture.rst index 0043f5153..cf5c54951 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -82,7 +82,7 @@ Problematic Parts - Support for async and multi-threading: ``debug_toolbar.middleware.DebugToolbarMiddleware`` is now async compatible and can process async requests. However certain panels such as ``SQLPanel``, ``TimerPanel``, - ``RequestPanel``, ``HistoryPanel`` and ``ProfilingPanel`` aren't fully + ``RequestPanel`` and ``ProfilingPanel`` aren't fully compatible and currently being worked on. For now, these panels are disabled by default when running in async environment. follow the progress of this issue in `Async compatible toolbar project `_. diff --git a/tests/test_integration.py b/tests/test_integration.py index df276d90c..ca31a294c 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -11,7 +11,7 @@ from django.db import connection from django.http import HttpResponse from django.template.loader import get_template -from django.test import RequestFactory +from django.test import AsyncRequestFactory, RequestFactory from django.test.utils import override_settings from debug_toolbar.forms import SignedDataForm @@ -126,6 +126,22 @@ def test_should_render_panels_multiprocess(self): request.META.pop("wsgi.multiprocess") self.assertTrue(toolbar.should_render_panels()) + def test_should_render_panels_asgi(self): + """ + The toolbar not should render the panels on each request when wsgi.multiprocess + is True or missing in case of async context rather than multithreaded + wsgi. + """ + async_request = AsyncRequestFactory().get("/") + # by default ASGIRequest will have wsgi.multiprocess set to True + # but we are still assigning this to true cause this could change + # and we specifically need to check that method returns false even with + # wsgi.multiprocess set to true + async_request.META["wsgi.multiprocess"] = True + toolbar = DebugToolbar(async_request, self.get_response) + toolbar.config["RENDER_PANELS"] = None + self.assertFalse(toolbar.should_render_panels()) + def _resolve_stats(self, path): # takes stats from Request panel request = rf.get(path)