[bug] Plugin fix (#12805) (#12806)

* [bug] Handle null plugin instance

* Add regression test

* Fix secondary instances of same bug

(cherry picked from commit 2566d332a8)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
github-actions[bot]
2026-09-07 17:12:37 +10:00
committed by GitHub
co-authored by Oliver
parent b5c6742491
commit ad8013dc23
2 changed files with 53 additions and 2 deletions
+4 -2
View File
@@ -741,7 +741,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
@@ -831,7 +833,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: