1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Enable plugins to create root-level navigation menus

This commit is contained in:
jeremystretch
2022-09-28 16:08:03 -04:00
parent 00d2dcda68
commit db90b084cf
3 changed files with 50 additions and 45 deletions

View File

@ -6,15 +6,16 @@ from django.apps import AppConfig
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.template.loader import get_template 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.plugins.utils import import_object
from extras.registry import registry
from netbox.navigation import MenuGroup
from utilities.choices import ButtonColorChoices
# Initialize plugin registry # Initialize plugin registry
registry['plugins'] = { registry['plugins'] = {
'graphql_schemas': [], 'graphql_schemas': [],
'menus': [],
'menu_items': {}, 'menu_items': {},
'preferences': {}, 'preferences': {},
'template_extensions': collections.defaultdict(list), '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 # Default integration paths. Plugin authors can override these to customize the paths to
# integrated components. # integrated components.
graphql_schema = 'graphql.schema' graphql_schema = 'graphql.schema'
menu = 'navigation.menu'
menu_items = 'navigation.menu_items' menu_items = 'navigation.menu_items'
menu_header = 'navigation.menu_heading'
template_extensions = 'template_content.template_extensions' template_extensions = 'template_content.template_extensions'
user_preferences = 'preferences.preferences' user_preferences = 'preferences.preferences'
@ -70,15 +71,11 @@ class PluginConfig(AppConfig):
if template_extensions is not None: if template_extensions is not None:
register_template_extensions(template_extensions) register_template_extensions(template_extensions)
# Register navigation menu items (if defined) # Register navigation menu or menu items (if defined)
try: if menu := import_object(f"{self.__module__}.{self.menu}"):
menu_header = import_object(f"{self.__module__}.{self.menu_header}") register_menu(menu)
except AttributeError: if menu_items := import_object(f"{self.__module__}.{self.menu_items}"):
menu_header = None register_menu_items(self.verbose_name, menu_items)
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 GraphQL schema (if defined) # Register GraphQL schema (if defined)
graphql_schema = import_object(f"{self.__module__}.{self.graphql_schema}") graphql_schema = import_object(f"{self.__module__}.{self.graphql_schema}")
@ -206,6 +203,22 @@ def register_template_extensions(class_list):
# Navigation menu links # 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: class PluginMenuItem:
""" """
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for 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 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) 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): if not isinstance(button, PluginMenuButton):
raise TypeError(f"{button} must be an instance of extras.plugins.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] = class_list
registry['plugins']['menu_items'][section_name]['header'] = menu_header
registry['plugins']['menu_items'][section_name]['items'] = class_list
# #

View File

@ -63,7 +63,7 @@ class PluginTest(TestCase):
Check that plugin MenuItems and MenuButtons are registered. Check that plugin MenuItems and MenuButtons are registered.
""" """
self.assertIn('Dummy plugin', registry['plugins']['menu_items']) 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), 2)
self.assertEqual(len(menu_items[0].buttons), 2) self.assertEqual(len(menu_items[0].buttons), 2)

View File

@ -1,5 +1,5 @@
from extras.registry import registry from extras.registry import registry
from .navigation import * from . import *
# #
@ -324,31 +324,19 @@ MENUS = [
# Add plugin menus # Add plugin menus
# #
for menu in registry['plugins']['menus']:
MENUS.append(menu)
if registry['plugins']['menu_items']: if registry['plugins']['menu_items']:
plugin_menu_groups = []
for plugin_name, data in registry['plugins']['menu_items'].items(): # Build the default plugins menu
if data['header']: groups = [
menu_groups = [MenuGroup(label=plugin_name, items=data["items"])] MenuGroup(label=label, items=items)
icon = data["header"]["icon"] for label, items in registry['plugins']['menu_items'].items()
MENUS.append(Menu( ]
label=data["header"]["title"], plugins_menu = Menu(
icon_class=f"mdi {icon}", label="Plugins",
groups=menu_groups icon_class="mdi mdi-puzzle",
)) groups=groups
else: )
plugin_menu_groups.append( MENUS.append(plugins_menu)
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)