mirror of
https://github.com/inventree/InvenTree.git
synced 2026-10-10 04:06:14 +00:00
feat: add plugin support for custom page integration (#12900)
* Added plugin UI route functionality. * Updated Sample UI plugin with route example. * Added UI route tests. * Plugin name added to UI Route URL. * Updated docs. * Docs fix. * Updated sample_route text. * Update src/backend/InvenTree/plugin/base/ui/mixins.py Co-authored-by: Matthias Mair <code@mjmair.com> * Fixed nav dynamic link highlighting. * Added changelog entry. --------- Co-authored-by: Matthias Mair <code@mjmair.com> Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
11 files changed
+199
-5
No files matched your search
@@ -22,6 +22,7 @@ FeatureType = Literal[
|
||||
'template_preview', # Custom template preview
|
||||
'navigation', # Custom navigation items
|
||||
'primary_action', # Custom primary action buttons
|
||||
'route', # Custom react router path
|
||||
]
|
||||
|
||||
|
||||
@@ -108,6 +109,7 @@ class UserInterfaceMixin:
|
||||
'template_editor': self.get_ui_template_editors,
|
||||
'template_preview': self.get_ui_template_previews,
|
||||
'primary_action': self.get_ui_primary_actions,
|
||||
'route': self.get_ui_routes,
|
||||
}
|
||||
|
||||
if feature_type in feature_map:
|
||||
@@ -231,3 +233,7 @@ class UserInterfaceMixin:
|
||||
|
||||
# Default implementation returns an empty list
|
||||
return []
|
||||
|
||||
def get_ui_routes(self, request, context, **kwargs):
|
||||
"""Return a list of custom React routes."""
|
||||
return []
|
||||
@@ -264,3 +264,42 @@ class UserInterfaceMixinTests(InvenTreeAPITestCase):
|
||||
self.assertEqual(response.data[0]['plugin_name'], 'sampleui')
|
||||
self.assertEqual(response.data[0]['key'], 'sample-primary-action')
|
||||
self.assertEqual(response.data[0]['title'], 'Sample Primary Action')
|
||||
|
||||
def test_ui_routes(self):
|
||||
"""Test that the sample UI plugin provides custom routes."""
|
||||
response = self.get(
|
||||
reverse('api-plugin-ui-feature-list', kwargs={'feature': 'route'})
|
||||
)
|
||||
|
||||
self.assertEqual(2, len(response.data))
|
||||
|
||||
routes = {route['key']: route for route in response.data}
|
||||
|
||||
self.assertIn('sample-route', routes)
|
||||
self.assertIn('sample-route-arg', routes)
|
||||
|
||||
self.assertEqual(
|
||||
routes['sample-route'],
|
||||
{
|
||||
'plugin_name': 'sampleui',
|
||||
'feature_type': 'route',
|
||||
'key': 'sample-route',
|
||||
'title': 'Sample Route',
|
||||
'options': {'path': 'test'},
|
||||
'context': None,
|
||||
'source': '/static/plugins/sampleui/sample_route.js:getBasicPage',
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
routes['sample-route-arg'],
|
||||
{
|
||||
'plugin_name': 'sampleui',
|
||||
'feature_type': 'route',
|
||||
'key': 'sample-route-arg',
|
||||
'title': 'Sample Route Arg',
|
||||
'options': {'path': 'test/:arg1'},
|
||||
'context': None,
|
||||
'source': '/static/plugins/sampleui/sample_route.js:getArgPage',
|
||||
},
|
||||
)
|
||||
@@ -222,7 +222,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
'key': 'sample-nav-item',
|
||||
'title': 'Sample Nav Item',
|
||||
'icon': 'ti:menu',
|
||||
'options': {'url': '/sample/page/'},
|
||||
'options': {'url': 'plugin/sampleui/test'},
|
||||
}
|
||||
]
|
||||
|
||||
@@ -237,6 +237,25 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
}
|
||||
]
|
||||
|
||||
def get_ui_routes(self, request, context, **kwargs):
|
||||
"""Return a list of custom UI routes."""
|
||||
return [
|
||||
{
|
||||
# Adds a simple route to /web/plugin/sampleui/test
|
||||
'key': 'sample-route',
|
||||
'title': 'Sample Route',
|
||||
'source': self.plugin_static_file('sample_route.js:getBasicPage'),
|
||||
'options': {'path': 'test'},
|
||||
},
|
||||
{
|
||||
# Adds a simple route, with an argument to /web/plugin/sampleui/test/:arg1 (e.g., /web/plugin/sampleui/test/1)
|
||||
'key': 'sample-route-arg',
|
||||
'title': 'Sample Route Arg',
|
||||
'source': self.plugin_static_file('sample_route.js:getArgPage'),
|
||||
'options': {'path': 'test/:arg1'},
|
||||
},
|
||||
]
|
||||
|
||||
def get_admin_context(self) -> dict:
|
||||
"""Return custom context data which can be rendered in the admin panel."""
|
||||
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}
|
||||
@@ -0,0 +1,20 @@
|
||||
export function getBasicPage(_context) {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ style: { padding: 24 } },
|
||||
React.createElement('h1', null, 'Sample Plugin Route'),
|
||||
React.createElement('p', null, 'This page has been dynamically rendered by the plugin system.')
|
||||
);
|
||||
}
|
||||
|
||||
export function getArgPage(_context) {
|
||||
const arg1 = window.location.pathname.split('/').pop();
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ style: { padding: 24 } },
|
||||
React.createElement('h1', null, 'Sample Plugin Route'),
|
||||
React.createElement('p', null, 'This page has been dynamically rendered by the plugin system.'),
|
||||
React.createElement('p', null, `Arg 1: ${arg1}.`)
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user