Задания Cron из БД в Django (через APscheduler) - PullRequest
0 голосов
/ 27 апреля 2020

У меня есть некоторые записи в БД, такие как

class Rules(models.Model):
server = models.ForeignKey(Servers, on_delete=models.CASCADE)
rule_text = models.CharField(max_length=2000)

, т.е. некоторые правила, связанные с сервером. Каждое правило представляет собой строку crontab, такую ​​как

0 9-17/2 * * 1-5

Я хочу вызывать server_stop (сервер) и server_start (сервер), основываясь на всех правилах, которые есть в БД (и добавлять / редактировать / удалять правила)

Возможно ли это?

1 Ответ

0 голосов

Мб кто-нибудь сталкивается с той же проблемой, поэтому я покажу свою реализацию

Вам нужно:

APScheduler # for all work, 
django-apscheduler #only for store scheduler jobs in Django ORM, in my case in Postgres

Прежде всего, вам нужно сделать один планировщик (из APScheduler) и django -apscheduler), чтобы у вас всегда был один планировщик и много заданий

from pytz import utc
from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler.jobstores import DjangoJobStore
from django_apscheduler.jobstores import register_events


class MetaSingleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class SchedulerHandler(metaclass=MetaSingleton):
    scheduler = None

    def retrieve_scheduler(self):
        if self.scheduler is None:
            self.scheduler = BackgroundScheduler(timezone=utc)
            self.scheduler.add_jobstore(DjangoJobStore(), 'djangojobstore')
            self.scheduler.start()
            register_events(self.scheduler)
            self.scheduler.print_jobs(jobstore='djangojobstore')
        return self.scheduler

в моем urls.py Я вызываю этот метод для запуска планировщика при запуске сервера:

scheduler = SchedulerHandler().retrieve_scheduler()

Теперь, если мне нужно добавить работу, я просто вызываю add_job для существующего планировщика:

def create_cron_job(cron_string, action, rule_id, job_id=''):
    cron_values_list = cron_string.split(' ')
    if len(cron_values_list) == 5:
        print(cron_string, action, rule_id)
        scheduler = SchedulerHandler().retrieve_scheduler()
        if job_id: #I don't need to update job, so if there is a job created, I just delete it and add a new (but you can use .modify() func)
            delete_cron_job(job_id)
        job = scheduler.add_job(
            server_action_task, #here the func that will be called by scheduler
            trigger='cron',
            jobstore='djangojobstore',
            minute=cron_values_list[0],
            hour=cron_values_list[1],
            day=cron_values_list[2],
            month=cron_values_list[3],
            day_of_week=cron_values_list[4],
            args=[cron_string, action, rule_id] #here the args to pass to server_action_task
        )
        return job.id
    else:
        pass


def delete_cron_job(job_id):
    scheduler = SchedulerHandler().retrieve_scheduler()
    scheduler.remove_job(job_id)

И наконец я добавляю вызов этого метода где-то в моем проекте (в APIView или Serializer)

def create(self, validated_data):
    rule = Rules.objects.create(**validated_data)
    start_job_id = create_cron_job(rule.start_rule, 'start', rule.id)
    stop_job_id = create_cron_job(rule.stop_rule, 'stop', rule.id)
    rule.start_job_id = start_job_id
    rule.stop_job_id = stop_job_id
    rule.save()
    return rule

Мб это не оптимальное решение, но оно работает

Обратите внимание, если у вас есть cascade model deletion, вам придется вручную удалять задания из планировщика. Я сделал это, добавив pre_delete signal receiver в мой Rules model

from django.db.models.signals import pre_delete
from django.dispatch import receiver    

class Rules(models.Model):
        server = models.ForeignKey(Servers, on_delete=models.CASCADE)
        start_rule = models.CharField(max_length=40)
        stop_rule = models.CharField(max_length=40)
        start_job_id = models.CharField(max_length=50, null=True)
        stop_job_id = models.CharField(max_length=50, null=True)
        rule_type = models.CharField(max_length=12, default='common')  # ENUM common / individual


    @receiver(pre_delete, sender=Rules)
    def delete_rule(instance, **kwargs):
        delete_cron_job(instance.start_job_id)
        delete_cron_job(instance.stop_job_id)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...