create update schedule through view

This commit is contained in:
Simon
2024-08-01 17:25:04 +02:00
parent 37df4f8b5b
commit 3d12fe7b5e
3 changed files with 111 additions and 41 deletions

View File

@@ -6,6 +6,7 @@ Functionality:
from datetime import datetime
from appsettings.src.config import AppConfig
from celery.schedules import crontab
from common.src.env_settings import EnvironmentSettings
from django.utils import dateformat
from django_celery_beat.models import CrontabSchedule
@@ -24,33 +25,24 @@ class ScheduleBuilder:
"run_backup": "0 18 0",
"version_check": "0 11 *",
}
CONFIG = {
"check_reindex_days": "check_reindex",
"run_backup_rotate": "run_backup",
"update_subscribed_notify": "update_subscribed",
"download_pending_notify": "download_pending",
"check_reindex_notify": "check_reindex",
}
MSG = "message:setting"
def __init__(self):
self.config = AppConfig().config
def update_schedule_conf(self, form_post):
"""process form post, schedules need to be validated before"""
for key, value in form_post.items():
if not value:
continue
def update_schedule(
self, task_name: str, cron_schedule: str, schedule_conf: dict | None
) -> None:
"""update schedule"""
if cron_schedule == "auto":
cron_schedule = self.SCHEDULES[task_name]
if key in self.SCHEDULES:
if value == "auto":
value = self.SCHEDULES.get(key)
if cron_schedule:
_ = self.get_set_task(task_name, cron_schedule)
_ = self.get_set_task(key, value)
continue
if key in self.CONFIG:
self.set_config(key, value)
if schedule_conf:
for key, value in schedule_conf.items():
self.set_config(task_name, key, value)
def get_set_task(self, task_name, schedule=False):
"""get task"""
@@ -73,21 +65,77 @@ class ScheduleBuilder:
return task
@staticmethod
def get_set_cron_tab(schedule):
def get_set_cron_tab(schedule: str) -> CrontabSchedule:
"""needs to be validated before"""
kwargs = dict(zip(["minute", "hour", "day_of_week"], schedule.split()))
kwargs.update({"timezone": EnvironmentSettings.TZ})
crontab, _ = CrontabSchedule.objects.get_or_create(**kwargs)
task_crontab, _ = CrontabSchedule.objects.get_or_create(**kwargs)
return crontab
return task_crontab
def set_config(self, key, value):
"""set task_config"""
task_name = self.CONFIG.get(key)
if not task_name:
raise ValueError("invalid config key")
def set_config(self, task_name: str, key: str, value) -> None:
"""set task_config, validate before"""
try:
task = CustomPeriodicTask.objects.get(name=task_name)
task.task_config.update({key: value})
task.save()
except CustomPeriodicTask.DoesNotExist:
pass
task = CustomPeriodicTask.objects.get(name=task_name)
config_key = key.split(f"{task_name}_")[-1]
task.task_config.update({config_key: value})
task.save()
class CrontabValidator:
"""validate crontab"""
CONFIG = {
"check_reindex": ["days"],
"run_backup": ["rotate"],
}
@staticmethod
def validate_fields(cron_fields: str) -> None:
"""expect 3 cron fields"""
if not len(cron_fields) == 3:
raise ValueError("expected three cron schedule fields")
@staticmethod
def validate_minute(minute_field: str):
"""expect minute int"""
if not minute_field.isdigit():
raise ValueError("Invalid value for minutes. Must be an integer.")
minutes = int(minute_field)
if not 0 <= minutes <= 59:
raise ValueError("Invalid minutes. Must be between 0 and 59.")
@staticmethod
def validate_cron_tab(minute, hour, day_of_week):
"""check if crontab can be created"""
try:
crontab(minute=minute, hour=hour, day_of_week=day_of_week)
except ValueError as err:
raise ValueError(f"invalid crontab: {err}") from err
def validate_cron(self, cron_expression):
"""create crontab schedule"""
if not cron_expression or cron_expression == "auto":
return
cron_fields = cron_expression.split()
self.validate_fields(cron_fields)
minute, hour, day_of_week = cron_fields
self.validate_minute(minute)
self.validate_cron_tab(minute, hour, day_of_week)
def validate_config(self, task_name: str, schedule_config: dict):
"""validate config for given task"""
if not schedule_config:
return
config_keys = self.CONFIG.get(task_name)
if not config_keys:
raise ValueError(f"task '{task_name}' doesn't take config")
for key in schedule_config:
if key not in config_keys:
raise ValueError(f"invalid config key for task '{task_name}'")

View File

@@ -20,7 +20,7 @@ urlpatterns = [
name="api-task-id",
),
path(
"schedule/",
"schedule/<slug:task_name>/",
views.ScheduleView.as_view(),
name="api-schedule",
),

View File

@@ -1,8 +1,10 @@
"""all task API views"""
from common.views_base import AdminOnly, ApiBaseView
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from task.models import CustomPeriodicTask
from task.src.config_schedule import CrontabValidator, ScheduleBuilder
from task.src.notify import Notifications, get_all_notifications
from task.src.task_config import TASK_CONFIG
from task.src.task_manager import TaskCommand, TaskManager
@@ -118,20 +120,40 @@ class TaskIDView(ApiBaseView):
class ScheduleView(ApiBaseView):
"""resolves to /api/task/schedule/<task-name>/
POST: create/update schedule for task with config
- example: {"schedule": "0 0 *", "config": {"days": 90}}
DEL: delete schedule for task
"""
permission_classes = [AdminOnly]
def delete(self, request):
"""delete schedule by task_name query"""
task_name = request.data.get("task_name")
try:
task = CustomPeriodicTask.objects.get(name=task_name)
except CustomPeriodicTask.DoesNotExist:
message = {"message": "task_name not found"}
return Response(message, status=404)
def post(self, request, task_name):
"""create/update schedule for task"""
cron_schedule = request.data.get("schedule")
schedule_config = request.data.get("config")
if not cron_schedule and not schedule_config:
message = {"message": "expected schedule or config key"}
return Response(message, status=400)
try:
validator = CrontabValidator()
validator.validate_cron(cron_schedule)
validator.validate_config(task_name, schedule_config)
except ValueError as err:
return Response({"message": str(err)}, status=400)
ScheduleBuilder().update_schedule(
task_name, cron_schedule, schedule_config
)
message = f"update schedule for task {task_name}"
if schedule_config:
message += f" with config {schedule_config}"
return Response({"message": message})
def delete(self, request, task_name):
"""delete schedule by task_name query"""
task = get_object_or_404(CustomPeriodicTask, name=task_name)
_ = task.delete()
return Response({"success": True})