fixes related to automation and celery schedules

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-18 12:22:27 +00:00
parent 3a65fb919a
commit 879ef6ff06
9 changed files with 358 additions and 27 deletions

View File

@@ -17,14 +17,14 @@ class AutomationConfigResource(resources.ModelResource):
class Meta:
model = AutomationConfig
fields = ('id', 'site__domain', 'is_enabled', 'frequency', 'scheduled_time',
'within_stage_delay', 'between_stage_delay', 'last_run_at', 'created_at')
'last_run_at', 'next_run_at', 'created_at')
export_order = fields
@admin.register(AutomationConfig)
class AutomationConfigAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = AutomationConfigResource
list_display = ('site', 'is_enabled', 'frequency', 'scheduled_time', 'within_stage_delay', 'between_stage_delay', 'last_run_at')
list_display = ('site', 'is_enabled', 'frequency', 'scheduled_time', 'next_scheduled_run', 'last_run_at')
list_filter = ('is_enabled', 'frequency')
search_fields = ('site__domain',)
actions = [
@@ -34,6 +34,142 @@ class AutomationConfigAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
'bulk_update_delays',
]
def next_scheduled_run(self, obj):
"""
Calculate the next scheduled run based on:
- Celery Beat schedule (every 15 minutes at :00, :15, :30, :45)
- Frequency (daily, weekly, monthly)
- Scheduled time
- 23-hour block after last_run_at
Celery checks window at :00 for :00-:14, at :15 for :15-:29, etc.
So scheduled_time 12:12 will be picked up at the 12:00 check.
"""
from django.utils import timezone
from datetime import timedelta
if not obj.is_enabled:
return 'Disabled'
now = timezone.now()
scheduled_hour = obj.scheduled_time.hour
scheduled_minute = obj.scheduled_time.minute
# Calculate the Celery window start time for this scheduled_time
# If scheduled at :12, Celery checks at :00 (window :00-:14)
# If scheduled at :35, Celery checks at :30 (window :30-:44)
window_start_minute = (scheduled_minute // 15) * 15
# Calculate next occurrence based on frequency
def get_next_celery_pickup():
# Start with today at the Celery window start time
candidate = now.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if obj.frequency == 'daily':
# If time has passed today (Celery already checked this window), next is tomorrow
if candidate <= now:
candidate += timedelta(days=1)
elif obj.frequency == 'weekly':
# Run on Mondays
days_until_monday = (7 - now.weekday()) % 7
if days_until_monday == 0:
# Today is Monday - check if time passed
candidate = now.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if candidate <= now:
days_until_monday = 7
candidate += timedelta(days=7)
else:
candidate = now + timedelta(days=days_until_monday)
candidate = candidate.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
elif obj.frequency == 'monthly':
# Run on 1st of month
candidate = now.replace(
day=1,
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if candidate <= now:
# Next month
if now.month == 12:
candidate = candidate.replace(year=now.year + 1, month=1)
else:
candidate = candidate.replace(month=now.month + 1)
return candidate
next_celery_pickup = get_next_celery_pickup()
# Check 23-hour block
if obj.last_run_at:
earliest_eligible = obj.last_run_at + timedelta(hours=23)
if next_celery_pickup < earliest_eligible:
# Blocked - need to skip to next cycle
if obj.frequency == 'daily':
# Move to next day's window
next_celery_pickup = earliest_eligible.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if next_celery_pickup < earliest_eligible:
next_celery_pickup += timedelta(days=1)
elif obj.frequency == 'weekly':
# Find next Monday after earliest_eligible
days_until_monday = (7 - earliest_eligible.weekday()) % 7
if days_until_monday == 0:
test_candidate = earliest_eligible.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if test_candidate <= earliest_eligible:
days_until_monday = 7
next_celery_pickup = earliest_eligible + timedelta(days=days_until_monday)
next_celery_pickup = next_celery_pickup.replace(
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
elif obj.frequency == 'monthly':
# Find next 1st of month after earliest_eligible
next_celery_pickup = earliest_eligible.replace(
day=1,
hour=scheduled_hour,
minute=window_start_minute,
second=0,
microsecond=0
)
if next_celery_pickup < earliest_eligible:
if earliest_eligible.month == 12:
next_celery_pickup = next_celery_pickup.replace(year=earliest_eligible.year + 1, month=1)
else:
next_celery_pickup = next_celery_pickup.replace(month=earliest_eligible.month + 1)
# Format nicely
return next_celery_pickup.strftime('%b %d, %Y, %-I:%M %p')
next_scheduled_run.short_description = 'Next Scheduled Run'
def bulk_enable(self, request, queryset):
"""Enable selected automation configs"""
updated = queryset.update(is_enabled=True)