From fbfbd8dc126709f5baaf441e2963c1e818a1db07 Mon Sep 17 00:00:00 2001 From: Chiemezuo Date: Wed, 22 Oct 2025 15:54:31 +0100 Subject: [PATCH 1/3] setup preliminary tasks panel logic --- debug_toolbar/panels/tasks.py | 15 +++++++++++++++ debug_toolbar/settings.py | 1 + .../templates/debug_toolbar/panels/tasks.html | 14 ++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 debug_toolbar/panels/tasks.py create mode 100644 debug_toolbar/templates/debug_toolbar/panels/tasks.html diff --git a/debug_toolbar/panels/tasks.py b/debug_toolbar/panels/tasks.py new file mode 100644 index 000000000..36fc9f534 --- /dev/null +++ b/debug_toolbar/panels/tasks.py @@ -0,0 +1,15 @@ +from django.utils.translation import gettext_lazy as _ + +from debug_toolbar.panels import Panel + + +class TasksPanel(Panel): + """ + Panel that displays Django tasks queued or executed during the + processing of the request. + """ + + title = _("Tasks") + template = "debug_toolbar/panels/tasks.html" + + is_async = True diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index d6b9003b6..3fc768ff3 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -81,6 +81,7 @@ def get_config(): "debug_toolbar.panels.cache.CachePanel", "debug_toolbar.panels.signals.SignalsPanel", "debug_toolbar.panels.community.CommunityPanel", + "debug_toolbar.panels.tasks.TasksPanel", "debug_toolbar.panels.redirects.RedirectsPanel", "debug_toolbar.panels.profiling.ProfilingPanel", ] diff --git a/debug_toolbar/templates/debug_toolbar/panels/tasks.html b/debug_toolbar/templates/debug_toolbar/panels/tasks.html new file mode 100644 index 000000000..b37495c40 --- /dev/null +++ b/debug_toolbar/templates/debug_toolbar/panels/tasks.html @@ -0,0 +1,14 @@ + + + + + + + + {% for task in tasks %} + + + + {% endfor %} + +
Task Info
{{ task }}
From 1243d26c22ebdd7e43efeb7c7c0c6dcee0acd765 Mon Sep 17 00:00:00 2001 From: Chiemezuo Date: Wed, 22 Oct 2025 19:28:18 +0100 Subject: [PATCH 2/3] Set dummy TasksPanel methods --- debug_toolbar/panels/tasks.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/debug_toolbar/panels/tasks.py b/debug_toolbar/panels/tasks.py index 36fc9f534..cd2954f4c 100644 --- a/debug_toolbar/panels/tasks.py +++ b/debug_toolbar/panels/tasks.py @@ -1,4 +1,4 @@ -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext_lazy as _, ngettext from debug_toolbar.panels import Panel @@ -13,3 +13,27 @@ class TasksPanel(Panel): template = "debug_toolbar/panels/tasks.html" is_async = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.queued_tasks = [] + + @property + def nav_subtitle(self): + num_tasks = self.get_stats()["total_tasks"] + return ngettext( + "%(num_tasks)d task enqueued", + "%(num_tasks)d tasks enqueued", + num_tasks, + ) % {"num_tasks": num_tasks} + + def generate_stats(self, request, response): + stats = {"tasks": self.queued_tasks, "total_tasks": len(self.queued_tasks)} + + self.record_stats(stats) + + def enable_instrumentation(self): + pass + + def disable_instrumentation(self): + pass From 483cd4e91597b4b1d48447cd64571497cd0bd0c9 Mon Sep 17 00:00:00 2001 From: Chiemezuo Date: Wed, 29 Oct 2025 19:06:12 +0100 Subject: [PATCH 3/3] Experiment with tasks panel --- debug_toolbar/panels/tasks.py | 52 +++++++++++++++++++++++++++++++++-- example/async_/tasks.py | 19 +++++++++++++ example/views.py | 8 ++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 example/async_/tasks.py diff --git a/debug_toolbar/panels/tasks.py b/debug_toolbar/panels/tasks.py index cd2954f4c..e81449fcd 100644 --- a/debug_toolbar/panels/tasks.py +++ b/debug_toolbar/panels/tasks.py @@ -33,7 +33,55 @@ def generate_stats(self, request, response): self.record_stats(stats) def enable_instrumentation(self): - pass + """Hook into task system to collect queued tasks""" + try: + import django + + if django.VERSION < (6, 0): + return + from django.tasks import Task + + print("[TasksPanel] instrumentation enabled:", hasattr(Task, "enqueue")) + + # Store original enqueue method + if hasattr(Task, "enqueue"): + self._original_enqueue = Task.enqueue + + def wrapped_enqueue(task, *args, **kwargs): + result = self._original_enqueue(task, *args, **kwargs).return_value + self._record_task(task, args, kwargs, result) + return result + + Task.enqueue = wrapped_enqueue + except (ImportError, AttributeError): + pass + + def _record_task(self, task, args, kwargs, result): + """Record a task that was queued""" + task_info = { + "name": getattr(task, "__name__", str(task)), + "args": repr(args) if args else "", + "kwargs": repr(kwargs) if kwargs else "", + } + self.queued_tasks.append(task_info) def disable_instrumentation(self): - pass + """Restore original methods""" + try: + from django.tasks import Task + + if hasattr(self, "_original_enqueue"): + Task.enqueue = self._original_enqueue + except (ImportError, AttributeError): + pass + + def _check_tasks_available(self): + """Check if Django tasks system is available""" + try: + import django + + if django.VERSION < (6, 0): + return False + return True + except (ImportError, AttributeError): + return False diff --git a/example/async_/tasks.py b/example/async_/tasks.py new file mode 100644 index 000000000..24dec438c --- /dev/null +++ b/example/async_/tasks.py @@ -0,0 +1,19 @@ +try: + from django.tasks import task +except ImportError: + # Define a fallback decorator + def task(func=None, **kwargs): + def decorator(f): + return f + + return decorator if func is None else decorator(func) + + +@task +def send_welcome_message(message): + return f"Sent message: {message}" + + +@task +def generate_report(report_id): + return f"Report {report_id} generated" diff --git a/example/views.py b/example/views.py index 3e1cb04a6..5a66f124f 100644 --- a/example/views.py +++ b/example/views.py @@ -1,5 +1,6 @@ import asyncio +import django from asgiref.sync import sync_to_async from django.contrib.auth.models import User from django.http import JsonResponse @@ -20,6 +21,13 @@ def jinja2_view(request): async def async_home(request): + if django.VERSION >= (6, 0): + from .async_.tasks import generate_report, send_welcome_message + + # Queue some tasks + send_welcome_message.enqueue(message="hi there") + generate_report.enqueue(report_id=456) + return await sync_to_async(render)(request, "index.html")