diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index 183e491580..629ee1f31a 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -38,6 +38,8 @@ from part.models import PartCategory
from common.models import InvenTreeSetting, ColorTheme
from users.models import check_user_role, RuleSet
+from plugin.registry import registry
+
from .forms import DeleteForm, EditUserForm, SetPasswordForm
from .forms import SettingCategorySelectForm
from .helpers import str2bool
@@ -56,6 +58,38 @@ def auth_request(request):
return HttpResponse(status=403)
+class InvenTreePluginMixin:
+ """
+ Custom view mixin which adds context data to the view,
+ based on loaded plugins.
+
+ This allows rendered pages to be augmented by loaded plugins.
+
+ """
+
+ def get_plugin_panels(self):
+ """
+ Return a list of extra 'plugin panels' associated with this view
+ """
+
+ panels = []
+
+ for plug in registry.with_mixin('panel'):
+
+ panels += plug.render_panels(self, self.request)
+
+ return panels
+
+ def get_context_data(self, **kwargs):
+
+ ctx = super().get_context_data(**kwargs)
+
+ if settings.PLUGINS_ENABLED:
+ ctx['plugin_panels'] = self.get_plugin_panels()
+
+ return ctx
+
+
class InvenTreeRoleMixin(PermissionRequiredMixin):
"""
Permission class based on user roles, not user 'permissions'.
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index aa3ad4963a..cd8404be5b 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -397,9 +397,11 @@
-
+
+{% include "panel/plugin_panels.html" %}
+
{% endblock %}
{% block js_load %}
@@ -1083,4 +1085,6 @@
}
});
+ {% include "panel/plugin_javascript.html" %}
+
{% endblock %}
diff --git a/InvenTree/part/templates/part/part_sidebar.html b/InvenTree/part/templates/part/part_sidebar.html
index e8763fb973..18890b82af 100644
--- a/InvenTree/part/templates/part/part_sidebar.html
+++ b/InvenTree/part/templates/part/part_sidebar.html
@@ -58,3 +58,5 @@
{% include "sidebar_item.html" with label="part-attachments" text=text icon="fa-paperclip" %}
{% trans "Notes" as text %}
{% include "sidebar_item.html" with label="part-notes" text=text icon="fa-clipboard" %}
+
+{% include "panel/plugin_menu_items.html" %}
\ No newline at end of file
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index efaf83ae95..cb9725f94e 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -49,7 +49,7 @@ from order.models import PurchaseOrderLineItem
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.views import QRCodeView
-from InvenTree.views import InvenTreeRoleMixin
+from InvenTree.views import InvenTreeRoleMixin, InvenTreePluginMixin
from InvenTree.helpers import str2bool
@@ -365,7 +365,7 @@ class PartImportAjax(FileManagementAjaxView, PartImport):
return PartImport.validate(self, self.steps.current, form, **kwargs)
-class PartDetail(InvenTreeRoleMixin, DetailView):
+class PartDetail(InvenTreeRoleMixin, InvenTreePluginMixin, DetailView):
""" Detail view for Part object
"""
diff --git a/InvenTree/plugin/samples/integration/custom_panel_sample.py b/InvenTree/plugin/samples/integration/custom_panel_sample.py
index 5cca44f524..3b999cce27 100644
--- a/InvenTree/plugin/samples/integration/custom_panel_sample.py
+++ b/InvenTree/plugin/samples/integration/custom_panel_sample.py
@@ -46,9 +46,6 @@ class CustomPanelSample(PanelMixin, IntegrationPluginBase):
# 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()
@@ -58,11 +55,7 @@ class CustomPanelSample(PanelMixin, IntegrationPluginBase):
'icon': 'fa-user',
'content': 'I have no children!
'
})
- else:
- print("abcdefgh")
-
except:
- print("error could not get object!")
pass
return panels
diff --git a/InvenTree/templates/panel/plugin_javascript.html b/InvenTree/templates/panel/plugin_javascript.html
new file mode 100644
index 0000000000..bf8b7fea34
--- /dev/null
+++ b/InvenTree/templates/panel/plugin_javascript.html
@@ -0,0 +1,10 @@
+{% if plugin_panels %}
+// Run custom javascript when plugin panels are loaded
+{% for panel in plugin_panels %}
+{% if panel.javascript %}
+onPanelLoad('{{ panel.key }}', function() {
+{{ panel.javascript | safe }}
+});
+{% endif %}
+{% endfor %}
+{% endif %}
\ No newline at end of file
diff --git a/InvenTree/templates/panel/plugin_menu_items.html b/InvenTree/templates/panel/plugin_menu_items.html
new file mode 100644
index 0000000000..2c084a021e
--- /dev/null
+++ b/InvenTree/templates/panel/plugin_menu_items.html
@@ -0,0 +1,3 @@
+{% for panel in plugin_panels %}
+{% include "sidebar_item.html" with label=panel.key text=panel.title icon=panel.icon %}
+{% endfor %}
\ No newline at end of file
diff --git a/InvenTree/templates/panel/plugin_panels.html b/InvenTree/templates/panel/plugin_panels.html
new file mode 100644
index 0000000000..ddbdbeee45
--- /dev/null
+++ b/InvenTree/templates/panel/plugin_panels.html
@@ -0,0 +1,18 @@
+{% for panel in plugin_panels %}
+
+
+
+
+
{{ panel.title }}
+ {% include "spacer.html" %}
+
+
+
+
+
+
+ {{ panel.content | safe }}
+
+
+
+{% endfor %}
\ No newline at end of file