fixes related to automation and celery schedules
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user