diff --git a/InvenTree/plugin/base/integration/test_mixins.py b/InvenTree/plugin/base/integration/test_mixins.py index 40c073413a..c5e8c4e712 100644 --- a/InvenTree/plugin/base/integration/test_mixins.py +++ b/InvenTree/plugin/base/integration/test_mixins.py @@ -24,9 +24,9 @@ class BaseMixinDefinition: def test_mixin_name(self): """Test that the mixin registers itseld correctly.""" # mixin name - self.assertIn(self.MIXIN_NAME, [item['key'] for item in self.mixin.registered_mixins]) + self.assertIn(self.MIXIN_NAME, {item['key'] for item in self.mixin.registered_mixins.values()}) # human name - self.assertIn(self.MIXIN_HUMAN_NAME, [item['human_name'] for item in self.mixin.registered_mixins]) + self.assertIn(self.MIXIN_HUMAN_NAME, {item['human_name'] for item in self.mixin.registered_mixins.values()}) class SettingsMixinTest(BaseMixinDefinition, InvenTreeTestCase): diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 57f80bdecf..b34585fb97 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -1,5 +1,6 @@ """Plugin model definitions.""" +import inspect import warnings from django.conf import settings @@ -58,7 +59,9 @@ class PluginConfig(models.Model): def mixins(self): """Returns all registered mixins.""" try: - return self.plugin._mixinreg + if inspect.isclass(self.plugin): + return self.plugin.get_registered_mixins(self, with_base=True, with_cls=False) + return self.plugin.get_registered_mixins(with_base=True, with_cls=False) except (AttributeError, ValueError): # pragma: no cover return {} @@ -166,7 +169,9 @@ class PluginSetting(common.models.BaseInvenTreeSetting): if issubclass(plugin.__class__, InvenTreePlugin): plugin = plugin.plugin_config() - kwargs['settings'] = registry.mixins_settings.get(plugin.key, {}) + mixin_settings = getattr(registry, 'mixins_settings') + if mixin_settings: + kwargs['settings'] = mixin_settings.get(plugin.key, {}) return super().get_setting_definition(key, **kwargs) diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index 29b9891eff..8ef51bb203 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -161,19 +161,29 @@ class MixinBase: self._mixinreg[key] = { 'key': key, 'human_name': human_name, + 'cls': cls, } + def get_registered_mixins(self, with_base: bool = False, with_cls: bool = True): + """Get all registered mixins for the plugin.""" + mixins = getattr(self, '_mixinreg', None) + if not mixins: + return {} + + mixins = mixins.copy() + # filter out base + if not with_base and 'base' in mixins: + del mixins['base'] + + # Do not return the mixin class if flas is set + if not with_cls: + return {key: {k: v for k, v in mixin.items() if k != 'cls'} for key, mixin in mixins.items()} + return mixins + @property def registered_mixins(self, with_base: bool = False): """Get all registered mixins for the plugin.""" - mixins = getattr(self, '_mixinreg', None) - if mixins: - # filter out base - if not with_base and 'base' in mixins: - del mixins['base'] - # only return dict - mixins = list(mixins.values()) - return mixins + return self.get_registered_mixins(with_base=with_base) class VersionMixin: diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 0b546fca47..3079993825 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -42,7 +42,7 @@ class PluginsRegistry: DEFAULT_MIXIN_ORDER = [SettingsMixin, ScheduleMixin, AppMixin, UrlsMixin] - def __init__(self, mixin_order: list = None) -> None: + def __init__(self) -> None: """Initialize registry. Set up all needed references for internal and external states. @@ -53,6 +53,7 @@ class PluginsRegistry: self.plugins_full: Dict[str, InvenTreePlugin] = {} # List of all plugin instances self.plugin_modules: List(InvenTreePlugin) = [] # Holds all discovered plugins + self.mixin_modules: Dict[str, any] = {} # Holds all discovered mixins self.errors = {} # Holds discovering errors @@ -63,10 +64,6 @@ class PluginsRegistry: self.installed_apps = [] # Holds all added plugin_paths - # mixins - self.mixins_settings = {} - self.mixin_order = mixin_order or self.DEFAULT_MIXIN_ORDER - def get_plugin(self, slug): """Lookup plugin by slug (unique key).""" if slug not in self.plugins: @@ -322,6 +319,15 @@ class PluginsRegistry: return collected_plugins + def discover_mixins(self): + """Discover all mixins from plugins and register them.""" + collected_mixins = {} + + for plg in self.plugins.values(): + collected_mixins.update(plg.get_registered_mixins()) + + self.mixin_modules = collected_mixins + def install_plugin_file(self): """Make sure all plugins are installed in the current environment.""" if settings.PLUGIN_FILE_CHECKED: @@ -468,6 +474,17 @@ class PluginsRegistry: else: # pragma: no cover safe_reference(plugin=plg, key=plg_key, active=False) + def __get_mixin_order(self): + """Returns a list of mixin classes, in the order that they should be activated.""" + # Preset list of mixins + order = self.DEFAULT_MIXIN_ORDER + + # Append mixins that are not defined in the default list + order += [m.get('cls') for m in self.mixin_modules.values() if m.get('cls') not in order] + + # Final list of mixins + return order + def _activate_plugins(self, force_reload=False, full_reload: bool = False): """Run activation functions for all plugins. @@ -475,11 +492,14 @@ class PluginsRegistry: force_reload (bool, optional): Also reload base apps. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. """ - # activate integrations + # Collect mixins + self.discover_mixins() + + # Activate integrations plugins = self.plugins.items() logger.info(f'Found {len(plugins)} active plugins') - for mixin in self.mixin_order: + for mixin in self.__get_mixin_order(): if hasattr(mixin, '_activate_mixin'): mixin._activate_mixin(self, plugins, force_reload=force_reload, full_reload=full_reload) @@ -491,7 +511,7 @@ class PluginsRegistry: Args: force_reload (bool, optional): Also reload base apps. Defaults to False. """ - for mixin in self.mixin_order: + for mixin in reversed(self.__get_mixin_order()): if hasattr(mixin, '_deactivate_mixin'): mixin._deactivate_mixin(self, force_reload=force_reload)