diff --git a/InvenTree/plugin/apps.py b/InvenTree/plugin/apps.py index bd56e05709..28f1acac8e 100644 --- a/InvenTree/plugin/apps.py +++ b/InvenTree/plugin/apps.py @@ -7,7 +7,6 @@ The main code for plugin special sauce is in the plugin registry in `InvenTree/p import logging from django.apps import AppConfig -from django.conf import settings from django.utils.translation import gettext_lazy as _ from maintenance_mode.core import set_maintenance_mode @@ -26,34 +25,34 @@ class PluginAppConfig(AppConfig): def ready(self): """The ready method is extended to initialize plugins.""" - if settings.PLUGINS_ENABLED: - if not canAppAccessDatabase(allow_test=True, allow_plugins=True): - logger.info("Skipping plugin loading sequence") # pragma: no cover - else: - logger.info('Loading InvenTree plugins') + if not canAppAccessDatabase(allow_test=True, allow_plugins=True): + logger.info("Skipping plugin loading sequence") # pragma: no cover + else: + logger.info('Loading InvenTree plugins') - if not registry.is_loading: - # this is the first startup - try: - from common.models import InvenTreeSetting - if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False): - # make sure all plugins are installed - registry.install_plugin_file() - except Exception: # pragma: no cover - pass + if not registry.is_loading: + # this is the first startup + try: + from common.models import InvenTreeSetting + if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False): + # make sure all plugins are installed + registry.install_plugin_file() + except Exception: # pragma: no cover + pass - # get plugins and init them - registry.plugin_modules = registry.collect_plugins() - registry.load_plugins() + # get plugins and init them + registry.plugin_modules = registry.collect_plugins() + registry.load_plugins() - # drop out of maintenance - # makes sure we did not have an error in reloading and maintenance is still active - set_maintenance_mode(False) + # drop out of maintenance + # makes sure we did not have an error in reloading and maintenance is still active + set_maintenance_mode(False) - # check git version - registry.git_is_modern = check_git_version() - if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage - log_error(_('Your enviroment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load') + # check git version + registry.git_is_modern = check_git_version() + + if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage + log_error(_('Your environment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load') else: logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index ece0395092..17a98719e4 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -106,6 +106,11 @@ class MetaBase: def is_active(self): """Return True if this plugin is currently active.""" + + # Builtin plugins are always considered "active" + if self.is_builtin(): + return True + cfg = self.plugin_config() if cfg: @@ -300,6 +305,16 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase): """Is this plugin part of the samples?""" return self.check_is_sample() + @classmethod + def check_is_builtin(cls) -> bool: + """Determine if a particular plugin class is a 'builtin' plugin""" + return str(cls.check_package_path()).startswith('plugin/builtin') + + @property + def is_builtin(self) -> bool: + """Is this plugin is builtin""" + return self.check_is_builtin() + @classmethod def check_package_path(cls): """Path to the plugin.""" diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index bc832fa128..d4996ad4ac 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -108,9 +108,6 @@ class PluginsRegistry: Args: full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. """ - if not settings.PLUGINS_ENABLED: - # Plugins not enabled, do nothing - return # pragma: no cover logger.info('Start loading plugins') @@ -167,9 +164,6 @@ class PluginsRegistry: def unload_plugins(self): """Unload and deactivate all IntegrationPlugins.""" - if not settings.PLUGINS_ENABLED: - # Plugins not enabled, do nothing - return # pragma: no cover logger.info('Start unloading plugins') @@ -187,6 +181,7 @@ class PluginsRegistry: # remove maintenance if not _maintenance: set_maintenance_mode(False) # pragma: no cover + logger.info('Finished unloading plugins') def reload_plugins(self, full_reload: bool = False): @@ -210,62 +205,63 @@ class PluginsRegistry: def plugin_dirs(self): """Construct a list of directories from where plugins can be loaded""" + # Builtin plugins are *always* loaded dirs = ['plugin.builtin', ] - if settings.TESTING or settings.DEBUG: - # If in TEST or DEBUG mode, load plugins from the 'samples' directory - dirs.append('plugin.samples') + if settings.PLUGINS_ENABLED: + # Any 'external' plugins are only loaded if PLUGINS_ENABLED is set to True - if settings.TESTING: - custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None) - else: # pragma: no cover - custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir') + if settings.TESTING or settings.DEBUG: + # If in TEST or DEBUG mode, load plugins from the 'samples' directory + dirs.append('plugin.samples') - # Load from user specified directories (unless in testing mode) - dirs.append('plugins') + if settings.TESTING: + custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None) + else: # pragma: no cover + custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir') - if custom_dirs is not None: - # Allow multiple plugin directories to be specified - for pd_text in custom_dirs.split(','): - pd = Path(pd_text.strip()).absolute() + # Load from user specified directories (unless in testing mode) + dirs.append('plugins') - # Attempt to create the directory if it does not already exist - if not pd.exists(): - try: - pd.mkdir(exist_ok=True) - except Exception: # pragma: no cover - logger.error(f"Could not create plugin directory '{pd}'") - continue + if custom_dirs is not None: + # Allow multiple plugin directories to be specified + for pd_text in custom_dirs.split(','): + pd = Path(pd_text.strip()).absolute() - # Ensure the directory has an __init__.py file - init_filename = pd.joinpath('__init__.py') + # Attempt to create the directory if it does not already exist + if not pd.exists(): + try: + pd.mkdir(exist_ok=True) + except Exception: # pragma: no cover + logger.error(f"Could not create plugin directory '{pd}'") + continue - if not init_filename.exists(): - try: - init_filename.write_text("# InvenTree plugin directory\n") - except Exception: # pragma: no cover - logger.error(f"Could not create file '{init_filename}'") - continue + # Ensure the directory has an __init__.py file + init_filename = pd.joinpath('__init__.py') - # By this point, we have confirmed that the directory at least exists - if pd.exists() and pd.is_dir(): - # Convert to python dot-path - if pd.is_relative_to(settings.BASE_DIR): - pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts) - else: - pd_path = str(pd) + if not init_filename.exists(): + try: + init_filename.write_text("# InvenTree plugin directory\n") + except Exception: # pragma: no cover + logger.error(f"Could not create file '{init_filename}'") + continue - # Add path - dirs.append(pd_path) - logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'") + # By this point, we have confirmed that the directory at least exists + if pd.exists() and pd.is_dir(): + # Convert to python dot-path + if pd.is_relative_to(settings.BASE_DIR): + pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts) + else: + pd_path = str(pd) + + # Add path + dirs.append(pd_path) + logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'") return dirs def collect_plugins(self): """Collect plugins from all possible ways of loading. Returned as list.""" - if not settings.PLUGINS_ENABLED: - # Plugins not enabled, do nothing - return # pragma: no cover collected_plugins = [] @@ -293,17 +289,20 @@ class PluginsRegistry: if modules: [collected_plugins.append(item) for item in modules] - # Check if not running in testing mode and apps should be loaded from hooks - if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): - # Collect plugins from setup entry points - for entry in get_entrypoints(): - try: - plugin = entry.load() - plugin.is_package = True - plugin._get_package_metadata() - collected_plugins.append(plugin) - except Exception as error: # pragma: no cover - handle_error(error, do_raise=False, log_name='discovery') + # From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled + if settings.PLUGINS_ENABLED: + + # Check if not running in testing mode and apps should be loaded from hooks + if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): + # Collect plugins from setup entry points + for entry in get_entrypoints(): + try: + plugin = entry.load() + plugin.is_package = True + plugin._get_package_metadata() + collected_plugins.append(plugin) + except Exception as error: # pragma: no cover + handle_error(error, do_raise=False, log_name='discovery') # Log collected plugins logger.info(f'Collected {len(collected_plugins)} plugins!')