diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 95e88ca8c..a5fdbea10 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -6,15 +6,16 @@ from django.apps import AppConfig from django.core.exceptions import ImproperlyConfigured from django.template.loader import get_template -from extras.registry import registry -from utilities.choices import ButtonColorChoices - from extras.plugins.utils import import_object +from extras.registry import registry +from netbox.navigation import MenuGroup +from utilities.choices import ButtonColorChoices # Initialize plugin registry registry['plugins'] = { 'graphql_schemas': [], + 'menus': [], 'menu_items': {}, 'preferences': {}, 'template_extensions': collections.defaultdict(list), @@ -57,8 +58,8 @@ class PluginConfig(AppConfig): # Default integration paths. Plugin authors can override these to customize the paths to # integrated components. graphql_schema = 'graphql.schema' + menu = 'navigation.menu' menu_items = 'navigation.menu_items' - menu_header = 'navigation.menu_heading' template_extensions = 'template_content.template_extensions' user_preferences = 'preferences.preferences' @@ -70,15 +71,11 @@ class PluginConfig(AppConfig): if template_extensions is not None: register_template_extensions(template_extensions) - # Register navigation menu items (if defined) - try: - menu_header = import_object(f"{self.__module__}.{self.menu_header}") - except AttributeError: - menu_header = None - - menu_items = import_object(f"{self.__module__}.{self.menu_items}") - if menu_items is not None: - register_menu_items(self.verbose_name, menu_header, menu_items) + # Register navigation menu or menu items (if defined) + if menu := import_object(f"{self.__module__}.{self.menu}"): + register_menu(menu) + if menu_items := import_object(f"{self.__module__}.{self.menu_items}"): + register_menu_items(self.verbose_name, menu_items) # Register GraphQL schema (if defined) graphql_schema = import_object(f"{self.__module__}.{self.graphql_schema}") @@ -206,6 +203,22 @@ def register_template_extensions(class_list): # Navigation menu links # +class PluginMenu: + icon = 'mdi-puzzle' + + def __init__(self, label, groups, icon=None): + self.label = label + self.groups = [ + MenuGroup(label, items) for label, items in groups + ] + if icon is not None: + self.icon = icon + + @property + def icon_class(self): + return f'mdi {self.icon}' + + class PluginMenuItem: """ This class represents a navigation menu item. This constitutes primary link and its text, but also allows for @@ -252,7 +265,13 @@ class PluginMenuButton: self.color = color -def register_menu_items(section_name, menu_header, class_list): +def register_menu(menu): + if not isinstance(menu, PluginMenu): + raise TypeError(f"{menu} must be an instance of extras.plugins.PluginMenu") + registry['plugins']['menus'].append(menu) + + +def register_menu_items(section_name, class_list): """ Register a list of PluginMenuItem instances for a given menu section (e.g. plugin name) """ @@ -264,9 +283,7 @@ def register_menu_items(section_name, menu_header, class_list): if not isinstance(button, PluginMenuButton): raise TypeError(f"{button} must be an instance of extras.plugins.PluginMenuButton") - registry['plugins']['menu_items'][section_name] = {} - registry['plugins']['menu_items'][section_name]['header'] = menu_header - registry['plugins']['menu_items'][section_name]['items'] = class_list + registry['plugins']['menu_items'][section_name] = class_list # diff --git a/netbox/extras/tests/test_plugins.py b/netbox/extras/tests/test_plugins.py index 733ae3a39..299cab9ef 100644 --- a/netbox/extras/tests/test_plugins.py +++ b/netbox/extras/tests/test_plugins.py @@ -63,7 +63,7 @@ class PluginTest(TestCase): Check that plugin MenuItems and MenuButtons are registered. """ self.assertIn('Dummy plugin', registry['plugins']['menu_items']) - menu_items = registry['plugins']['menu_items']['Dummy plugin']['items'] + menu_items = registry['plugins']['menu_items']['Dummy plugin'] self.assertEqual(len(menu_items), 2) self.assertEqual(len(menu_items[0].buttons), 2) diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 9eb762c23..400a7bf5a 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -1,5 +1,5 @@ from extras.registry import registry -from .navigation import * +from . import * # @@ -324,31 +324,19 @@ MENUS = [ # Add plugin menus # +for menu in registry['plugins']['menus']: + MENUS.append(menu) + if registry['plugins']['menu_items']: - plugin_menu_groups = [] - for plugin_name, data in registry['plugins']['menu_items'].items(): - if data['header']: - menu_groups = [MenuGroup(label=plugin_name, items=data["items"])] - icon = data["header"]["icon"] - MENUS.append(Menu( - label=data["header"]["title"], - icon_class=f"mdi {icon}", - groups=menu_groups - )) - else: - plugin_menu_groups.append( - MenuGroup( - label=plugin_name, - items=data["items"] - ) - ) - - if plugin_menu_groups: - PLUGIN_MENU = Menu( - label="Plugins", - icon_class="mdi mdi-puzzle", - groups=plugin_menu_groups - ) - - MENUS.append(PLUGIN_MENU) + # Build the default plugins menu + groups = [ + MenuGroup(label=label, items=items) + for label, items in registry['plugins']['menu_items'].items() + ] + plugins_menu = Menu( + label="Plugins", + icon_class="mdi mdi-puzzle", + groups=groups + ) + MENUS.append(plugins_menu)