[bug] Plugin fix (#12805)

* [bug] Handle null plugin instance

* Add regression test

* Fix secondary instances of same bug
This commit is contained in:
Oliver
2026-09-07 14:45:48 +10:00
committed by GitHub
parent 9bc5054d84
commit 2566d332a8
2 changed files with 53 additions and 2 deletions
+4 -2
View File
@@ -747,7 +747,9 @@ class PluginsRegistry:
self.plugins[key] = plugin
else:
# Deactivate plugin in db (if currently set as active)
if not settings.PLUGIN_TESTING and plugin.db.active: # pragma: no cover
if (
not settings.PLUGIN_TESTING and plugin.db and plugin.db.active
): # pragma: no cover
plugin.db.active = False
plugin.db.save(no_reload=True)
self.plugins_inactive[key] = plugin.db
@@ -837,7 +839,7 @@ class PluginsRegistry:
dt = time.time() - t_start
logger.debug('Loaded plugin `%s` in %.3fs', plg_name, dt)
if mandatory and not plg_db.active: # pragma: no cover
if mandatory and plg_db and not plg_db.active: # pragma: no cover
# If this is a mandatory plugin, ensure it is marked as active
logger.info(
'Plugin `%s` is a mandatory plugin - activating', plg_name
@@ -389,6 +389,55 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
'This is a dummy error', find_error('Test:init_plugin', 'broken_sample')
)
def test_init_plugin_missing_config(self):
"""Test that _init_plugin does not crash if PluginConfig cannot be looked up.
get_plugin_config() can legitimately return None - e.g. if the database
is not ready, or PluginConfig creation is disallowed in the current
context - leaving plugin.db as None. _init_plugin must still be able to
mark such a plugin as inactive without raising
AttributeError: 'NoneType' object has no attribute 'active'.
"""
class MissingConfigPlugin(InvenTreePlugin):
NAME = 'MissingConfigPlugin'
SLUG = 'missingconfigplugin'
self.addCleanup(registry.reload_plugins, full_reload=True, collect=True)
# PLUGIN_TESTING=True would force-load the plugin regardless of its
# (missing) PluginConfig - disable it to hit the 'inactive' path below
with override_settings(PLUGIN_TESTING=False):
with mock.patch.object(registry, 'get_plugin_config', return_value=None):
registry._init_plugin(MissingConfigPlugin, {})
self.assertIn('missingconfigplugin', registry.plugins_full)
self.assertNotIn('missingconfigplugin', registry.plugins)
def test_init_plugin_missing_config_mandatory(self):
"""Test that a mandatory plugin with no PluginConfig does not error out.
Same underlying gap as test_init_plugin_missing_config, but hit via the
'ensure mandatory plugin is active' branch instead of the 'deactivate'
branch - both dereferenced plg_db.active without checking plg_db was
actually found.
"""
class MissingConfigMandatoryPlugin(InvenTreePlugin):
NAME = 'MissingConfigMandatoryPlugin'
SLUG = 'missingconfigmandatoryplugin'
self.addCleanup(registry.reload_plugins, full_reload=True, collect=True)
registry.errors.pop('MissingConfigMandatoryPlugin:init_plugin', None)
with override_settings(PLUGINS_MANDATORY=['missingconfigmandatoryplugin']):
with mock.patch.object(registry, 'get_plugin_config', return_value=None):
registry._init_plugin(MissingConfigMandatoryPlugin, {})
# No spurious 'plugin failed to load' error should have been recorded
self.assertNotIn('MissingConfigMandatoryPlugin:init_plugin', registry.errors)
self.assertIn('missingconfigmandatoryplugin', registry.plugins_full)
def test_plugin_override_mandatory(self):
"""Test that a plugin cannot override the is_mandatory method."""
with self.assertRaises(TypeError) as e: