diff --git a/src/backend/InvenTree/plugin/base/integration/PanelMixin.py b/src/backend/InvenTree/plugin/base/integration/PanelMixin.py new file mode 100644 index 0000000000..d4d374e67b --- /dev/null +++ b/src/backend/InvenTree/plugin/base/integration/PanelMixin.py @@ -0,0 +1,149 @@ +"""PanelMixin plugin class definition. + +Allows integration of custom 'panels' into the user interface. +""" + +import logging + +from InvenTree.helpers import generateTestKey +from plugin.helpers import MixinNotImplementedError, render_template, render_text + +logger = logging.getLogger('inventree') + + +class PanelMixin: + """Mixin which allows integration of custom 'panels' into a particular page. + + The mixin provides a number of key functionalities: + + - Adds an (initially hidden) panel to the page + - Allows rendering of custom templated content to the panel + - Adds a menu item to the 'navbar' on the left side of the screen + - Allows custom javascript to be run when the panel is initially loaded + + The PanelMixin class allows multiple panels to be returned for any page, + and also allows the plugin to return panels for many different pages. + + Any class implementing this mixin must provide the 'get_custom_panels' method, + which dynamically returns the custom panels for a particular page. + + This method is provided with: + + - view : The View object which is being rendered + - request : The HTTPRequest object + + Note that as this is called dynamically (per request), + then the actual panels returned can vary depending on the particular request or page + + The 'get_custom_panels' method must return a list of dict objects, each with the following keys: + + - title : The title of the panel, to appear in the sidebar menu + - description : Extra descriptive text (optional) + - icon : The icon to appear in the sidebar menu + - content : The HTML content to appear in the panel, OR + - content_template : A template file which will be rendered to produce the panel content + - javascript : The javascript content to be rendered when the panel is loaded, OR + - javascript_template : A template file which will be rendered to produce javascript + + e.g. + + { + 'title': "Updates", + 'description': "Latest updates for this part", + 'javascript': 'alert("You just loaded this panel!")', + 'content': 'Hello world', + } + """ + + class MixinMeta: + """Meta for mixin.""" + + MIXIN_NAME = 'Panel' + + def __init__(self): + """Register mixin.""" + super().__init__() + self.add_mixin('panel', True, __class__) + + def get_custom_panels(self, view, request): + """This method *must* be implemented by the plugin class.""" + raise MixinNotImplementedError( + f"{__class__} is missing the 'get_custom_panels' method" + ) + + def get_panel_context(self, view, request, context): + """Build the context data to be used for template rendering. + + Custom class can override this to provide any custom context data. + + (See the example in "custom_panel_sample.py") + """ + # Provide some standard context items to the template for rendering + context['plugin'] = self + context['request'] = request + context['user'] = getattr(request, 'user', None) + context['view'] = view + + try: + context['object'] = view.get_object() + except AttributeError: # pragma: no cover + pass + + return context + + def render_panels(self, view, request, context): + """Get panels for a view. + + Args: + view: Current view context + request: Current request for passthrough + context: Rendering context + + Returns: + Array of panels + """ + panels = [] + + # Construct an updated context object for template rendering + ctx = self.get_panel_context(view, request, context) + + custom_panels = self.get_custom_panels(view, request) or [] + + for panel in custom_panels: + content_template = panel.get('content_template', None) + javascript_template = panel.get('javascript_template', None) + + if content_template: + # Render content template to HTML + panel['content'] = render_template(self, content_template, ctx) + else: + # Render content string to HTML + panel['content'] = render_text(panel.get('content', ''), ctx) + + if javascript_template: + # Render javascript template to HTML + panel['javascript'] = render_template(self, javascript_template, ctx) + else: + # Render javascript string to HTML + panel['javascript'] = render_text(panel.get('javascript', ''), ctx) + + # Check for required keys + required_keys = ['title', 'content'] + + if any(key not in panel for key in required_keys): + logger.warning( + 'Custom panel for plugin %s is missing a required parameter', + __class__, + ) + continue + + # Add some information on this plugin + panel['plugin'] = self + panel['slug'] = self.slug + + # Add a 'key' for the panel, which is mostly guaranteed to be unique + panel['key'] = generateTestKey(self.slug + panel.get('title', 'panel')) + + panels.append(panel) + + return panels diff --git a/src/backend/InvenTree/plugin/base/integration/mixins.py b/src/backend/InvenTree/plugin/base/integration/mixins.py index 0421472483..5644dc8f8e 100644 --- a/src/backend/InvenTree/plugin/base/integration/mixins.py +++ b/src/backend/InvenTree/plugin/base/integration/mixins.py @@ -2,8 +2,7 @@ import logging -from InvenTree.helpers import generateTestKey -from plugin.helpers import MixinNotImplementedError, render_template, render_text +from plugin.helpers import MixinNotImplementedError logger = logging.getLogger('inventree') @@ -54,144 +53,6 @@ class NavigationMixin: return getattr(self, 'NAVIGATION_TAB_ICON', 'fas fa-question') -class PanelMixin: - """Mixin which allows integration of custom 'panels' into a particular page. - - The mixin provides a number of key functionalities: - - - Adds an (initially hidden) panel to the page - - Allows rendering of custom templated content to the panel - - Adds a menu item to the 'navbar' on the left side of the screen - - Allows custom javascript to be run when the panel is initially loaded - - The PanelMixin class allows multiple panels to be returned for any page, - and also allows the plugin to return panels for many different pages. - - Any class implementing this mixin must provide the 'get_custom_panels' method, - which dynamically returns the custom panels for a particular page. - - This method is provided with: - - - view : The View object which is being rendered - - request : The HTTPRequest object - - Note that as this is called dynamically (per request), - then the actual panels returned can vary depending on the particular request or page - - The 'get_custom_panels' method must return a list of dict objects, each with the following keys: - - - title : The title of the panel, to appear in the sidebar menu - - description : Extra descriptive text (optional) - - icon : The icon to appear in the sidebar menu - - content : The HTML content to appear in the panel, OR - - content_template : A template file which will be rendered to produce the panel content - - javascript : The javascript content to be rendered when the panel is loaded, OR - - javascript_template : A template file which will be rendered to produce javascript - - e.g. - - { - 'title': "Updates", - 'description': "Latest updates for this part", - 'javascript': 'alert("You just loaded this panel!")', - 'content': 'Hello world', - } - """ - - class MixinMeta: - """Meta for mixin.""" - - MIXIN_NAME = 'Panel' - - def __init__(self): - """Register mixin.""" - super().__init__() - self.add_mixin('panel', True, __class__) - - def get_custom_panels(self, view, request): - """This method *must* be implemented by the plugin class.""" - raise MixinNotImplementedError( - f"{__class__} is missing the 'get_custom_panels' method" - ) - - def get_panel_context(self, view, request, context): - """Build the context data to be used for template rendering. - - Custom class can override this to provide any custom context data. - - (See the example in "custom_panel_sample.py") - """ - # Provide some standard context items to the template for rendering - context['plugin'] = self - context['request'] = request - context['user'] = getattr(request, 'user', None) - context['view'] = view - - try: - context['object'] = view.get_object() - except AttributeError: # pragma: no cover - pass - - return context - - def render_panels(self, view, request, context): - """Get panels for a view. - - Args: - view: Current view context - request: Current request for passthrough - context: Rendering context - - Returns: - Array of panels - """ - panels = [] - - # Construct an updated context object for template rendering - ctx = self.get_panel_context(view, request, context) - - custom_panels = self.get_custom_panels(view, request) or [] - - for panel in custom_panels: - content_template = panel.get('content_template', None) - javascript_template = panel.get('javascript_template', None) - - if content_template: - # Render content template to HTML - panel['content'] = render_template(self, content_template, ctx) - else: - # Render content string to HTML - panel['content'] = render_text(panel.get('content', ''), ctx) - - if javascript_template: - # Render javascript template to HTML - panel['javascript'] = render_template(self, javascript_template, ctx) - else: - # Render javascript string to HTML - panel['javascript'] = render_text(panel.get('javascript', ''), ctx) - - # Check for required keys - required_keys = ['title', 'content'] - - if any(key not in panel for key in required_keys): - logger.warning( - 'Custom panel for plugin %s is missing a required parameter', - __class__, - ) - continue - - # Add some information on this plugin - panel['plugin'] = self - panel['slug'] = self.slug - - # Add a 'key' for the panel, which is mostly guaranteed to be unique - panel['key'] = generateTestKey(self.slug + panel.get('title', 'panel')) - - panels.append(panel) - - return panels - - class SettingsContentMixin: """Mixin which allows integration of custom HTML content into a plugins settings page. diff --git a/src/backend/InvenTree/plugin/base/integration/test_mixins.py b/src/backend/InvenTree/plugin/base/integration/test_mixins.py index 00f3d437c0..43bd7b9d1f 100644 --- a/src/backend/InvenTree/plugin/base/integration/test_mixins.py +++ b/src/backend/InvenTree/plugin/base/integration/test_mixins.py @@ -10,7 +10,7 @@ from error_report.models import Error from InvenTree.unit_test import InvenTreeTestCase from plugin import InvenTreePlugin -from plugin.base.integration.mixins import PanelMixin +from plugin.base.integration.PanelMixin import PanelMixin from plugin.helpers import MixinNotImplementedError from plugin.mixins import ( APICallMixin, diff --git a/src/backend/InvenTree/plugin/mixins/__init__.py b/src/backend/InvenTree/plugin/mixins/__init__.py index 0e39462ad6..369711d8a5 100644 --- a/src/backend/InvenTree/plugin/mixins/__init__.py +++ b/src/backend/InvenTree/plugin/mixins/__init__.py @@ -7,11 +7,8 @@ from plugin.base.event.mixins import EventMixin from plugin.base.integration.APICallMixin import APICallMixin from plugin.base.integration.AppMixin import AppMixin from plugin.base.integration.CurrencyExchangeMixin import CurrencyExchangeMixin -from plugin.base.integration.mixins import ( - NavigationMixin, - PanelMixin, - SettingsContentMixin, -) +from plugin.base.integration.mixins import NavigationMixin, SettingsContentMixin +from plugin.base.integration.PanelMixin import PanelMixin from plugin.base.integration.ReportMixin import ReportMixin from plugin.base.integration.ScheduleMixin import ScheduleMixin from plugin.base.integration.SettingsMixin import SettingsMixin