diff --git a/debug_toolbar/panels/tasks.py b/debug_toolbar/panels/tasks.py new file mode 100644 index 000000000..e81449fcd --- /dev/null +++ b/debug_toolbar/panels/tasks.py @@ -0,0 +1,87 @@ +from django.utils.translation import gettext_lazy as _, ngettext + +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 + + 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): + """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): + """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/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 @@ +
| Task Info | +
|---|
| {{ task }} | +