From 3eb1fa32f9b808c5861eb300e78bbcdee4b3c537 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 16:51:00 +1100 Subject: [PATCH] Scheduled tasks get registered for the background worker --- .../plugin/builtin/integration/mixins.py | 57 +++++++++++++++++++ InvenTree/plugin/registry.py | 54 +++++++++++++++++- .../{scheduled_tasks.py => scheduled_task.py} | 4 +- 3 files changed, 110 insertions(+), 5 deletions(-) rename InvenTree/plugin/samples/integration/{scheduled_tasks.py => scheduled_task.py} (79%) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 9be1598fe7..ca3af01e53 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -2,6 +2,8 @@ Plugin mixin classes """ +import logging + from django.conf.urls import url, include from django.db.utils import OperationalError, ProgrammingError @@ -9,6 +11,9 @@ from plugin.models import PluginConfig, PluginSetting from plugin.urls import PLUGIN_BASE +logger = logging.getLogger('inventree') + + class SettingsMixin: """ Mixin that enables global settings for the plugin @@ -116,6 +121,58 @@ class ScheduleMixin: if schedule == 'I' and 'minutes' not in task: raise ValueError(f"Task '{key}' is missing 'minutes' parameter") + def get_task_name(self, key): + # Generate a 'unique' task name + slug = self.plugin_slug() + return f"plugin.{slug}.{key}" + + def get_task_names(self): + # Returns a list of all task names associated with this plugin instance + return [self.get_task_name(key) for key in self.scheduled_tasks.keys()] + + def register_tasks(self): + """ + Register the tasks with the database + """ + + from django_q.models import Schedule + + for key, task in self.scheduled_tasks.items(): + + task_name = self.get_task_name(key) + + # If a matching scheduled task does not exist, create it! + if not Schedule.objects.filter(name=task_name).exists(): + + logger.info(f"Adding scheduled task '{task_name}'") + + Schedule.objects.create( + name=task_name, + func=task['func'], + schedule_type=task['schedule'], + minutes=task.get('minutes', None), + repeats=task.get('repeats', -1), + ) + + + def unregister_tasks(self): + """ + Deregister the tasks with the database + """ + + from django_q.models import Schedule + + for key, task in self.scheduled_tasks.items(): + + task_name = self.get_task_name(key) + + try: + scheduled_task = Schedule.objects.get(name=task_name) + scheduled_task.delete() + except Schedule.DoesNotExist: + pass + + class UrlsMixin: """ Mixin that enables custom URLs for the plugin diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index f01f452607..86e31608e7 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -262,18 +262,21 @@ class PluginsRegistry: logger.info(f'Found {len(plugins)} active plugins') self.activate_integration_settings(plugins) + self.activate_integration_schedule(plugins) self.activate_integration_app(plugins, force_reload=force_reload) def _deactivate_plugins(self): """ Run integration deactivation functions for all plugins """ + self.deactivate_integration_app() + self.deactivate_integration_schedule() self.deactivate_integration_settings() def activate_integration_settings(self, plugins): - logger.info('Registering IntegrationPlugin global settings') + logger.info('Activating plugin settings') self.mixins_settings = {} @@ -293,8 +296,50 @@ class PluginsRegistry: # clear cache self.mixins_settings = {} + def activate_integration_schedule(self, plugins): + + logger.info('Activating plugin tasks') + + from common.models import InvenTreeSetting + from django_q.models import Schedule + + # List of tasks we have activated + task_keys = [] + + if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_SCHEDULE'): + + for slug, plugin in plugins: + + if plugin.mixin_enabled('schedule'): + config = plugin.plugin_config() + + # Only active tasks for plugins which are enabled + if config and config.active: + plugin.register_tasks() + task_keys += plugin.get_task_names() + + logger.info(f"Activated {len(task_keys)} scheduled tasks") + + # Remove any scheduled tasks which do not match + # This stops 'old' plugin tasks from accumulating + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + + deleted_count = 0 + + for task in scheduled_plugin_tasks: + if task.name not in task_keys: + task.delete() + deleted_count += 1 + + if deleted_count > 0: + logger.info(f"Removed {deleted_count} old scheduled tasks") + + def deactivate_integration_schedule(self): + pass + def activate_integration_app(self, plugins, force_reload=False): - """activate AppMixin plugins - add custom apps and reload + """ + Activate AppMixin plugins - add custom apps and reload :param plugins: list of IntegrationPlugins that should be installed :type plugins: dict @@ -378,7 +423,10 @@ class PluginsRegistry: return plugin_path def deactivate_integration_app(self): - """deactivate integration app - some magic required""" + """ + Deactivate integration app - some magic required + """ + # unregister models from admin for plugin_path in self.installed_apps: models = [] # the modelrefs need to be collected as poping an item in a iter is not welcomed diff --git a/InvenTree/plugin/samples/integration/scheduled_tasks.py b/InvenTree/plugin/samples/integration/scheduled_task.py similarity index 79% rename from InvenTree/plugin/samples/integration/scheduled_tasks.py rename to InvenTree/plugin/samples/integration/scheduled_task.py index 04672ebed3..fb84c03503 100644 --- a/InvenTree/plugin/samples/integration/scheduled_tasks.py +++ b/InvenTree/plugin/samples/integration/scheduled_task.py @@ -26,12 +26,12 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): SCHEDULED_TASKS = { 'hello': { - 'func': 'plugin.builtin.integration.mixins.ScheduleMixin.print_hello', + 'func': 'plugin.samples.integration.scheduled_task.print_hello', 'schedule': 'I', 'minutes': 5, }, 'world': { - 'func': 'plugin.builtin.integration.mixins.ScheduleMixin.print_world', + 'func': 'plugin.samples.integration.scheduled_task.print_hello', 'schedule': 'H', } } \ No newline at end of file