Мб кто-нибудь сталкивается с той же проблемой, поэтому я покажу свою реализацию
Вам нужно:
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)