From 27aa8b84148aeb1f0d6b8fcea311b3f9b64f6151 Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Thu, 19 Feb 2026 17:00:07 +0100 Subject: [PATCH 1/2] Ignore safely tasks already enqueued. 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. --- procrastinate/contrib/django/admin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/procrastinate/contrib/django/admin.py b/procrastinate/contrib/django/admin.py index 8c9eb9cf7..b166ff9cc 100644 --- a/procrastinate/contrib/django/admin.py +++ b/procrastinate/contrib/django/admin.py @@ -1,10 +1,12 @@ from __future__ import annotations import json +from contextlib import suppress from typing import Any from django.apps import apps from django.contrib import admin +from django.db import transaction from django.db.models import Prefetch, QuerySet from django.http.request import HttpRequest from django.template.loader import render_to_string @@ -14,6 +16,7 @@ from procrastinate import App, utils from procrastinate.contrib.django.apps import ProcrastinateConfig +from procrastinate.exceptions import AlreadyEnqueued from procrastinate.jobs import Status from . import models @@ -143,9 +146,10 @@ def retry(self, request: HttpRequest, queryset: QuerySet[models.ProcrastinateJob for job in queryset.filter( status__in=(Status.FAILED.value, Status.DOING.value) ): - p_app.job_manager.retry_job_by_id( - job.id, utils.utcnow(), job.priority, job.queue_name, job.lock - ) + with transaction.atomic(), suppress(AlreadyEnqueued): + p_app.job_manager.retry_job_by_id( + job.id, utils.utcnow(), job.priority, job.queue_name, job.lock + ) @admin.action(description="Cancel Job (only 'todo' jobs)") def cancel(self, request: HttpRequest, queryset: QuerySet[models.ProcrastinateJob]): From 50901e73768ec511e7c9f1eabd7d43b5c45ff60e Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Thu, 19 Feb 2026 17:27:07 +0100 Subject: [PATCH 2/2] reverse the order of the context managers to let the savepoint be rolledback --- procrastinate/contrib/django/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/procrastinate/contrib/django/admin.py b/procrastinate/contrib/django/admin.py index b166ff9cc..7145115df 100644 --- a/procrastinate/contrib/django/admin.py +++ b/procrastinate/contrib/django/admin.py @@ -146,7 +146,7 @@ def retry(self, request: HttpRequest, queryset: QuerySet[models.ProcrastinateJob for job in queryset.filter( status__in=(Status.FAILED.value, Status.DOING.value) ): - with transaction.atomic(), suppress(AlreadyEnqueued): + with suppress(AlreadyEnqueued), transaction.atomic(): p_app.job_manager.retry_job_by_id( job.id, utils.utcnow(), job.priority, job.queue_name, job.lock )