Skip to content

[django][admin] Ignore safely tasks already enqueued. - #1512

Open
ticosax wants to merge 2 commits into
procrastinate-org:mainfrom
ticosax:admin-retry-queueing-lock-friendly
Open

[django][admin] Ignore safely tasks already enqueued.#1512
ticosax wants to merge 2 commits into
procrastinate-org:mainfrom
ticosax:admin-retry-queueing-lock-friendly

Conversation

@ticosax

@ticosax ticosax commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Provides, in my opinion, a more user friendly experience, by allowing the submission to work, even if duplicate jobs have been selected.

It could be tedious to expect users to perform a strict selection by eliminating duplicated entries. Happy path scenario:
User selects 1 page of failed jobs to retry.
only the new one will be accepted for a retry, and
will safely keep the other ones for a later attempt.
User repeats until all failed jobs have been retried.

Successful PR Checklist:

  • Tests
    • (not applicable?)
  • Documentation
    • (not applicable?)

PR label(s):

Summary by CodeRabbit

  • Bug Fixes
    • Retry operations in the Django admin now run inside a database transaction for improved reliability.
    • Retry attempts now suppress duplicate-enqueue errors to reduce noisy failures and improve admin behavior.

Provides, in my opinion, a more user friendly experience, by allowing the submission to work,
even if duplicate jobs have been selected.

It could be tedious to expect users to perform a strict selection by eliminating duplicated entries.
Happy path scenario:
 User selects 1 page of failed jobs to retry.
    only the new one will be accepted for a retry, and
    will safely keep the other ones for a later attempt.
 User repeats until all failed jobs have been retried.
@ticosax
ticosax requested a review from a team as a code owner February 19, 2026 16:01
@github-actions github-actions Bot added the PR type: miscellaneous 👾 Contains misc changes label Feb 19, 2026
@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

The retry flow in the Django admin module is wrapped within an atomic database transaction and configured to suppress AlreadyEnqueued exceptions during retry attempts. This modifies error handling for retry operations without altering the public method interface.

Changes

Cohort / File(s) Summary
Django Admin Retry Flow
procrastinate/contrib/django/admin.py
Wrapped the retry operation in an atomic transaction and added suppression of the AlreadyEnqueued exception during retry attempts, changing runtime error handling and transaction scope while keeping the public API unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 I nudge a job back into play,
Wrapped in atoms to guide the way.
If already queued, I hush the sound,
No double-work will now be found.
The rabbit hops — safe retries all round.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[django][admin] Ignore safely tasks already enqueued' directly addresses the main change: making admin retry submission tolerant of duplicate jobs by safely ignoring already-enqueued tasks.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@github-actions

Copy link
Copy Markdown

Coverage report

This PR does not seem to contain any modification to coverable code.

@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

Caution

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

⚠️ Outside diff range comments (1)
procrastinate/contrib/django/admin.py (1)

143-152: ⚠️ Potential issue | 🟡 Minor

No feedback when jobs are skipped as already-enqueued.

When suppress silences AlreadyEnqueued for one or more jobs, the admin action completes with no message. If all selected jobs are skipped, the user sees nothing — indistinguishable from a no-op. Consider tracking a counter and emitting a self.message_user() summary.

💡 Example feedback pattern
 `@admin.action`(description="Retry Job")
 def retry(self, request: HttpRequest, queryset: QuerySet[models.ProcrastinateJob]):
     app_config: ProcrastinateConfig = apps.get_app_config("procrastinate")
     p_app: App = app_config.app
+    retried, skipped = 0, 0
     for job in queryset.filter(
         status__in=(Status.FAILED.value, Status.DOING.value)
     ):
         with suppress(AlreadyEnqueued), transaction.atomic():
-            p_app.job_manager.retry_job_by_id(
-                job.id, utils.utcnow(), job.priority, job.queue_name, job.lock
-            )
+            p_app.job_manager.retry_job_by_id(
+                job.id, utils.utcnow(), job.priority, job.queue_name, job.lock
+            )
+            retried += 1
+        else:
+            skipped += 1
+    self.message_user(
+        request,
+        f"Retried {retried} job(s); {skipped} skipped (already enqueued).",
+    )

(The else clause on the with block does not exist in Python — track the counter inside suppress with a flag or a dedicated counter approach instead.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@procrastinate/contrib/django/admin.py` around lines 143 - 152, The retry
admin action currently suppresses AlreadyEnqueued silently; update
ProcrastinateAdmin.retry to count how many jobs were retried vs skipped: iterate
the same queryset.filter(status__in=(Status.FAILED.value, Status.DOING.value))
and for each job wrap the p_app.job_manager.retry_job_by_id(...) call in the
existing with transaction.atomic(), suppress(AlreadyEnqueued): but set a local
flag or increment a skipped counter inside the suppress scope when
AlreadyEnqueued would have been raised (e.g. use a try/except around the call to
detect AlreadyEnqueued and increment skipped), and increment a retried counter
when the call succeeds; after the loop call self.message_user(request, f"Retried
{retried} job(s); skipped {skipped} already-enqueued job(s).") to surface the
result to the admin UI.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@procrastinate/contrib/django/admin.py`:
- Around line 149-152: The context-manager order around the retry must be
reversed so the atomic block is inside the suppress: wrap
p_app.job_manager.retry_job_by_id(...) with suppress(AlreadyEnqueued) on the
outside and transaction.atomic() on the inside; i.e., change the current "with
transaction.atomic(), suppress(AlreadyEnqueued):" to have suppress as the outer
context and transaction.atomic() as the inner context so transaction.atomic()
sees the exception first and the suppress then swallows AlreadyEnqueued.

---

Outside diff comments:
In `@procrastinate/contrib/django/admin.py`:
- Around line 143-152: The retry admin action currently suppresses
AlreadyEnqueued silently; update ProcrastinateAdmin.retry to count how many jobs
were retried vs skipped: iterate the same
queryset.filter(status__in=(Status.FAILED.value, Status.DOING.value)) and for
each job wrap the p_app.job_manager.retry_job_by_id(...) call in the existing
with transaction.atomic(), suppress(AlreadyEnqueued): but set a local flag or
increment a skipped counter inside the suppress scope when AlreadyEnqueued would
have been raised (e.g. use a try/except around the call to detect
AlreadyEnqueued and increment skipped), and increment a retried counter when the
call succeeds; after the loop call self.message_user(request, f"Retried
{retried} job(s); skipped {skipped} already-enqueued job(s).") to surface the
result to the admin UI.

Comment thread procrastinate/contrib/django/admin.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR type: miscellaneous 👾 Contains misc changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant