ssMerge branch 'pms_plugins'

This commit is contained in:
2024-12-24 21:36:24 +09:00
66 changed files with 859 additions and 1967 deletions

View File

@@ -1,16 +1,14 @@
from django.contrib import admin
from django import forms
from django.utils.functional import cached_property
from .models import ScheduledTask
from django.templatetags.static import static
from scheduler.utils import get_project_functions
class CustomAdmin(admin.ModelAdmin):
class Media:
css = {"all": (static("scheduler/admin.css"),)}
js = (static("scheduler/admin.js"),)
class ScheduledTaskForm(forms.ModelForm):
"""
Форма для модели ScheduledTask с кастомным полем для выбора дней недели.
"""
DAYS_OF_WEEK_CHOICES = [
(0, "Воскресенье"),
(1, "Понедельник"),
@@ -25,7 +23,7 @@ class ScheduledTaskForm(forms.ModelForm):
choices=DAYS_OF_WEEK_CHOICES,
widget=forms.CheckboxSelectMultiple,
label="Дни недели",
required=False, # Опционально
required=False,
)
class Meta:
@@ -36,24 +34,32 @@ class ScheduledTaskForm(forms.ModelForm):
"minutes",
"hours",
"months",
"weekdays", # Используем только поле с галочками
"weekdays",
"active",
]
def clean_weekdays(self):
"""
Преобразуем список выбранных дней в строку для хранения в базе.
Преобразует список выбранных дней в строку для сохранения в базе.
"""
weekdays = self.cleaned_data.get("weekdays", [])
return ",".join(map(str, weekdays))
@admin.register(ScheduledTask)
class ScheduledTaskAdmin(admin.ModelAdmin):
"""
Кастомный класс для управления ScheduledTask в админке.
"""
form = ScheduledTaskForm
list_display = ("task_name", "function_path", "minutes", "hours", "months", "weekdays", "active", "formatted_last_run")
list_filter = ("active",)
search_fields = ("task_name", "function_path")
def formatted_last_run(self, obj):
"""
Отформатированный вывод времени последнего запуска задачи.
"""
return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда"
formatted_last_run.short_description = "Последний запуск"
formatted_last_run.short_description = "Последний запуск"

View File

@@ -1,7 +1,15 @@
from django.apps import AppConfig
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler_instance = AsyncIOScheduler()
class SchedulerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'scheduler'
verbose_name="Планировщик заданий"
verbose_name = 'Планировщик задач'
def ready(self):
"""
Метод ready вызывается при старте приложения.
Здесь не нужно запускать scheduler_instance.start(), чтобы избежать ошибок.
"""
pass

View File

@@ -0,0 +1,39 @@
import asyncio
from django.core.management.base import BaseCommand
from scheduler.apps import scheduler_instance
from scheduler.tasks import load_tasks_to_scheduler
class Command(BaseCommand):
help = "Запуск планировщика задач"
def handle(self, *args, **kwargs):
"""
Создаёт новый event loop, запускает планировщик и загружает задачи.
"""
try:
print("Проверка состояния перед запуском:")
print(f"Scheduler instance: {scheduler_instance}")
# Создаём новый event loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Устанавливаем event loop в планировщик
scheduler_instance.configure(event_loop=loop)
# Запускаем планировщик
scheduler_instance.start()
# Загружаем задачи
load_tasks_to_scheduler(scheduler_instance)
self.stdout.write(self.style.SUCCESS("Планировщик успешно запущен."))
# Удерживаем цикл событий
loop.run_forever()
except KeyboardInterrupt:
self.stdout.write(self.style.WARNING("Остановка планировщика."))
finally:
# Завершаем работу планировщика
scheduler_instance.shutdown(wait=False)

View File

@@ -1,30 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-10 08:38
import scheduler.models
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ScheduledTask',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_name', models.CharField(max_length=255, verbose_name='Название задачи')),
('function_path', models.CharField(choices=scheduler.models.get_available_functions, max_length=500, verbose_name='Путь к функции (модуль.функция)')),
('minutes', models.CharField(default='*', max_length=255, verbose_name='Минуты')),
('hours', models.CharField(default='*', max_length=255, verbose_name='Часы')),
('days', models.CharField(default='*', max_length=255, verbose_name='Дни')),
('months', models.CharField(default='*', max_length=255, verbose_name='Месяцы')),
('weekdays', models.JSONField(default=list, verbose_name='Дни недели')),
('active', models.BooleanField(default=True, verbose_name='Активно')),
('last_run', models.DateTimeField(blank=True, null=True, verbose_name='Последний запуск')),
],
),
]

17
scheduler/reload_tasks.py Normal file
View File

@@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from scheduler.tasks import load_tasks_to_scheduler
from scheduler.apps import scheduler_instance
class Command(BaseCommand):
help = "Перезагрузка задач в планировщике"
def handle(self, *args, **kwargs):
try:
# Удаляем все существующие задачи
scheduler_instance.remove_all_jobs()
# Загружаем задачи заново
load_tasks_to_scheduler(scheduler_instance)
self.stdout.write(self.style.SUCCESS("Задачи успешно перезагружены."))
except Exception as e:
self.stdout.write(self.style.ERROR(f"Ошибка перезагрузки задач: {e}"))

View File

@@ -5,7 +5,6 @@ from scheduler.models import ScheduledTask
import importlib
from apscheduler.triggers.cron import CronTrigger
def format_weekdays(weekdays):
"""Преобразует список дней недели в строку."""
if isinstance(weekdays, list):
@@ -43,7 +42,10 @@ def setup_scheduler():
print("Планировщик запущен.")
return scheduler
def load_tasks_to_scheduler(scheduler: BaseScheduler):
def load_tasks_to_scheduler(scheduler):
"""
Загружает активные задачи в планировщик.
"""
tasks = ScheduledTask.objects.filter(active=True)
for task in tasks:
try: