mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Adds a new "Panel" mixin which can render custom panels on given pages
- Adds item to sidebar menu - Adds panel content - Runs custom javascript when the page is loaded
This commit is contained in:
		@@ -9,6 +9,8 @@ import requests
 | 
			
		||||
from django.urls import include, re_path
 | 
			
		||||
from django.db.utils import OperationalError, ProgrammingError
 | 
			
		||||
 | 
			
		||||
import InvenTree.helpers
 | 
			
		||||
 | 
			
		||||
from plugin.models import PluginConfig, PluginSetting
 | 
			
		||||
from plugin.urls import PLUGIN_BASE
 | 
			
		||||
from plugin.helpers import MixinImplementationError, MixinNotImplementedError
 | 
			
		||||
@@ -563,26 +565,72 @@ class PanelMixin:
 | 
			
		||||
 | 
			
		||||
    This method is provided with:
 | 
			
		||||
 | 
			
		||||
    - page: The name of the page e.g. 'part-detail'
 | 
			
		||||
    - instance: The model instance specific to the page
 | 
			
		||||
    - request: The request object responsible for the page load
 | 
			
		||||
 | 
			
		||||
    It must return a list of CustomPanel class instances (see below).
 | 
			
		||||
    - 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:
 | 
			
		||||
 | 
			
		||||
    class CustomPanel:
 | 
			
		||||
        ...
 | 
			
		||||
    - 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 loade, 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:
 | 
			
		||||
        MIXIN_NAME = 'Panel'
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.add_mixin('panel', True, __class__)
 | 
			
		||||
    
 | 
			
		||||
    def get_custom_panels(self, page, instance, request):
 | 
			
		||||
 | 
			
		||||
    def render_panels(self, view, request):
 | 
			
		||||
 | 
			
		||||
        panels = []
 | 
			
		||||
 | 
			
		||||
        for panel in self.get_custom_panels(view, request):
 | 
			
		||||
 | 
			
		||||
            if 'content_template' in panel:
 | 
			
		||||
                # TODO: Render the actual content
 | 
			
		||||
                ...
 | 
			
		||||
 | 
			
		||||
            if 'javascript_template' in panel:
 | 
			
		||||
                # TODO: Render the actual content
 | 
			
		||||
                ...
 | 
			
		||||
            
 | 
			
		||||
            # Check for required keys
 | 
			
		||||
            required_keys = ['title', 'content']
 | 
			
		||||
 | 
			
		||||
            if any([key not in panel for key in required_keys]):
 | 
			
		||||
                logger.warning(f"Custom panel for plugin '{__class__}' is missing a required key")
 | 
			
		||||
                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'] = InvenTree.helpers.generateTestKey(self.slug + panel.get('title', 'panel')) 
 | 
			
		||||
 | 
			
		||||
            panels.append(panel)
 | 
			
		||||
 | 
			
		||||
        return panels
 | 
			
		||||
 | 
			
		||||
    def get_custom_panels(self, view, request):
 | 
			
		||||
        """ This method *must* be implemented by the plugin class """
 | 
			
		||||
        raise NotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
 | 
			
		||||
 
 | 
			
		||||
@@ -307,14 +307,17 @@ class PluginsRegistry:
 | 
			
		||||
                # TODO check more stuff -> as of Nov 2021 there are not many checks in place
 | 
			
		||||
                # but we could enhance those to check signatures, run the plugin against a whitelist etc.
 | 
			
		||||
                logger.info(f'Loading integration plugin {plugin.PLUGIN_NAME}')
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    plugin = plugin()
 | 
			
		||||
                except Exception as error:
 | 
			
		||||
                    # log error and raise it -> disable plugin
 | 
			
		||||
                    handle_error(error, log_name='init')
 | 
			
		||||
 | 
			
		||||
                logger.info(f'Loaded integration plugin {plugin.slug}')
 | 
			
		||||
                logger.debug(f'Loaded integration plugin {plugin.PLUGIN_NAME}')
 | 
			
		||||
 | 
			
		||||
                plugin.is_package = was_packaged
 | 
			
		||||
 | 
			
		||||
                if plugin_db_setting:
 | 
			
		||||
                    plugin.pk = plugin_db_setting.pk
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@ Sample plugin which renders custom panels on certain pages
 | 
			
		||||
from plugin import IntegrationPluginBase
 | 
			
		||||
from plugin.mixins import PanelMixin
 | 
			
		||||
 | 
			
		||||
from part.views import PartDetail
 | 
			
		||||
from stock.views import StockLocationDetail
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomPanelSample(PanelMixin, IntegrationPluginBase):
 | 
			
		||||
    """
 | 
			
		||||
@@ -15,8 +18,51 @@ class CustomPanelSample(PanelMixin, IntegrationPluginBase):
 | 
			
		||||
    PLUGIN_SLUG = "panel"
 | 
			
		||||
    PLUGIN_TITLE = "Custom Panel Example"
 | 
			
		||||
 | 
			
		||||
    def get_custom_panels(self, page, instance, request):
 | 
			
		||||
    def get_custom_panels(self, view, request):
 | 
			
		||||
 | 
			
		||||
        print("get_custom_panels:")
 | 
			
		||||
        panels = [
 | 
			
		||||
            {
 | 
			
		||||
                # This 'hello world' panel will be displayed on any view which implements custom panels
 | 
			
		||||
                'title': 'Hello World',
 | 
			
		||||
                'icon': 'fas fa-boxes',
 | 
			
		||||
                'content': '<b>Hello world!</b>',
 | 
			
		||||
                'description': 'A simple panel which renders hello world',
 | 
			
		||||
                'javascript': 'alert("Hello world");',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                # This panel will not be displayed, as it is missing the 'content' key
 | 
			
		||||
                'title': 'No Content',
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        return []
 | 
			
		||||
        # This panel will *only* display on the PartDetail view
 | 
			
		||||
        if isinstance(view, PartDetail):
 | 
			
		||||
            panels.append({
 | 
			
		||||
                'title': 'Custom Part Panel',
 | 
			
		||||
                'icon': 'fas fa-shapes',
 | 
			
		||||
                'content': '<em>This content only appears on the PartDetail page, you know!</em>',
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        # This panel will *only* display on the StockLocation view,
 | 
			
		||||
        # and *only* if the StockLocation has *no* child locations
 | 
			
		||||
        if isinstance(view, StockLocationDetail):
 | 
			
		||||
            
 | 
			
		||||
            print("yep, stocklocation view!")
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                loc = view.get_object()
 | 
			
		||||
 | 
			
		||||
                if not loc.get_descendants(include_self=False).exists():
 | 
			
		||||
                    panels.append({
 | 
			
		||||
                        'title': 'Childless',
 | 
			
		||||
                        'icon': 'fa-user',
 | 
			
		||||
                        'content': '<h4>I have no children!</h4>'
 | 
			
		||||
                    })
 | 
			
		||||
                else:
 | 
			
		||||
                    print("abcdefgh")
 | 
			
		||||
 | 
			
		||||
            except:
 | 
			
		||||
                print("error could not get object!")
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        return panels
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user