Repository: inventree/InvenTree

Issue:
[Plugin] Raise error message if plugin fails version check
If a particular plugin fails version check during loading, it currently silently loads (but is inactive).

It would be helpful to add an error message to alert the user that the plugin has failed to load, due to a version incompatibility.


Discussion:

Resolving patch:
diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py
--- a/InvenTree/common/notifications.py
+++ b/InvenTree/common/notifications.py
@@ -12,7 +12,7 @@
 from common.models import NotificationEntry, NotificationMessage
 from InvenTree.ready import isImportingData
 from plugin import registry
-from plugin.models import NotificationUserSetting
+from plugin.models import NotificationUserSetting, PluginConfig
 from users.models import Owner
 
 logger = logging.getLogger('inventree')
@@ -397,6 +397,28 @@ def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
         logger.info(f"No possible users for notification '{category}'")
 
 
+def trigger_superuser_notification(plugin: PluginConfig, msg: str):
+    """Trigger a notification to all superusers.
+
+    Args:
+        plugin (PluginConfig): Plugin that is raising the notification
+        msg (str): Detailed message that should be attached
+    """
+    users = get_user_model().objects.filter(is_superuser=True)
+
+    trigger_notification(
+        plugin,
+        'inventree.plugin',
+        context={
+            'error': plugin,
+            'name': _('Error raised by plugin'),
+            'message': msg,
+        },
+        targets=users,
+        delivery_methods=set([UIMessageNotification]),
+    )
+
+
 def deliver_notification(cls: NotificationMethod, obj, category: str, targets, context: dict):
     """Send notification with the provided class.
 
diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py
--- a/InvenTree/plugin/registry.py
+++ b/InvenTree/plugin/registry.py
@@ -19,6 +19,7 @@
 from django.db.utils import IntegrityError, OperationalError, ProgrammingError
 from django.urls import clear_url_caches, include, re_path
 from django.utils.text import slugify
+from django.utils.translation import gettext_lazy as _
 
 from maintenance_mode.core import (get_maintenance_mode, maintenance_mode_on,
                                    set_maintenance_mode)
@@ -349,6 +350,8 @@ def _init_plugins(self, disabled: str = None):
         Raises:
             error: IntegrationPluginError
         """
+        # Imports need to be in this level to prevent early db model imports
+        from InvenTree import version
         from plugin.models import PluginConfig
 
         def safe_reference(plugin, key: str, active: bool = True):
@@ -372,7 +375,7 @@ def safe_reference(plugin, key: str, active: bool = True):
             plg_key = slugify(plg.SLUG if getattr(plg, 'SLUG', None) else plg_name)  # keys are slugs!
 
             try:
-                plg_db, _ = PluginConfig.objects.get_or_create(key=plg_key, name=plg_name)
+                plg_db, _created = PluginConfig.objects.get_or_create(key=plg_key, name=plg_name)
             except (OperationalError, ProgrammingError) as error:
                 # Exception if the database has not been migrated yet - check if test are running - raise if not
                 if not settings.PLUGIN_TESTING:
@@ -406,7 +409,16 @@ def safe_reference(plugin, key: str, active: bool = True):
 
                 # Run version check for plugin
                 if (plg_i.MIN_VERSION or plg_i.MAX_VERSION) and not plg_i.check_version():
+                    # Disable plugin
                     safe_reference(plugin=plg_i, key=plg_key, active=False)
+
+                    _msg = _(f'Plugin `{plg_name}` is not compatible with the current InvenTree version {version.inventreeVersion()}!')
+                    if plg_i.MIN_VERSION:
+                        _msg += _(f'Plugin requires at least version {plg_i.MIN_VERSION}')
+                    if plg_i.MAX_VERSION:
+                        _msg += _(f'Plugin requires at most version {plg_i.MAX_VERSION}')
+                    # Log to error stack
+                    log_error(_msg, reference='init')
                 else:
                     safe_reference(plugin=plg_i, key=plg_key)
             else:  # pragma: no cover
@@ -467,7 +479,7 @@ def activate_plugin_schedule(self, plugins):
 
         if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_SCHEDULE'):
 
-            for _, plugin in plugins:
+            for _key, plugin in plugins:
 
                 if plugin.mixin_enabled('schedule'):
                     config = plugin.plugin_config()
@@ -522,7 +534,7 @@ def activate_plugin_app(self, plugins, force_reload=False, full_reload: bool = F
             apps_changed = False
 
             # add them to the INSTALLED_APPS
-            for _, plugin in plugins:
+            for _key, plugin in plugins:
                 if plugin.mixin_enabled('app'):
                     plugin_path = self._get_plugin_path(plugin)
                     if plugin_path not in settings.INSTALLED_APPS:


Test patch: