2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-16 20:15:44 +00:00

[Feature] Mandatory Plugins (#9339)

* Define which builtin plugins are always-active

* Adds 'mandatory' property to PluginConfig

* Update API / frontend

* Fix form method

* Tweaks

* Bump API version

* Tweak unit tests
This commit is contained in:
Oliver
2025-03-20 10:19:31 +11:00
committed by GitHub
parent ae1ec31ca9
commit bdc5f9e84e
7 changed files with 80 additions and 21 deletions

View File

@ -1,13 +1,17 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 326
INVENTREE_API_VERSION = 327
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v327 - 2025-03-20 : https://github.com/inventree/InvenTree/pull/9339
- Adds "is_mandatory" field to the Plugin API
- Adds ability to filter by "mandatory" status in the Plugin API
v326 - 2025-03-18 : https://github.com/inventree/InvenTree/pull/9096
- Overhaul the data-export API functionality
- Allow customization of data exporting via plugins

View File

@ -18,6 +18,7 @@ import plugin.serializers as PluginSerializers
from common.api import GlobalSettingsPermissions
from InvenTree.api import MetadataView
from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.helpers import str2bool
from InvenTree.mixins import (
CreateAPI,
ListAPI,
@ -27,13 +28,13 @@ from InvenTree.mixins import (
UpdateAPI,
)
from InvenTree.permissions import IsSuperuser, IsSuperuserOrReadOnly
from plugin import registry
from plugin.base.action.api import ActionPluginView
from plugin.base.barcodes.api import barcode_api_urls
from plugin.base.locate.api import LocatePluginView
from plugin.base.ui.api import ui_plugins_api_urls
from plugin.models import PluginConfig, PluginSetting
from plugin.plugin import InvenTreePlugin
from plugin.registry import registry
class PluginFilter(rest_filters.FilterSet):
@ -75,7 +76,7 @@ class PluginFilter(rest_filters.FilterSet):
return queryset.filter(pk__in=matches)
builtin = rest_filters.BooleanFilter(
field_name='builtin', label='Builtin', method='filter_builtin'
field_name='builtin', label=_('Builtin'), method='filter_builtin'
)
def filter_builtin(self, queryset, name, value):
@ -88,8 +89,19 @@ class PluginFilter(rest_filters.FilterSet):
return queryset.filter(pk__in=matches)
mandatory = rest_filters.BooleanFilter(
field_name='mandatory', label=_('Mandatory'), method='filter_mandatory'
)
def filter_mandatory(self, queryset, name, value):
"""Filter by 'mandatory' flag."""
if str2bool(value):
return queryset.filter(key__in=registry.MANDATORY_PLUGINS)
else:
return queryset.exclude(key__in=registry.MANDATORY_PLUGINS)
sample = rest_filters.BooleanFilter(
field_name='sample', label='Sample', method='filter_sample'
field_name='sample', label=_('Sample'), method='filter_sample'
)
def filter_sample(self, queryset, name, value):
@ -103,7 +115,7 @@ class PluginFilter(rest_filters.FilterSet):
return queryset.filter(pk__in=matches)
installed = rest_filters.BooleanFilter(
field_name='installed', label='Installed', method='filter_installed'
field_name='installed', label=_('Installed'), method='filter_installed'
)
def filter_installed(self, queryset, name, value):
@ -135,8 +147,6 @@ class PluginList(ListAPI):
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = ['active']
ordering_fields = ['key', 'name', 'active']
ordering = ['-active', 'name', 'key']

View File

@ -15,6 +15,11 @@ class SupplierBarcodeTests(InvenTreeAPITestCase):
SCAN_URL = reverse('api-barcode-scan')
def setUp(self):
"""Ensure the digikey plugin is enabled."""
super().setUp()
registry.set_plugin_state('digikeyplugin', True)
@classmethod
def setUpTestData(cls):
"""Create supplier parts for barcodes."""

View File

@ -13,8 +13,9 @@ from django.utils.translation import gettext_lazy as _
import common.models
import InvenTree.models
import plugin.staticfiles
from plugin import InvenTreePlugin, registry
from plugin import InvenTreePlugin
from plugin.events import PluginEvents, trigger_event
from plugin.registry import registry
class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
@ -146,8 +147,8 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
super().save(force_insert, force_update, *args, **kwargs)
if self.is_builtin():
# Force active if builtin
if self.is_mandatory():
# Force active if mandatory plugin
self.active = True
if not no_reload and self.active != self.__org_active:
@ -180,6 +181,11 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
return self.plugin.check_is_builtin()
@admin.display(boolean=True, description=_('Mandatory Plugin'))
def is_mandatory(self) -> bool:
"""Return True if this plugin is mandatory."""
return self.key in registry.MANDATORY_PLUGINS
@admin.display(boolean=True, description=_('Package Plugin'))
def is_package(self) -> bool:
"""Return True if this is a 'package' plugin."""

View File

@ -53,6 +53,21 @@ class PluginsRegistry:
DEFAULT_MIXIN_ORDER = [SettingsMixin, ScheduleMixin, AppMixin, UrlsMixin]
# This list of plugins are *always* enabled, and are loaded by default
# This is because they provide core functionality to the InvenTree system
# Other 'builtin' plugins are automatically loaded, but can be disabled by the user
MANDATORY_PLUGINS = [
'inventreebarcode',
'bom-exporter',
'inventree-exporter',
'inventreecorenotificationsplugin',
'inventreecurrencyexchange',
'inventreecorenotificationsplugin',
'inventreelabel',
'inventreelabelmachine',
'inventreelabelsheet',
]
def __init__(self) -> None:
"""Initialize registry.
@ -518,10 +533,11 @@ class PluginsRegistry:
if getattr(plugin, 'is_package', False):
package_name = getattr(plugin, 'package_name', None)
# Auto-enable builtin plugins
if builtin and plg_db and not plg_db.active:
plg_db.active = True
plg_db.save()
# Auto-enable default builtin plugins
if builtin and plg_db and plg_db.is_mandatory():
if not plg_db.active:
plg_db.active = True
plg_db.save()
# Save the package_name attribute to the plugin
if plg_db.package_name != package_name:

View File

@ -59,12 +59,14 @@ class PluginConfigSerializer(serializers.ModelSerializer):
'is_sample',
'is_installed',
'is_package',
'is_mandatory',
]
read_only_fields = ['key', 'is_builtin', 'is_sample', 'is_installed']
meta = serializers.DictField(read_only=True)
mixins = serializers.DictField(read_only=True)
is_mandatory = serializers.BooleanField(read_only=True)
class PluginAdminDetailSerializer(serializers.ModelSerializer):