2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 21:15:41 +00:00

Allow loading of "builtin" plugins, even if "plugins" are not explicitly loaded

This commit is contained in:
Oliver Walters
2022-11-01 23:14:50 +11:00
parent db45b6f9dc
commit 058587d86c
3 changed files with 96 additions and 83 deletions

View File

@ -7,7 +7,6 @@ The main code for plugin special sauce is in the plugin registry in `InvenTree/p
import logging import logging
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from maintenance_mode.core import set_maintenance_mode from maintenance_mode.core import set_maintenance_mode
@ -26,34 +25,34 @@ class PluginAppConfig(AppConfig):
def ready(self): def ready(self):
"""The ready method is extended to initialize plugins.""" """The ready method is extended to initialize plugins."""
if settings.PLUGINS_ENABLED: if not canAppAccessDatabase(allow_test=True, allow_plugins=True):
if not canAppAccessDatabase(allow_test=True, allow_plugins=True): logger.info("Skipping plugin loading sequence") # pragma: no cover
logger.info("Skipping plugin loading sequence") # pragma: no cover else:
else: logger.info('Loading InvenTree plugins')
logger.info('Loading InvenTree plugins')
if not registry.is_loading: if not registry.is_loading:
# this is the first startup # this is the first startup
try: try:
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False): if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False):
# make sure all plugins are installed # make sure all plugins are installed
registry.install_plugin_file() registry.install_plugin_file()
except Exception: # pragma: no cover except Exception: # pragma: no cover
pass pass
# get plugins and init them # get plugins and init them
registry.plugin_modules = registry.collect_plugins() registry.plugin_modules = registry.collect_plugins()
registry.load_plugins() registry.load_plugins()
# drop out of maintenance # drop out of maintenance
# makes sure we did not have an error in reloading and maintenance is still active # makes sure we did not have an error in reloading and maintenance is still active
set_maintenance_mode(False) set_maintenance_mode(False)
# check git version # check git version
registry.git_is_modern = 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') 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: else:
logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover

View File

@ -106,6 +106,11 @@ class MetaBase:
def is_active(self): def is_active(self):
"""Return True if this plugin is currently active.""" """Return True if this plugin is currently active."""
# Builtin plugins are always considered "active"
if self.is_builtin():
return True
cfg = self.plugin_config() cfg = self.plugin_config()
if cfg: if cfg:
@ -300,6 +305,16 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
"""Is this plugin part of the samples?""" """Is this plugin part of the samples?"""
return self.check_is_sample() 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 @classmethod
def check_package_path(cls): def check_package_path(cls):
"""Path to the plugin.""" """Path to the plugin."""

View File

@ -108,9 +108,6 @@ class PluginsRegistry:
Args: Args:
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. 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') logger.info('Start loading plugins')
@ -167,9 +164,6 @@ class PluginsRegistry:
def unload_plugins(self): def unload_plugins(self):
"""Unload and deactivate all IntegrationPlugins.""" """Unload and deactivate all IntegrationPlugins."""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
logger.info('Start unloading plugins') logger.info('Start unloading plugins')
@ -187,6 +181,7 @@ class PluginsRegistry:
# remove maintenance # remove maintenance
if not _maintenance: if not _maintenance:
set_maintenance_mode(False) # pragma: no cover set_maintenance_mode(False) # pragma: no cover
logger.info('Finished unloading plugins') logger.info('Finished unloading plugins')
def reload_plugins(self, full_reload: bool = False): def reload_plugins(self, full_reload: bool = False):
@ -210,62 +205,63 @@ class PluginsRegistry:
def plugin_dirs(self): def plugin_dirs(self):
"""Construct a list of directories from where plugins can be loaded""" """Construct a list of directories from where plugins can be loaded"""
# Builtin plugins are *always* loaded
dirs = ['plugin.builtin', ] dirs = ['plugin.builtin', ]
if settings.TESTING or settings.DEBUG: if settings.PLUGINS_ENABLED:
# If in TEST or DEBUG mode, load plugins from the 'samples' directory # Any 'external' plugins are only loaded if PLUGINS_ENABLED is set to True
dirs.append('plugin.samples')
if settings.TESTING: if settings.TESTING or settings.DEBUG:
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None) # If in TEST or DEBUG mode, load plugins from the 'samples' directory
else: # pragma: no cover dirs.append('plugin.samples')
custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
# Load from user specified directories (unless in testing mode) if settings.TESTING:
dirs.append('plugins') 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: # Load from user specified directories (unless in testing mode)
# Allow multiple plugin directories to be specified dirs.append('plugins')
for pd_text in custom_dirs.split(','):
pd = Path(pd_text.strip()).absolute()
# Attempt to create the directory if it does not already exist if custom_dirs is not None:
if not pd.exists(): # Allow multiple plugin directories to be specified
try: for pd_text in custom_dirs.split(','):
pd.mkdir(exist_ok=True) pd = Path(pd_text.strip()).absolute()
except Exception: # pragma: no cover
logger.error(f"Could not create plugin directory '{pd}'")
continue
# Ensure the directory has an __init__.py file # Attempt to create the directory if it does not already exist
init_filename = pd.joinpath('__init__.py') 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(): # Ensure the directory has an __init__.py file
try: init_filename = pd.joinpath('__init__.py')
init_filename.write_text("# InvenTree plugin directory\n")
except Exception: # pragma: no cover
logger.error(f"Could not create file '{init_filename}'")
continue
# By this point, we have confirmed that the directory at least exists if not init_filename.exists():
if pd.exists() and pd.is_dir(): try:
# Convert to python dot-path init_filename.write_text("# InvenTree plugin directory\n")
if pd.is_relative_to(settings.BASE_DIR): except Exception: # pragma: no cover
pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts) logger.error(f"Could not create file '{init_filename}'")
else: continue
pd_path = str(pd)
# Add path # By this point, we have confirmed that the directory at least exists
dirs.append(pd_path) if pd.exists() and pd.is_dir():
logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'") # 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 return dirs
def collect_plugins(self): def collect_plugins(self):
"""Collect plugins from all possible ways of loading. Returned as list.""" """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 = [] collected_plugins = []
@ -293,17 +289,20 @@ class PluginsRegistry:
if modules: if modules:
[collected_plugins.append(item) for item in modules] [collected_plugins.append(item) for item in modules]
# Check if not running in testing mode and apps should be loaded from hooks # From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled
if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): if settings.PLUGINS_ENABLED:
# Collect plugins from setup entry points
for entry in get_entrypoints(): # Check if not running in testing mode and apps should be loaded from hooks
try: if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP):
plugin = entry.load() # Collect plugins from setup entry points
plugin.is_package = True for entry in get_entrypoints():
plugin._get_package_metadata() try:
collected_plugins.append(plugin) plugin = entry.load()
except Exception as error: # pragma: no cover plugin.is_package = True
handle_error(error, do_raise=False, log_name='discovery') 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 # Log collected plugins
logger.info(f'Collected {len(collected_plugins)} plugins!') logger.info(f'Collected {len(collected_plugins)} plugins!')