diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 37cc9111d1..52384620c0 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -54,68 +54,16 @@ class InvenTreeConfig(AppConfig): def start_background_tasks(self): """Start all background tests for InvenTree.""" - try: - from django_q.models import Schedule - except AppRegistryNotReady: # pragma: no cover - logger.warning("Cannot start background tasks - app registry not ready") - return logger.info("Starting background tasks...") - - # Remove successful task results from the database - InvenTree.tasks.schedule_task( - 'InvenTree.tasks.delete_successful_tasks', - schedule_type=Schedule.DAILY, - ) - - # Check for InvenTree updates - InvenTree.tasks.schedule_task( - 'InvenTree.tasks.check_for_updates', - schedule_type=Schedule.DAILY - ) - - # Heartbeat to let the server know the background worker is running - InvenTree.tasks.schedule_task( - 'InvenTree.tasks.heartbeat', - schedule_type=Schedule.MINUTES, - minutes=15 - ) - - # Keep exchange rates up to date - InvenTree.tasks.schedule_task( - 'InvenTree.tasks.update_exchange_rates', - schedule_type=Schedule.DAILY, - ) - - # Delete old error messages - InvenTree.tasks.schedule_task( - 'InvenTree.tasks.delete_old_error_logs', - schedule_type=Schedule.DAILY, - ) - - # Delete old notification records - InvenTree.tasks.schedule_task( - 'common.tasks.delete_old_notifications', - schedule_type=Schedule.DAILY, - ) - - # Check for overdue purchase orders - InvenTree.tasks.schedule_task( - 'order.tasks.check_overdue_purchase_orders', - schedule_type=Schedule.DAILY - ) - - # Check for overdue sales orders - InvenTree.tasks.schedule_task( - 'order.tasks.check_overdue_sales_orders', - schedule_type=Schedule.DAILY, - ) - - # Check for overdue build orders - InvenTree.tasks.schedule_task( - 'build.tasks.check_overdue_build_orders', - schedule_type=Schedule.DAILY - ) + # Run through registered tasks + for task in InvenTree.tasks.tasks.task_list: + InvenTree.tasks.schedule_task( + task.func, + schedule_type=task.interval, + minutes=task.minutes, + ) + logger.info("Started background tasks...") # Make regular backups InvenTree.tasks.schedule_task( diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 43b8caa846..7c22a7bb31 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -4,7 +4,9 @@ import json import logging import re import warnings +from dataclasses import dataclass from datetime import timedelta +from typing import Callable from django.conf import settings from django.core import mail as django_mail @@ -126,6 +128,69 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs) _func(*args, **kwargs) +@dataclass() +class ScheduledTask: + """A scheduled task. + + - interval: The interval at which the task should be run + - minutes: The number of minutes between task runs + - func: The function to be run + """ + + func: Callable + interval: str + minutes: int = None + + MINUTES = "I" + HOURLY = "H" + DAILY = "D" + WEEKLY = "W" + MONTHLY = "M" + QUARTERLY = "Q" + YEARLY = "Y" + TYPE = [MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY] + + +class TaskRegister: + """Registery for periodicall tasks.""" + task_list: list[ScheduledTask] = [] + + def register(self, task, schedule, minutes: int = None): + """Register a task with the que.""" + self.task_list.append(ScheduledTask(task, schedule, minutes)) + + +tasks = TaskRegister() + + +def scheduled_task(interval: str, minutes: int = None): + """Register the given task as a scheduled task. + + - interval: The interval at which the task should be run + - minutes: The number of minutes between task runs + + Example: + ```python + @register(ScheduledTask.DAILY) + def my_custom_funciton(): + ... + ``` + """ + + def _task_wrapper(admin_class): + if not isinstance(admin_class, Callable): + raise ValueError('Wrapped object must be a function') + + if interval not in ScheduledTask.TYPE: + raise ValueError(f'Invalid interval. Must be one of {ScheduledTask.TYPE}') + + tasks.register(admin_class, interval, minutes=minutes) + + return admin_class + return _task_wrapper + + +@scheduled_task(ScheduledTask.MINUTES, 15) def heartbeat(): """Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running. @@ -149,6 +214,7 @@ def heartbeat(): heartbeats.delete() +@scheduled_task(ScheduledTask.DAILY) def delete_successful_tasks(): """Delete successful task logs which are more than a month old.""" try: @@ -168,6 +234,7 @@ def delete_successful_tasks(): results.delete() +@scheduled_task(ScheduledTask.DAILY) def delete_old_error_logs(): """Delete old error logs from the server.""" try: @@ -190,6 +257,7 @@ def delete_old_error_logs(): return +@scheduled_task(ScheduledTask.DAILY) def check_for_updates(): """Check if there is an update for InvenTree.""" try: @@ -232,6 +300,7 @@ def check_for_updates(): ) +@scheduled_task(ScheduledTask.DAILY) def update_exchange_rates(): """Update currency exchange rates.""" try: diff --git a/InvenTree/build/tasks.py b/InvenTree/build/tasks.py index fcda1f8bff..6623686155 100644 --- a/InvenTree/build/tasks.py +++ b/InvenTree/build/tasks.py @@ -144,6 +144,7 @@ def notify_overdue_build_order(bo: build.models.Build): trigger_event(event_name, build_order=bo.pk) +@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY) def check_overdue_build_orders(): """Check if any outstanding BuildOrders have just become overdue diff --git a/InvenTree/common/tasks.py b/InvenTree/common/tasks.py index 56fd3fb04f..e600136560 100644 --- a/InvenTree/common/tasks.py +++ b/InvenTree/common/tasks.py @@ -5,9 +5,12 @@ from datetime import datetime, timedelta from django.core.exceptions import AppRegistryNotReady +from InvenTree.tasks import ScheduledTask, scheduled_task + logger = logging.getLogger('inventree') +@scheduled_task(ScheduledTask.DAILY) def delete_old_notifications(): """Remove old notifications from the database. diff --git a/InvenTree/order/tasks.py b/InvenTree/order/tasks.py index 103fc3cc46..574a732ffb 100644 --- a/InvenTree/order/tasks.py +++ b/InvenTree/order/tasks.py @@ -6,12 +6,13 @@ from django.utils.translation import gettext_lazy as _ import common.notifications import InvenTree.helpers -import InvenTree.tasks import order.models from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus +from InvenTree.tasks import ScheduledTask, scheduled_task from plugin.events import trigger_event +@scheduled_task(ScheduledTask.DAILY) def notify_overdue_purchase_order(po: order.models.PurchaseOrder): """Notify users that a PurchaseOrder has just become 'overdue'""" @@ -55,6 +56,7 @@ def notify_overdue_purchase_order(po: order.models.PurchaseOrder): ) +@scheduled_task(ScheduledTask.DAILY) def check_overdue_purchase_orders(): """Check if any outstanding PurchaseOrders have just become overdue: