diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 8b59306e3c..f7b6b2ce80 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -133,6 +133,34 @@ STATIC_URL = '/static/' # Web URL endpoint for served media files MEDIA_URL = '/media/' +# Are plugins enabled? +PLUGINS_ENABLED = get_boolean_setting( + 'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False +) + +PLUGINS_INSTALL_DISABLED = get_boolean_setting( + 'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False +) + +PLUGIN_FILE = config.get_plugin_file() + +# Plugin test settings +PLUGIN_TESTING = get_setting( + 'INVENTREE_PLUGIN_TESTING', 'PLUGIN_TESTING', TESTING +) # Are plugins being tested? + +PLUGIN_TESTING_SETUP = get_setting( + 'INVENTREE_PLUGIN_TESTING_SETUP', 'PLUGIN_TESTING_SETUP', False +) # Load plugins from setup hooks in testing? + +PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now + +PLUGIN_RETRY = get_setting( + 'INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 3, typecast=int +) # How often should plugin loading be tried? + +PLUGIN_FILE_CHECKED = False # Was the plugin file checked? + STATICFILES_DIRS = [] # Translated Template settings @@ -153,6 +181,12 @@ if DEBUG and 'collectstatic' not in sys.argv: if web_dir.exists(): STATICFILES_DIRS.append(web_dir) + # Append directory for sample plugin static content (if in debug mode) + if PLUGINS_ENABLED: + print('Adding plugin sample static content') + STATICFILES_DIRS.append(BASE_DIR.joinpath('plugin', 'samples', 'static')) + + print('-', STATICFILES_DIRS[-1]) STATFILES_I18_PROCESSORS = ['InvenTree.context.status_codes'] # Color Themes Directory @@ -1254,29 +1288,6 @@ IGNORED_ERRORS = [Http404, django.core.exceptions.PermissionDenied] MAINTENANCE_MODE_RETRY_AFTER = 10 MAINTENANCE_MODE_STATE_BACKEND = 'InvenTree.backends.InvenTreeMaintenanceModeBackend' -# Are plugins enabled? -PLUGINS_ENABLED = get_boolean_setting( - 'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False -) -PLUGINS_INSTALL_DISABLED = get_boolean_setting( - 'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False -) - -PLUGIN_FILE = config.get_plugin_file() - -# Plugin test settings -PLUGIN_TESTING = get_setting( - 'INVENTREE_PLUGIN_TESTING', 'PLUGIN_TESTING', TESTING -) # Are plugins being tested? -PLUGIN_TESTING_SETUP = get_setting( - 'INVENTREE_PLUGIN_TESTING_SETUP', 'PLUGIN_TESTING_SETUP', False -) # Load plugins from setup hooks in testing? -PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now -PLUGIN_RETRY = get_setting( - 'INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 3, typecast=int -) # How often should plugin loading be tried? -PLUGIN_FILE_CHECKED = False # Was the plugin file checked? - # Flag to allow table events during testing TESTING_TABLE_EVENTS = False diff --git a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py index 1642342130..4c22066c74 100644 --- a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py @@ -36,6 +36,12 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug 'default': True, 'validator': bool, }, + 'ENABLE_DYNAMIC_PANEL': { + 'name': _('Enable Dynamic Panel'), + 'description': _('Enable dynamic panels for testing'), + 'default': True, + 'validator': bool, + }, } def get_custom_panels(self, instance_type: str, instance_id: int, request): @@ -74,6 +80,14 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug 'source': '/this/does/not/exist.js', }) + # A dynamic panel which will be injected into the UI (loaded from external file) + if self.get_setting('ENABLE_DYNAMIC_PANEL'): + panels.append({ + 'name': 'dynamic_panel', + 'label': 'Dynamic Panel', + 'source': '/static/plugin/sample_panel.js', + }) + # Next, add a custom panel which will appear on the 'part' page # Note that this content is rendered from a template file, # using the django templating system diff --git a/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js b/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js new file mode 100644 index 0000000000..d79651b1d6 --- /dev/null +++ b/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js @@ -0,0 +1,30 @@ +/** + * A sample panel plugin for InvenTree. + * + * This plugin file is dynamically loaded, + * as specified in the plugin/samples/integration/user_interface_sample.py + * + * It provides a simple example of how panels can be dynamically rendered, + * as well as dynamically hidden, based on the provided context. + */ + +export function renderPanel(context) { + + const target = context.target; + + if (!target) { + console.error("No target provided to renderPanel"); + return; + } + + target.innerHTML = `hello world!`; +} + + +// Dynamically hide the panel based on the provided context +export function isPanelHidden(context) { + console.log("isPanelHidden:"); + console.log("context:", context); + + return false; +} diff --git a/src/backend/InvenTree/plugin/serializers.py b/src/backend/InvenTree/plugin/serializers.py index dc109bdc3c..36e9d55881 100644 --- a/src/backend/InvenTree/plugin/serializers.py +++ b/src/backend/InvenTree/plugin/serializers.py @@ -309,15 +309,27 @@ class PluginPanelSerializer(serializers.Serializer): class Meta: """Meta for serializer.""" - fields = ['plugin', 'name', 'label', 'icon', 'content', 'source'] + fields = [ + 'plugin', + 'name', + 'label', + # Following fields are optional + 'icon', + 'content', + 'source', + 'render_function', + 'hidden_function', + ] # Required fields plugin = serializers.CharField( label=_('Plugin Key'), required=True, allow_blank=False ) + name = serializers.CharField( label=_('Panel Name'), required=True, allow_blank=False ) + label = serializers.CharField( label=_('Panel Title'), required=True, allow_blank=False ) @@ -326,9 +338,27 @@ class PluginPanelSerializer(serializers.Serializer): icon = serializers.CharField( label=_('Panel Icon'), required=False, allow_blank=True ) + content = serializers.CharField( label=_('Panel Content (HTML)'), required=False, allow_blank=True ) + source = serializers.CharField( label=_('Panel Source (javascript)'), required=False, allow_blank=True ) + + render_function = serializers.CharField( + label=_('Render Function'), + help_text=_('Function to render the panel content'), + default='renderPanel', + required=False, + allow_blank=True, + ) + + hidden_function = serializers.CharField( + label=_('Hidden Function'), + help_text=_('Function name to determine if the the panel content is hidden'), + default='isPanelHidden', + required=False, + allow_blank=True, + )