Refactor PanelMixin class into its own file (#7181)

* Refactor PanelMixin class into its own file

* Fix unit test
This commit is contained in:
Oliver 2024-05-08 14:22:24 +10:00 committed by GitHub
parent c72dc2b8e4
commit d16ee6755e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 153 additions and 146 deletions

View File

@ -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': '<b>Hello world</b>',
}
"""
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

View File

@ -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': '<b>Hello world</b>',
}
"""
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.

View File

@ -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,

View File

@ -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