From 8103b842689c7430dcde809a3ecdd238d3ea4f6e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 14:23:32 +1100 Subject: [PATCH 01/12] Move mixins.py into main plugin directory --- InvenTree/plugin/builtin/integration/__init__.py | 0 InvenTree/plugin/{builtin/integration => }/mixins.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 InvenTree/plugin/builtin/integration/__init__.py rename InvenTree/plugin/{builtin/integration => }/mixins.py (100%) diff --git a/InvenTree/plugin/builtin/integration/__init__.py b/InvenTree/plugin/builtin/integration/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/mixins.py similarity index 100% rename from InvenTree/plugin/builtin/integration/mixins.py rename to InvenTree/plugin/mixins.py From 0773545615654930c66a01decec64aa527c6a809 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 14:54:04 +1100 Subject: [PATCH 02/12] Add "ScheduleMixin" for scheduling tasks --- InvenTree/plugin/mixins.py | 49 ++++++++++++++++++- .../samples/integration/scheduled_tasks.py | 16 ++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 InvenTree/plugin/samples/integration/scheduled_tasks.py diff --git a/InvenTree/plugin/mixins.py b/InvenTree/plugin/mixins.py index c5d2411e4d..580afdc888 100644 --- a/InvenTree/plugin/mixins.py +++ b/InvenTree/plugin/mixins.py @@ -53,6 +53,51 @@ class SettingsMixin: PluginSetting.set_setting(key, value, user, plugin=plugin) +class ScheduleMixin: + """ + Mixin that provides support for scheduled tasks. + + Implementing classes must provide a dict object called SCHEDULED_TASKS, + which provides information on the tasks to be scheduled. + + SCHEDULED_TASKS = { + # Name of the task (will be prepended with the plugin name) + 'test_server': { + 'func': 'myplugin.tasks.test_server', # Python function to call (no arguments!) + 'schedule': "I", # Schedule type (see django_q.Schedule) + 'minutes': 30, # Number of minutes (only if schedule type = Minutes) + 'repeats': 5, # Number of repeats (leave blank for 'forever') + } + } + """ + + SCHEDULED_TASKS = {} + + class MixinMeta: + MIXIN_NAME = 'Schedule' + + def __init__(self): + super().__init__() + self.add_mixin('schedule', 'has_scheduled_tasks', __class__) + self.scheduled_tasks = getattr(self, 'SCHEDULED_TASKS', {}) + + self.validate_scheduled_tasks() + + @property + def has_scheduled_tasks(self): + return bool(self.scheduled_tasks) + + def validate_scheduled_tasks(self): + """ + Check that the provided scheduled tasks are valid + """ + + if not self.has_scheduled_tasks(): + raise ValueError(f"SCHEDULED_TASKS not defined for plugin '{__class__}'") + + for key, task in self.scheduled_tasks.items(): + print(key, task) + class UrlsMixin: """ Mixin that enables custom URLs for the plugin @@ -112,7 +157,9 @@ class NavigationMixin: NAVIGATION_TAB_ICON = "fas fa-question" class MixinMeta: - """meta options for this mixin""" + """ + meta options for this mixin + """ MIXIN_NAME = 'Navigation Links' def __init__(self): diff --git a/InvenTree/plugin/samples/integration/scheduled_tasks.py b/InvenTree/plugin/samples/integration/scheduled_tasks.py new file mode 100644 index 0000000000..14d8399f03 --- /dev/null +++ b/InvenTree/plugin/samples/integration/scheduled_tasks.py @@ -0,0 +1,16 @@ +""" +Sample plugin which supports task scheduling +""" + +from plugin import IntegrationPluginBase +from plugin.mixins import ScheduleMixin + + +class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): + """ + A sample plugin which provides support for scheduled tasks + """ + + PLUGIN_NAME = "ScheduledTasksPlugin" + PLUGIN_SLUG = "schedule" + PLUGIN_TITLE = "A plugin which provides scheduled task support" From 326b897d14f4bd2d6ddacd2d1c2105942283cc7b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 14:54:12 +1100 Subject: [PATCH 03/12] Revert "Move mixins.py into main plugin directory" This reverts commit 8103b842689c7430dcde809a3ecdd238d3ea4f6e. --- InvenTree/plugin/builtin/integration/__init__.py | 0 InvenTree/plugin/{ => builtin/integration}/mixins.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 InvenTree/plugin/builtin/integration/__init__.py rename InvenTree/plugin/{ => builtin/integration}/mixins.py (100%) diff --git a/InvenTree/plugin/builtin/integration/__init__.py b/InvenTree/plugin/builtin/integration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/plugin/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py similarity index 100% rename from InvenTree/plugin/mixins.py rename to InvenTree/plugin/builtin/integration/mixins.py From 794a9e75e8ed11ce544eacb75fdd64453edc81b4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 15:37:43 +1100 Subject: [PATCH 04/12] Add validation for scheduled tasks defined by a plugin --- InvenTree/common/models.py | 7 ++++++ .../plugin/builtin/integration/mixins.py | 24 ++++++++++++++++--- InvenTree/plugin/integration.py | 4 ++++ InvenTree/plugin/mixins/__init__.py | 8 +++++-- .../samples/integration/scheduled_tasks.py | 23 +++++++++++++++++- .../templates/InvenTree/settings/plugin.html | 3 ++- 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 16d0be035a..15c12f1a36 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -978,6 +978,13 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, 'requires_restart': True, }, + 'ENABLE_PLUGINS_SCHEDULE': { + 'name': _('Enable schedule integration'), + 'description': _('Enable plugins to run scheduled tasks'), + 'default': False, + 'validator': bool, + 'requires_restart': True, + } } class Meta: diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 580afdc888..9be1598fe7 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -69,8 +69,12 @@ class ScheduleMixin: 'repeats': 5, # Number of repeats (leave blank for 'forever') } } + + Note: 'schedule' parameter must be one of ['I', 'H', 'D', 'W', 'M', 'Q', 'Y'] """ + ALLOWABLE_SCHEDULE_TYPES = ['I', 'H', 'D', 'W', 'M', 'Q', 'Y'] + SCHEDULED_TASKS = {} class MixinMeta: @@ -92,11 +96,25 @@ class ScheduleMixin: Check that the provided scheduled tasks are valid """ - if not self.has_scheduled_tasks(): - raise ValueError(f"SCHEDULED_TASKS not defined for plugin '{__class__}'") + if not self.has_scheduled_tasks: + raise ValueError(f"SCHEDULED_TASKS not defined") for key, task in self.scheduled_tasks.items(): - print(key, task) + + if 'func' not in task: + raise ValueError(f"Task '{key}' is missing 'func' parameter") + + if 'schedule' not in task: + raise ValueError(f"Task '{key}' is missing 'schedule' parameter") + + schedule = task['schedule'].upper().strip() + + if schedule not in self.ALLOWABLE_SCHEDULE_TYPES: + raise ValueError(f"Task '{key}': Schedule '{schedule}' is not a valid option") + + # If 'minutes' is selected, it must be provided! + if schedule == 'I' and 'minutes' not in task: + raise ValueError(f"Task '{key}' is missing 'minutes' parameter") class UrlsMixin: """ diff --git a/InvenTree/plugin/integration.py b/InvenTree/plugin/integration.py index 73223593a5..b7ae7d1fc4 100644 --- a/InvenTree/plugin/integration.py +++ b/InvenTree/plugin/integration.py @@ -94,6 +94,10 @@ class IntegrationPluginBase(MixinBase, plugin.InvenTreePlugin): def slug(self): return self.plugin_slug() + @property + def name(self): + return self.plugin_name() + @property def human_name(self): """human readable name for labels etc.""" diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py index ceb5de5885..e9c910bb9e 100644 --- a/InvenTree/plugin/mixins/__init__.py +++ b/InvenTree/plugin/mixins/__init__.py @@ -1,9 +1,13 @@ -"""utility class to enable simpler imports""" -from ..builtin.integration.mixins import AppMixin, SettingsMixin, UrlsMixin, NavigationMixin +""" +Utility class to enable simpler imports +""" + +from ..builtin.integration.mixins import AppMixin, SettingsMixin, ScheduleMixin, UrlsMixin, NavigationMixin __all__ = [ 'AppMixin', 'NavigationMixin', + 'ScheduleMixin', 'SettingsMixin', 'UrlsMixin', ] diff --git a/InvenTree/plugin/samples/integration/scheduled_tasks.py b/InvenTree/plugin/samples/integration/scheduled_tasks.py index 14d8399f03..04672ebed3 100644 --- a/InvenTree/plugin/samples/integration/scheduled_tasks.py +++ b/InvenTree/plugin/samples/integration/scheduled_tasks.py @@ -6,6 +6,15 @@ from plugin import IntegrationPluginBase from plugin.mixins import ScheduleMixin +# Define some simple tasks to perform +def print_hello(): + print("Hello") + + +def print_world(): + print("World") + + class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): """ A sample plugin which provides support for scheduled tasks @@ -13,4 +22,16 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): PLUGIN_NAME = "ScheduledTasksPlugin" PLUGIN_SLUG = "schedule" - PLUGIN_TITLE = "A plugin which provides scheduled task support" + PLUGIN_TITLE = "Scheduled Tasks" + + SCHEDULED_TASKS = { + 'hello': { + 'func': 'plugin.builtin.integration.mixins.ScheduleMixin.print_hello', + 'schedule': 'I', + 'minutes': 5, + }, + 'world': { + 'func': 'plugin.builtin.integration.mixins.ScheduleMixin.print_world', + 'schedule': 'H', + } + } \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/plugin.html b/InvenTree/templates/InvenTree/settings/plugin.html index 960ec852b8..858d0f3ab9 100644 --- a/InvenTree/templates/InvenTree/settings/plugin.html +++ b/InvenTree/templates/InvenTree/settings/plugin.html @@ -19,6 +19,7 @@
+ {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_SCHEDULE" icon="fa-calendar-alt" %} {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_URL" icon="fa-link" %} {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_NAVIGATION" icon="fa-sitemap" %} {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_APP" icon="fa-rocket" %} @@ -28,7 +29,7 @@
-

{% trans "Plugin list" %}

+

{% trans "Plugins" %}

{% include "spacer.html" %}
{% url 'admin:plugin_pluginconfig_changelist' as url %} From 0ab9b2dbc76889d1fb53021b3798cc65a4888377 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 15:41:31 +1100 Subject: [PATCH 05/12] Bug fix - always allow plugins to register settings --- InvenTree/plugin/registry.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index fe28acfadb..f01f452607 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -272,14 +272,15 @@ class PluginsRegistry: self.deactivate_integration_settings() def activate_integration_settings(self, plugins): - from common.models import InvenTreeSetting - if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_GLOBALSETTING'): - logger.info('Registering IntegrationPlugin global settings') - for slug, plugin in plugins: - if plugin.mixin_enabled('settings'): - plugin_setting = plugin.settings - self.mixins_settings[slug] = plugin_setting + logger.info('Registering IntegrationPlugin global settings') + + self.mixins_settings = {} + + for slug, plugin in plugins: + if plugin.mixin_enabled('settings'): + plugin_setting = plugin.settings + self.mixins_settings[slug] = plugin_setting def deactivate_integration_settings(self): @@ -290,7 +291,7 @@ class PluginsRegistry: plugin_settings.update(plugin_setting) # clear cache - self.mixins_Fsettings = {} + self.mixins_settings = {} def activate_integration_app(self, plugins, force_reload=False): """activate AppMixin plugins - add custom apps and reload From ff598a22ffdf8bbd8a57f0a7cc18d9b1626a7743 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 16:20:16 +1100 Subject: [PATCH 06/12] bug fix : correct setting name when changing a 'requires restart' setting --- InvenTree/common/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 15c12f1a36..dee0eb2e8b 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -571,7 +571,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): super().save() if self.requires_restart(): - InvenTreeSetting.set_setting('SERVER_REQUIRES_RESTART', True, None) + InvenTreeSetting.set_setting('SERVER_RESTART_REQUIRED', True, None) """ Dict of all global settings values: From 3eb1fa32f9b808c5861eb300e78bbcdee4b3c537 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 16:51:00 +1100 Subject: [PATCH 07/12] 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 From 36feef65584c73f57969c8da7cde014e0e443251 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 16:53:51 +1100 Subject: [PATCH 08/12] Remove log message if not relevent --- InvenTree/plugin/registry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 86e31608e7..7e08ce3efb 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -318,7 +318,8 @@ class PluginsRegistry: plugin.register_tasks() task_keys += plugin.get_task_names() - logger.info(f"Activated {len(task_keys)} scheduled tasks") + if len(task_keys) > 0: + logger.info(f"Activated {len(task_keys)} scheduled tasks") # Remove any scheduled tasks which do not match # This stops 'old' plugin tasks from accumulating From c04e07c1fa391726d318677f8a390985b5f0aa9f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 17:04:33 +1100 Subject: [PATCH 09/12] Add a task which fails on purpose --- InvenTree/plugin/builtin/integration/mixins.py | 9 ++++----- InvenTree/plugin/registry.py | 6 +++--- .../plugin/samples/integration/scheduled_task.py | 12 ++++++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index ca3af01e53..54f739d04d 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -102,16 +102,16 @@ class ScheduleMixin: """ if not self.has_scheduled_tasks: - raise ValueError(f"SCHEDULED_TASKS not defined") + raise ValueError("SCHEDULED_TASKS not defined") for key, task in self.scheduled_tasks.items(): - + if 'func' not in task: raise ValueError(f"Task '{key}' is missing 'func' parameter") - + if 'schedule' not in task: raise ValueError(f"Task '{key}' is missing 'schedule' parameter") - + schedule = task['schedule'].upper().strip() if schedule not in self.ALLOWABLE_SCHEDULE_TYPES: @@ -153,7 +153,6 @@ class ScheduleMixin: minutes=task.get('minutes', None), repeats=task.get('repeats', -1), ) - def unregister_tasks(self): """ diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 7e08ce3efb..12dfc9d43e 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -299,7 +299,7 @@ class PluginsRegistry: def activate_integration_schedule(self, plugins): logger.info('Activating plugin tasks') - + from common.models import InvenTreeSetting from django_q.models import Schedule @@ -307,7 +307,7 @@ class PluginsRegistry: task_keys = [] if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_SCHEDULE'): - + for slug, plugin in plugins: if plugin.mixin_enabled('schedule'): @@ -427,7 +427,7 @@ class PluginsRegistry: """ 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_task.py b/InvenTree/plugin/samples/integration/scheduled_task.py index fb84c03503..5a8f866cd7 100644 --- a/InvenTree/plugin/samples/integration/scheduled_task.py +++ b/InvenTree/plugin/samples/integration/scheduled_task.py @@ -15,6 +15,10 @@ def print_world(): print("World") +def fail_task(): + raise ValueError("This task should fail!") + + class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): """ A sample plugin which provides support for scheduled tasks @@ -33,5 +37,9 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase): 'world': { 'func': 'plugin.samples.integration.scheduled_task.print_hello', 'schedule': 'H', - } - } \ No newline at end of file + }, + 'failure': { + 'func': 'plugin.samples.integration.scheduled_task.fail_task', + 'schedule': 'D', + }, + } From 103dfaa2a57e1d1f5a635ed128d0b0725c90d709 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 17:11:53 +1100 Subject: [PATCH 10/12] try/catch for operational error - Database might not yet be ready to load models --- .../plugin/builtin/integration/mixins.py | 50 +++++++++++-------- InvenTree/plugin/registry.py | 23 +++++---- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 54f739d04d..cd757edc9f 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -135,41 +135,49 @@ class ScheduleMixin: Register the tasks with the database """ - from django_q.models import Schedule + try: + from django_q.models import Schedule - for key, task in self.scheduled_tasks.items(): + for key, task in self.scheduled_tasks.items(): - task_name = self.get_task_name(key) + 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(): + # 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}'") + 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), - ) + Schedule.objects.create( + name=task_name, + func=task['func'], + schedule_type=task['schedule'], + minutes=task.get('minutes', None), + repeats=task.get('repeats', -1), + ) + except OperationalError: + # Database might not yet be ready + pass def unregister_tasks(self): """ Deregister the tasks with the database """ - from django_q.models import Schedule + try: + from django_q.models import Schedule - for key, task in self.scheduled_tasks.items(): + for key, task in self.scheduled_tasks.items(): - task_name = self.get_task_name(key) + task_name = self.get_task_name(key) - try: - scheduled_task = Schedule.objects.get(name=task_name) - scheduled_task.delete() - except Schedule.DoesNotExist: - pass + try: + scheduled_task = Schedule.objects.get(name=task_name) + scheduled_task.delete() + except Schedule.DoesNotExist: + pass + except OperationalError: + # Database might not yet be ready + pass class UrlsMixin: diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 12dfc9d43e..37196d7e54 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -301,7 +301,6 @@ class PluginsRegistry: logger.info('Activating plugin tasks') from common.models import InvenTreeSetting - from django_q.models import Schedule # List of tasks we have activated task_keys = [] @@ -323,17 +322,23 @@ class PluginsRegistry: # Remove any scheduled tasks which do not match # This stops 'old' plugin tasks from accumulating - scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + try: + from django_q.models import Schedule - deleted_count = 0 + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") - for task in scheduled_plugin_tasks: - if task.name not in task_keys: - task.delete() - deleted_count += 1 + deleted_count = 0 - if deleted_count > 0: - logger.info(f"Removed {deleted_count} old scheduled tasks") + 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") + except OperationalError: + # Database might not yet be ready + pass def deactivate_integration_schedule(self): pass From 668e2bfcd593d06d9e4441a1e4ebc50af1238b9b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 17:20:57 +1100 Subject: [PATCH 11/12] Further error catching --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++-- InvenTree/plugin/registry.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index cd757edc9f..5390740c03 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -154,7 +154,7 @@ class ScheduleMixin: minutes=task.get('minutes', None), repeats=task.get('repeats', -1), ) - except OperationalError: + except (ProgrammingError, OperationalError): # Database might not yet be ready pass @@ -175,7 +175,7 @@ class ScheduleMixin: scheduled_task.delete() except Schedule.DoesNotExist: pass - except OperationalError: + except (ProgrammingError, OperationalError): # Database might not yet be ready pass diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 37196d7e54..c6dbe959b8 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -336,7 +336,7 @@ class PluginsRegistry: if deleted_count > 0: logger.info(f"Removed {deleted_count} old scheduled tasks") - except OperationalError: + except (ProgrammingError, OperationalError): # Database might not yet be ready pass From 8efd45f0adc4e9766c7b64f9e565371c162ad077 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 7 Jan 2022 18:00:38 +1100 Subject: [PATCH 12/12] log warning message if db not ready --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++-- InvenTree/plugin/registry.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 5390740c03..c6198ed7a1 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -156,7 +156,7 @@ class ScheduleMixin: ) except (ProgrammingError, OperationalError): # Database might not yet be ready - pass + logger.warning("register_tasks failed, database not ready") def unregister_tasks(self): """ @@ -177,7 +177,7 @@ class ScheduleMixin: pass except (ProgrammingError, OperationalError): # Database might not yet be ready - pass + logger.warning("unregister_tasks failed, database not ready") class UrlsMixin: diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index c6dbe959b8..45df8cf94b 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -338,7 +338,7 @@ class PluginsRegistry: logger.info(f"Removed {deleted_count} old scheduled tasks") except (ProgrammingError, OperationalError): # Database might not yet be ready - pass + logger.warning("activate_integration_schedule failed, database not ready") def deactivate_integration_schedule(self): pass