mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 21:38:48 +00:00
Fix task register (#3805)
* fix schedule registration * add collection step for tasks * make tasks register configurable * extend docs * Also run InvenTree setup in testing * fix import loading method * fix wrong task registration * do not test * do only distinct testing * ignore import error for coverage
This commit is contained in:
parent
3956a45c48
commit
269b269de3
@ -1,8 +1,10 @@
|
|||||||
"""AppConfig for inventree app."""
|
"""AppConfig for inventree app."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from importlib import import_module
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig, apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
@ -23,10 +25,11 @@ class InvenTreeConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
"""Setup background tasks and update exchange rates."""
|
"""Setup background tasks and update exchange rates."""
|
||||||
if canAppAccessDatabase():
|
if canAppAccessDatabase() or settings.TESTING_ENV:
|
||||||
|
|
||||||
self.remove_obsolete_tasks()
|
self.remove_obsolete_tasks()
|
||||||
|
|
||||||
|
self.collect_tasks()
|
||||||
self.start_background_tasks()
|
self.start_background_tasks()
|
||||||
|
|
||||||
if not isInTestMode(): # pragma: no cover
|
if not isInTestMode(): # pragma: no cover
|
||||||
@ -54,68 +57,31 @@ class InvenTreeConfig(AppConfig):
|
|||||||
|
|
||||||
def start_background_tasks(self):
|
def start_background_tasks(self):
|
||||||
"""Start all background tests for InvenTree."""
|
"""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...")
|
logger.info("Starting background tasks...")
|
||||||
|
|
||||||
# Remove successful task results from the database
|
for task in InvenTree.tasks.tasks.task_list:
|
||||||
InvenTree.tasks.schedule_task(
|
ref_name = f'{task.func.__module__}.{task.func.__name__}'
|
||||||
'InvenTree.tasks.delete_successful_tasks',
|
InvenTree.tasks.schedule_task(
|
||||||
schedule_type=Schedule.DAILY,
|
ref_name,
|
||||||
)
|
schedule_type=task.interval,
|
||||||
|
minutes=task.minutes,
|
||||||
|
)
|
||||||
|
|
||||||
# Check for InvenTree updates
|
logger.info("Started background tasks...")
|
||||||
InvenTree.tasks.schedule_task(
|
|
||||||
'InvenTree.tasks.check_for_updates',
|
|
||||||
schedule_type=Schedule.DAILY
|
|
||||||
)
|
|
||||||
|
|
||||||
# Heartbeat to let the server know the background worker is running
|
def collect_tasks(self):
|
||||||
InvenTree.tasks.schedule_task(
|
"""Collect all background tasks."""
|
||||||
'InvenTree.tasks.heartbeat',
|
|
||||||
schedule_type=Schedule.MINUTES,
|
|
||||||
minutes=15
|
|
||||||
)
|
|
||||||
|
|
||||||
# Keep exchange rates up to date
|
for app_name, app in apps.app_configs.items():
|
||||||
InvenTree.tasks.schedule_task(
|
if app_name == 'InvenTree':
|
||||||
'InvenTree.tasks.update_exchange_rates',
|
continue
|
||||||
schedule_type=Schedule.DAILY,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete old error messages
|
if Path(app.path).joinpath('tasks.py').exists():
|
||||||
InvenTree.tasks.schedule_task(
|
try:
|
||||||
'InvenTree.tasks.delete_old_error_logs',
|
import_module(f'{app.module.__package__}.tasks')
|
||||||
schedule_type=Schedule.DAILY,
|
except Exception as e: # pragma: no cover
|
||||||
)
|
logger.error(f"Error loading tasks for {app_name}: {e}")
|
||||||
|
|
||||||
# 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
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_exchange_rates(self): # pragma: no cover
|
def update_exchange_rates(self): # pragma: no cover
|
||||||
"""Update exchange rates each time the server is started.
|
"""Update exchange rates each time the server is started.
|
||||||
|
@ -4,7 +4,9 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail as django_mail
|
from django.core import mail as django_mail
|
||||||
@ -126,6 +128,79 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
|
|||||||
_func(*args, **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, tasklist: TaskRegister = None):
|
||||||
|
"""Register the given task as a scheduled task.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
@register(ScheduledTask.DAILY)
|
||||||
|
def my_custom_funciton():
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interval (str): The interval at which the task should be run
|
||||||
|
minutes (int, optional): The number of minutes between task runs. Defaults to None.
|
||||||
|
tasklist (TaskRegister, optional): The list the tasks should be registered to. Defaults to None.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If decorated object is not callable
|
||||||
|
ValueError: If interval is not valid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_type_: _description_
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 = tasklist if tasklist else tasks
|
||||||
|
_tasks.register(admin_class, interval, minutes=minutes)
|
||||||
|
|
||||||
|
return admin_class
|
||||||
|
return _task_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.MINUTES, 15)
|
||||||
def heartbeat():
|
def heartbeat():
|
||||||
"""Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running.
|
"""Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running.
|
||||||
|
|
||||||
@ -149,6 +224,7 @@ def heartbeat():
|
|||||||
heartbeats.delete()
|
heartbeats.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_successful_tasks():
|
def delete_successful_tasks():
|
||||||
"""Delete successful task logs which are more than a month old."""
|
"""Delete successful task logs which are more than a month old."""
|
||||||
try:
|
try:
|
||||||
@ -168,6 +244,7 @@ def delete_successful_tasks():
|
|||||||
results.delete()
|
results.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_error_logs():
|
def delete_old_error_logs():
|
||||||
"""Delete old error logs from the server."""
|
"""Delete old error logs from the server."""
|
||||||
try:
|
try:
|
||||||
@ -190,6 +267,7 @@ def delete_old_error_logs():
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_for_updates():
|
def check_for_updates():
|
||||||
"""Check if there is an update for InvenTree."""
|
"""Check if there is an update for InvenTree."""
|
||||||
try:
|
try:
|
||||||
@ -232,6 +310,7 @@ def check_for_updates():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def update_exchange_rates():
|
def update_exchange_rates():
|
||||||
"""Update currency exchange rates."""
|
"""Update currency exchange rates."""
|
||||||
try:
|
try:
|
||||||
@ -273,6 +352,7 @@ def update_exchange_rates():
|
|||||||
logger.error(f"Error updating exchange rates: {e}")
|
logger.error(f"Error updating exchange rates: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def run_backup():
|
def run_backup():
|
||||||
"""Run the backup command."""
|
"""Run the backup command."""
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
|
@ -144,6 +144,7 @@ def notify_overdue_build_order(bo: build.models.Build):
|
|||||||
trigger_event(event_name, build_order=bo.pk)
|
trigger_event(event_name, build_order=bo.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY)
|
||||||
def check_overdue_build_orders():
|
def check_overdue_build_orders():
|
||||||
"""Check if any outstanding BuildOrders have just become overdue
|
"""Check if any outstanding BuildOrders have just become overdue
|
||||||
|
|
||||||
|
@ -5,9 +5,12 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
|
|
||||||
|
from InvenTree.tasks import ScheduledTask, scheduled_task
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_notifications():
|
def delete_old_notifications():
|
||||||
"""Remove old notifications from the database.
|
"""Remove old notifications from the database.
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
import common.notifications
|
import common.notifications
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.tasks
|
|
||||||
import order.models
|
import order.models
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||||
|
from InvenTree.tasks import ScheduledTask, scheduled_task
|
||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +55,7 @@ def notify_overdue_purchase_order(po: order.models.PurchaseOrder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_overdue_purchase_orders():
|
def check_overdue_purchase_orders():
|
||||||
"""Check if any outstanding PurchaseOrders have just become overdue:
|
"""Check if any outstanding PurchaseOrders have just become overdue:
|
||||||
|
|
||||||
@ -117,6 +118,7 @@ def notify_overdue_sales_order(so: order.models.SalesOrder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_overdue_sales_orders():
|
def check_overdue_sales_orders():
|
||||||
"""Check if any outstanding SalesOrders have just become overdue
|
"""Check if any outstanding SalesOrders have just become overdue
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user