Skip to content

Dj celery panel#2 task inspect backend - #3

Merged
yassi merged 6 commits into
mainfrom
dj-celery-panel#2_task_inspect_backend
Jan 20, 2026
Merged

Dj celery panel#2 task inspect backend#3
yassi merged 6 commits into
mainfrom
dj-celery-panel#2_task_inspect_backend

Conversation

@yassi

@yassi yassi commented Jan 20, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Real-time task inspection backend for live Celery task monitoring
    • Backend info panel shown on admin pages (backend name and data source)
  • Improvements

    • Task filtering UI (sidebar dropdown), preserved filters in links, and backend-driven defaults
    • Improved task search, status mapping (STARTED→ACTIVE, RESERVED), pagination, and UI styling for backend info/filter
  • Documentation

    • Expanded docs for swappable/custom backends and backend metadata
  • Tests

    • Added tests for DB and inspect backends, filtering, search, pagination, and metadata display
  • Chore

    • Project version bumped to 0.3.0

✏️ Tip: You can customize this high-level summary in your review settings.

@codecov

codecov Bot commented Jan 20, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds backend metadata support and exposes a new CeleryTasksInspectBackend for real-time task inspection; propagates backend_info through views, updates templates and styles to render backend metadata and optional task filters, and extends tests and docs to cover DB and Inspect backends.

Changes

Cohort / File(s) Summary
Public API & base
dj_celery_panel/celery_utils/__init__.py, dj_celery_panel/celery_utils/base.py
Exposes CeleryTasksInspectBackend in package exports. Adds CeleryAbstractInterface.get_backend_info() returning backend metadata: name, module, full_path, description, data_source.
Tasks backends & interface
dj_celery_panel/celery_utils/tasks.py
Extends CeleryTasksInterface.get_tasks with optional filter_type; adds get_available_filters() and get_default_filter(). Enhances CeleryTasksDjangoCeleryResultsBackend with metadata constants and filter/search support. Adds CeleryTasksInspectBackend implementing live inspect-based get_tasks, get_task_detail, and metadata constants.
Queues & Workers backends
dj_celery_panel/celery_utils/queues.py, dj_celery_panel/celery_utils/workers.py
Adds BACKEND_DESCRIPTION and DATA_SOURCE to inspect-style backends. Reformats worker JSON field construction into multi-line conditionals (no behavioral change).
Views
dj_celery_panel/views.py
Adds backend_info to contexts for tasks, workers, queues, task_detail, queue_detail, worker_detail. Tasks view: reads filter param, normalizes it, passes filter_type to backend, builds task_filters, current_filter, and show_filters.
Template fragment
dj_celery_panel/templates/admin/dj_celery_panel/_backend_info.html
New snippet that conditionally renders backend name and data_source.
Template updates
dj_celery_panel/templates/admin/dj_celery_panel/tasks.html, .../workers.html, .../queues.html, .../task_detail.html, .../queue_detail.html, .../worker_detail.html
Inserts _backend_info.html, restructures outer containers, maps STARTEDACTIVE, adds RESERVED handling, introduces explicit not-found states. tasks.html adds filter UI and preserves search/pagination while propagating filter param.
Styles
dj_celery_panel/templates/admin/dj_celery_panel/styles.css
Adds backend info block styles and filter dropdown/sidebar rules with dark-mode variants (additive CSS).
Documentation
docs/configuration.md, docs/features.md
Documents backend metadata attributes (BACKEND_DESCRIPTION, DATA_SOURCE, DEFAULT_FILTER, AVAILABLE_FILTERS), details CeleryTasksInspectBackend, and describes backend information display and filter usage.
Tests
tests/test_tasks.py
Adds tests for DB-backed and Inspect backends: filtering, searching, pagination, empty/error cases, metadata display, inspect-specific active-task behavior, and pagination.
Examples & config
example_project/example_project/settings.py, pyproject.toml
Adds commented example to use CeleryTasksInspectBackend in settings and bumps package version 0.2.00.3.0.
Queue/task-detail minor
dj_celery_panel/templates/admin/dj_celery_panel/queue_detail.html
Moves backend info include, removes redundant container divs, and adjusts closing tags for new structure.

Sequence Diagram(s)

sequenceDiagram
    participant Client as User
    participant View as Django View
    participant Interface as CeleryTasksInterface
    participant Backend as Tasks Backend
    participant InspectAPI as Celery Inspect API
    participant Template as Template Renderer

    Client->>View: GET /tasks?filter=active&search=foo
    View->>Interface: get_tasks(search_query="foo", filter_type="active")
    Interface->>Backend: get_tasks(search_query="foo", filter_type="active")
    alt Database backend
        Backend->>Backend: DB query (status filter + Q(name|id) search)
        Backend->>Backend: paginate & format TaskListPage
    else Inspect backend
        Backend->>InspectAPI: inspect.active()/inspect.reserved()/inspect.scheduled()
        InspectAPI-->>Backend: worker task dicts
        Backend->>Backend: in-memory filter/search, paginate, format TaskListPage
    end
    Backend-->>Interface: TaskListPage
    Interface-->>View: TaskListPage
    View->>View: get_available_filters(), get_default_filter(), get_backend_info()
    View->>Template: render(context with backend_info, task_filters, current_filter)
    Template-->>Client: HTML (includes backend info and optional filter UI)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through code and found a new way,

Backend names shining, live tasks at play.
Filters to nibble, templates dressed anew,
Tests and docs cheering — a fresh panel view!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: it introduces a new Celery Inspect backend for real-time task monitoring, which is the primary feature across the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 96.55% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dj_celery_panel/views.py (1)

101-157: Pass effective filter to template to maintain consistency across pagination and empty state messages.

The filter sidebar correctly marks the applied filter as selected using effective_filter, but pagination links, the search form, and empty state messaging use current_filter (the raw query param). When a backend defines DEFAULT_FILTER (e.g., CeleryTasksInspectBackend with DEFAULT_FILTER="active"), pagination links fail to preserve the effective filter across page changes because they only include current_filter.

🔧 Suggested fix
- filter_type = request.GET.get("filter", None)
+ filter_param_present = "filter" in request.GET
+ filter_type = request.GET.get("filter") if filter_param_present else None

- effective_filter = (
-     filter_type if filter_type is not None else task_interface.get_default_filter()
- )
+ if filter_param_present and (filter_type == "" or filter_type is None):
+     effective_filter = None
+ elif filter_type is None:
+     effective_filter = task_interface.get_default_filter()
+ else:
+     effective_filter = filter_type

...
- "current_filter": filter_type,
+ "current_filter": effective_filter,
+ "filter_param_present": filter_param_present,
🤖 Fix all issues with AI agents
In `@dj_celery_panel/celery_utils/tasks.py`:
- Around line 249-337: In get_tasks, validate the incoming filter_type (e.g.,
only accept the supported "active" or fall back to DEFAULT_FILTER) and enforce
pagination bounds: ensure per_page and page are positive integers (clamp
per_page to at least 1 and page to at least 1), compute total_pages and then
clamp page to be no greater than total_pages (or adjust slicing logic
accordingly) so negative/zero page values or per_page do not produce unexpected
slices; update only the get_tasks function to perform these checks before
calling inspect and computing start_idx/end_idx.

In `@dj_celery_panel/templates/admin/dj_celery_panel/tasks.html`:
- Around line 114-121: The pagination and filter link hrefs in the template use
unescaped user input (search_query, current_filter, and filter.value) which can
break URLs; update all hrefs that build query strings (the pagination anchors
referencing page/previous_page/next_page/total_pages and the filter sidebar
links) to URL-encode these variables by applying the template filter (e.g., use
search_query|urlencode, current_filter|urlencode, and filter.value|urlencode) so
every user-provided value is safely encoded in the query string.
- Around line 34-36: Track presence of the filter parameter and treat empty
string as an explicit "no filter": add filter_param_present = "filter" in
request.GET to the view context, update the template snippets that currently use
{% if current_filter %} to {% if filter_param_present %} so the hidden input is
rendered even for an empty filter, change any occurrences of `{% if filter.value
%}` to `{% if filter.value != None %}` to allow empty-string values to pass
through, and in the backend code that checks filter_type replace `if
filter_type:` with `if filter_type is not None:` so an explicit empty filter is
honored instead of falling back to DEFAULT_FILTER (apply the same updates to the
second block around lines 160-166).

In `@docs/configuration.md`:
- Around line 167-170: The example class CustomTasksBackend in the docs does not
inherit the stated base class CeleryAbstractInterface; update the snippet so
CustomTasksBackend subclasses CeleryAbstractInterface (or adjust the prose to no
longer claim it should extend CeleryAbstractInterface). Locate the example
definition for CustomTasksBackend in the doc and either change its declaration
to "class CustomTasksBackend(CeleryAbstractInterface):" or modify the
surrounding text to remove/clarify the inheritance requirement.

Comment on lines +249 to +337
def get_tasks(
self, search_query=None, page=1, per_page=50, filter_type=None
) -> TaskListPage:
"""
Get active tasks from Celery inspect API.

Args:
search_query: Optional search query to filter by task name or task ID
page: Page number for pagination
per_page: Number of tasks per page
filter_type: Type of tasks to retrieve (only "active" supported).
Should always be provided by the interface using DEFAULT_FILTER.

Returns:
TaskListPage with task information
"""
try:
all_tasks = []

# Get active tasks from all workers using CeleryInspector
try:
inspect = self.app.control.inspect()
active_tasks = inspect.active()
if active_tasks:
for worker, tasks in active_tasks.items():
for task in tasks:
task["worker"] = worker
task["state"] = "ACTIVE"
all_tasks.append(task)
except Exception as e:
# Log but don't fail - workers might be temporarily unavailable
import logging

logging.warning(f"Failed to get active tasks: {e}")

# Apply search filter (search by both task name and task ID)
if search_query:
search_lower = search_query.lower()
all_tasks = [
task
for task in all_tasks
if search_lower in task.get("name", "").lower()
or search_lower in task.get("id", "").lower()
]

# Calculate pagination
total_count = len(all_tasks)
total_pages = (total_count + per_page - 1) // per_page
start_idx = (page - 1) * per_page
end_idx = start_idx + per_page
paginated_tasks = all_tasks[start_idx:end_idx]

# Format tasks
formatted_tasks = []
for task in paginated_tasks:
formatted_task = {
"id": task.get("id", "N/A"),
"name": task.get("name", "Unknown"),
"status": task.get("state", "UNKNOWN"),
"worker": task.get("worker"),
"args": task.get("args"),
"kwargs": task.get("kwargs"),
"date_created": None, # Not available in inspect API
"date_done": None,
"date_started": None,
"result": None,
}

# Add ETA for scheduled tasks
if "eta" in task:
formatted_task["eta"] = task["eta"]

# Add time_start for active tasks
if "time_start" in task:
formatted_task["time_start"] = task["time_start"]

formatted_tasks.append(formatted_task)

return TaskListPage(
tasks=formatted_tasks,
total_count=total_count,
page=page,
per_page=per_page,
total_pages=total_pages,
has_previous=page > 1,
has_next=page < total_pages,
previous_page=page - 1 if page > 1 else None,
next_page=page + 1 if page < total_pages else None,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate filter_type and clamp pagination bounds.

filter_type is currently ignored and negative/zero page values can yield unexpected slicing. A small guard makes behavior consistent and avoids confusing UI states.

✅ Suggested guard rails
     def get_tasks(
         self, search_query=None, page=1, per_page=50, filter_type=None
     ) -> TaskListPage:
         """
         Get active tasks from Celery inspect API.
@@
         """
         try:
+            # Normalize pagination
+            if per_page <= 0:
+                per_page = 50
+            if page < 1:
+                page = 1
+
+            # Enforce supported filter(s)
+            if filter_type is not None and filter_type != "active":
+                return TaskListPage(
+                    tasks=[],
+                    total_count=0,
+                    page=page,
+                    per_page=per_page,
+                    total_pages=0,
+                    error="Unsupported filter for inspect backend.",
+                )
             all_tasks = []
🧰 Tools
🪛 Ruff (0.14.13)

250-250: Unused method argument: filter_type

(ARG002)


278-278: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@dj_celery_panel/celery_utils/tasks.py` around lines 249 - 337, In get_tasks,
validate the incoming filter_type (e.g., only accept the supported "active" or
fall back to DEFAULT_FILTER) and enforce pagination bounds: ensure per_page and
page are positive integers (clamp per_page to at least 1 and page to at least
1), compute total_pages and then clamp page to be no greater than total_pages
(or adjust slicing logic accordingly) so negative/zero page values or per_page
do not produce unexpected slices; update only the get_tasks function to perform
these checks before calling inspect and computing start_idx/end_idx.

Comment on lines +34 to +36
{% if current_filter %}
<input type="hidden" name="filter" value="{{ current_filter }}">
{% endif %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the template file and examine its structure
find . -name "tasks.html" -type f | head -5

Repository: yassi/dj-celery-panel

Length of output: 125


🏁 Script executed:

# Check the overall repository structure
git ls-files | grep -E "(template|view|celery)" | head -20

Repository: yassi/dj-celery-panel

Length of output: 1044


🏁 Script executed:

# Read the tasks.html file to examine the filter logic
cat -n ./dj_celery_panel/templates/admin/dj_celery_panel/tasks.html | head -180

Repository: yassi/dj-celery-panel

Length of output: 10137


🏁 Script executed:

# Look for view files that might handle the tasks view
find . -name "*.py" -type f | xargs grep -l "tasks.html" | head -10

Repository: yassi/dj-celery-panel

Length of output: 91


🏁 Script executed:

# Read the views.py file to understand filter handling
cat -n ./dj_celery_panel/views.py

Repository: yassi/dj-celery-panel

Length of output: 11644


🏁 Script executed:

# Look for filter-related code in celery_utils
grep -r "get_available_filters\|get_default_filter\|filter_type" dj_celery_panel/celery_utils/ -A 5 -B 2

Repository: yassi/dj-celery-panel

Length of output: 4638


🏁 Script executed:

# Check the CeleryTasksInterface class specifically
cat -n ./dj_celery_panel/celery_utils/tasks.py | head -150

Repository: yassi/dj-celery-panel

Length of output: 5944


🏁 Script executed:

# Continue reading the tasks.py file
tail -n +150 ./dj_celery_panel/celery_utils/tasks.py | head -100

Repository: yassi/dj-celery-panel

Length of output: 3466


🏁 Script executed:

# Search for DEFAULT_FILTER definitions in other backends
grep -r "DEFAULT_FILTER" dj_celery_panel/celery_utils/

Repository: yassi/dj-celery-panel

Length of output: 561


🏁 Script executed:

# Check if filter_param_present is used anywhere in the codebase
grep -r "filter_param_present" dj_celery_panel/

Repository: yassi/dj-celery-panel

Length of output: 47


🏁 Script executed:

# Verify the AVAILABLE_FILTERS structure and "All" filter value
grep -A 10 "AVAILABLE_FILTERS" dj_celery_panel/celery_utils/tasks.py

Repository: yassi/dj-celery-panel

Length of output: 1004


🏁 Script executed:

# Check what happens when filter_type is an empty string in the backend
grep -B 5 -A 10 "if filter_type:" dj_celery_panel/celery_utils/tasks.py

Repository: yassi/dj-celery-panel

Length of output: 584


🏁 Script executed:

# Look at the request.GET handling to understand empty vs missing filter param
grep -B 5 -A 15 "def tasks" dj_celery_panel/views.py | head -40

Repository: yassi/dj-celery-panel

Length of output: 599


🏁 Script executed:

# Check if there's any other handling of empty filter strings
grep -r "filter.*=.*\"\"" dj_celery_panel/ --include="*.py"

Repository: yassi/dj-celery-panel

Length of output: 47


🏁 Script executed:

# Verify the issue: when user clicks the "All" filter link, what URL is generated?
# Look at line 165 more carefully
sed -n '157,170p' ./dj_celery_panel/templates/admin/dj_celery_panel/tasks.html

Repository: yassi/dj-celery-panel

Length of output: 701


Implement filter parameter presence tracking to preserve empty filters and allow "All" to override backend defaults.

The current template logic gates on truthiness, which causes two issues:

  1. Clicking "All" omits the filter parameter entirely, allowing the backend's DEFAULT_FILTER to take precedence even when explicitly selecting "All" (e.g., with the "active" backend, users cannot view all tasks).

  2. An explicit empty filter (?filter=) is not persisted because the hidden input is gated on {% if current_filter %}, which is falsy for empty strings. The filter param is then dropped on subsequent searches.

Required changes:

  • View must track whether the filter parameter was present: add filter_param_present = "filter" in request.GET to context
  • Template must gate on parameter presence rather than truthiness: use {% if filter_param_present %} instead of {% if current_filter %}
  • Ensure empty filter passes through: use {% if filter.value != None %} instead of {% if filter.value %}
  • Backend must handle explicit empty filter: change if filter_type: to if filter_type is not None: to treat empty string as "no filter"

Also applies to: lines 160-166

🤖 Prompt for AI Agents
In `@dj_celery_panel/templates/admin/dj_celery_panel/tasks.html` around lines 34 -
36, Track presence of the filter parameter and treat empty string as an explicit
"no filter": add filter_param_present = "filter" in request.GET to the view
context, update the template snippets that currently use {% if current_filter %}
to {% if filter_param_present %} so the hidden input is rendered even for an
empty filter, change any occurrences of `{% if filter.value %}` to `{% if
filter.value != None %}` to allow empty-string values to pass through, and in
the backend code that checks filter_type replace `if filter_type:` with `if
filter_type is not None:` so an explicit empty filter is honored instead of
falling back to DEFAULT_FILTER (apply the same updates to the second block
around lines 160-166).

Comment on lines +114 to 121
<a href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}{% if current_filter %}&filter={{ current_filter }}{% endif %}">{% trans 'First' %}</a>
<a href="?page={{ previous_page }}{% if search_query %}&search={{ search_query }}{% endif %}{% if current_filter %}&filter={{ current_filter }}{% endif %}" class="prev">{% trans 'Previous' %}</a>
{% endif %}
<span class="this-page">{{ page }} {% trans 'of' %} {{ total_pages }}</span>
{% if has_next %}
<a href="?page={{ next_page }}{% if search_query %}&search={{ search_query }}{% endif %}" class="next">{% trans 'Next' %}</a>
<a href="?page={{ total_pages }}{% if search_query %}&search={{ search_query }}{% endif %}">{% trans 'Last' %}</a>
<a href="?page={{ next_page }}{% if search_query %}&search={{ search_query }}{% endif %}{% if current_filter %}&filter={{ current_filter }}{% endif %}" class="next">{% trans 'Next' %}</a>
<a href="?page={{ total_pages }}{% if search_query %}&search={{ search_query }}{% endif %}{% if current_filter %}&filter={{ current_filter }}{% endif %}">{% trans 'Last' %}</a>
{% endif %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "tasks\.html"

Repository: yassi/dj-celery-panel

Length of output: 191


🏁 Script executed:

fd "tasks.html" --type f

Repository: yassi/dj-celery-panel

Length of output: 191


🏁 Script executed:

wc -l dj_celery_panel/templates/admin/dj_celery_panel/tasks.html

Repository: yassi/dj-celery-panel

Length of output: 127


🏁 Script executed:

cat -n dj_celery_panel/templates/admin/dj_celery_panel/tasks.html | sed -n '100,130p'

Repository: yassi/dj-celery-panel

Length of output: 2017


🏁 Script executed:

cat -n dj_celery_panel/templates/admin/dj_celery_panel/tasks.html

Repository: yassi/dj-celery-panel

Length of output: 10137


🏁 Script executed:

rg "search_query|current_filter|filter\.value" dj_celery_panel/templates/admin/dj_celery_panel/tasks.html

Repository: yassi/dj-celery-panel

Length of output: 1541


🌐 Web query:

Django urlencode filter template

💡 Result:

The Django template filter urlencode percent-encodes a value for use in a URL. By default '/' is treated as safe (not escaped); you can pass an optional string of characters that should NOT be escaped, or pass an empty string "" to escape every character.

Examples:

  • Default: {{ value|urlencode }} — leaves '/' unescaped. [1]
  • Escape everything (including /): {{ value|urlencode:"" }}. [1]
  • You can apply urlencode to a URL produced by {% url %} by saving the url first and then filtering it: {% url 'name' arg as my_url %} {{ my_url|urlencode }} (or use {% filter urlencode %}...{% endfilter %}). [2]

References:
[1] Django docs — Built-in template tags and filters (urlencode).
[2] StackOverflow — encoding URL in Django template (using {% url ... as var %} and filter block).


URL-encode all query parameters in pagination and filter links.

Unescaped search_query, current_filter, and filter.value can break query strings when they contain &, =, or spaces. Apply |urlencode to every user-input variable inserted into href attributes:

  • Pagination links (lines 114, 115, 119, 120): search_query and current_filter
  • Filter sidebar links (lines 161, 165): filter.value and search_query
🔧 Example fix
-<a href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}{% if current_filter %}&filter={{ current_filter }}{% endif %}">
+<a href="?page=1{% if search_query %}&search={{ search_query|urlencode }}{% endif %}{% if current_filter %}&filter={{ current_filter|urlencode }}{% endif %}">

Apply the same pattern to all pagination links and filter sidebar links.

🤖 Prompt for AI Agents
In `@dj_celery_panel/templates/admin/dj_celery_panel/tasks.html` around lines 114
- 121, The pagination and filter link hrefs in the template use unescaped user
input (search_query, current_filter, and filter.value) which can break URLs;
update all hrefs that build query strings (the pagination anchors referencing
page/previous_page/next_page/total_pages and the filter sidebar links) to
URL-encode these variables by applying the template filter (e.g., use
search_query|urlencode, current_filter|urlencode, and filter.value|urlencode) so
every user-provided value is safely encoded in the query string.

Comment thread docs/configuration.md
Comment on lines +167 to 170
from dj_celery_panel.celery_utils import TaskListPage, TaskDetailPage

class CustomTasksBackend(CeleryAbstractInterface):
class CustomTasksBackend:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Custom backend example doesn’t match the stated inheritance.

The section says to extend CeleryAbstractInterface, but the example class doesn’t. Either update the text or make the snippet inherit the base class for clarity.

📘 Suggested doc tweak
-from dj_celery_panel.celery_utils import TaskListPage, TaskDetailPage
+from dj_celery_panel.celery_utils import CeleryAbstractInterface, TaskListPage, TaskDetailPage

-class CustomTasksBackend:
+class CustomTasksBackend(CeleryAbstractInterface):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from dj_celery_panel.celery_utils import TaskListPage, TaskDetailPage
class CustomTasksBackend(CeleryAbstractInterface):
class CustomTasksBackend:
"""
from dj_celery_panel.celery_utils import CeleryAbstractInterface, TaskListPage, TaskDetailPage
class CustomTasksBackend(CeleryAbstractInterface):
"""
🤖 Prompt for AI Agents
In `@docs/configuration.md` around lines 167 - 170, The example class
CustomTasksBackend in the docs does not inherit the stated base class
CeleryAbstractInterface; update the snippet so CustomTasksBackend subclasses
CeleryAbstractInterface (or adjust the prose to no longer claim it should extend
CeleryAbstractInterface). Locate the example definition for CustomTasksBackend
in the doc and either change its declaration to "class
CustomTasksBackend(CeleryAbstractInterface):" or modify the surrounding text to
remove/clarify the inheritance requirement.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/test_tasks.py`:
- Around line 128-165: The test test_inspect_backend_displays_active_tasks is
brittle because it assumes order by indexing response.context["tasks"][0];
change assertions to be order-independent by finding the task dict with id
"task-123" (e.g., iterate or use a comprehension/search over
response.context["tasks"]) and assert its fields (name, status, worker) instead
of relying on positional indexing; update similar checks for other tasks to
match by id.
🧹 Nitpick comments (1)
tests/test_tasks.py (1)

249-267: Assert the “silent failure” expectation.

The test comments say no warning should be shown, but there’s no assertion. Add one to lock the behavior.

✅ Suggested assertion
         self.assertEqual(len(response.context["tasks"]), 0)
         self.assertEqual(response.context["total_count"], 0)
         # Backend is designed to fail silently for temporary worker issues
         # No warning message should be shown to avoid alarming users
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 0)

Comment thread tests/test_tasks.py
Comment on lines +128 to +165
@override_settings(
DJ_CELERY_PANEL_SETTINGS={
"tasks_backend": "dj_celery_panel.celery_utils.CeleryTasksInspectBackend"
}
)
@patch("celery.app.control.Inspect.active")
def test_inspect_backend_displays_active_tasks(self, mock_active):
"""Test that inspect backend displays active tasks from workers."""
mock_active.return_value = {
"worker1@localhost": [
{
"id": "task-123",
"name": "app.tasks.process_data",
"args": [1, 2, 3],
"kwargs": {"priority": "high"},
"time_start": 1234567890.0,
},
{
"id": "task-456",
"name": "app.tasks.send_email",
"args": ["user@example.com"],
"kwargs": {},
"time_start": 1234567891.0,
},
]
}

response = self.client.get(reverse("dj_celery_panel:tasks"))

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["tasks"]), 2)
# Check first task
task1 = response.context["tasks"][0]
self.assertEqual(task1["id"], "task-123")
self.assertEqual(task1["name"], "app.tasks.process_data")
self.assertEqual(task1["status"], "ACTIVE")
self.assertEqual(task1["worker"], "worker1@localhost")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid order-dependent assertions for active tasks.

If the backend reorders tasks (e.g., by time or worker), indexing [0] can be brittle. Prefer matching by ID.

🔧 Suggested tweak (order-independent)
-        # Check first task
-        task1 = response.context["tasks"][0]
+        # Check expected task without relying on ordering
+        task1 = next(t for t in response.context["tasks"] if t["id"] == "task-123")
         self.assertEqual(task1["id"], "task-123")
         self.assertEqual(task1["name"], "app.tasks.process_data")
         self.assertEqual(task1["status"], "ACTIVE")
         self.assertEqual(task1["worker"], "worker1@localhost")
🤖 Prompt for AI Agents
In `@tests/test_tasks.py` around lines 128 - 165, The test
test_inspect_backend_displays_active_tasks is brittle because it assumes order
by indexing response.context["tasks"][0]; change assertions to be
order-independent by finding the task dict with id "task-123" (e.g., iterate or
use a comprehension/search over response.context["tasks"]) and assert its fields
(name, status, worker) instead of relying on positional indexing; update similar
checks for other tasks to match by id.

@yassi yassi linked an issue Jan 20, 2026 that may be closed by this pull request
@yassi
yassi merged commit 3200b9a into main Jan 20, 2026
5 of 6 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inspect based backed for populating tasks page

1 participant