Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion procrastinate/contrib/django/admin.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
from __future__ import annotations

import json
import logging

from django.contrib import admin
from django.contrib import admin, messages
from django.db import transaction
from django.db.models import Prefetch
from django.db.models.functions import Now
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import format_html
from django.utils.safestring import mark_safe

from . import models

logger = logging.getLogger(__name__)

JOB_STATUS_EMOJI_MAPPING = {
"todo": "🗓️",
"doing": "🚂",
Expand All @@ -26,6 +31,51 @@ class ProcrastinateEventInline(admin.StackedInline):
model = models.ProcrastinateEvent


@admin.action(
description="Reschedule marked jobs to run now", permissions=["reschedule"]
)
def reschedule(modeladmin, request, queryset):
"""
Reschedule marked jobs to run now.
Only processes jobs that are scheduled for the future and in 'todo' status.
"""
# Use transaction to ensure atomic updates
with transaction.atomic():
# Filter to only jobs that are scheduled for the future and in 'todo' status
eligible_jobs = queryset.select_for_update().filter(
status="todo", scheduled_at__gt=Now()
)

# Count jobs for user feedback
total_selected = queryset.count()
eligible_count = eligible_jobs.count()
skipped_count = total_selected - eligible_count

try:
# Update the scheduled_at time to now for eligible jobs
updated_count = eligible_jobs.update(scheduled_at=Now())

if updated_count > 0:
messages.success(
request,
f"Successfully moved {updated_count} job{'s' if updated_count != 1 else ''} to run immediately.",
)

if skipped_count > 0:
messages.info(
request,
f"Skipped {skipped_count} job{'s' if skipped_count != 1 else ''} "
"(not scheduled for future or not in 'todo' status).",
)

except Exception as e:
logger.exception("Failed to reschedule jobs")
messages.error(
request,
f"Failed to reschedule jobs: {e}",
)


@admin.register(models.ProcrastinateJob)
class ProcrastinateJobAdmin(admin.ModelAdmin):
fields = [
Expand Down Expand Up @@ -57,6 +107,7 @@ class ProcrastinateJobAdmin(admin.ModelAdmin):
"priority",
]
inlines = [ProcrastinateEventInline]
actions = [reschedule]

def get_readonly_fields(
self,
Expand All @@ -78,6 +129,12 @@ def has_add_permission(self, request, obj=None):
def has_delete_permission(self, request, obj=None):
return False

def has_reschedule_permission(self, request, obj=None):
"""
Check if the user has change permission on ProcrastinateJob.
"""
return super().has_change_permission(request)

def get_queryset(self, request):
return (
super()
Expand Down