Fixes #21173: Fix plugin menu registration order timing issue (#21248)

* Fixes #21173: Fix plugin menu registration order timing issue

- Converted static MENUS list to dynamic get_menus() function
- Ensures plugin menus are built at request time after all plugins complete ready()
- Fixes issue where only first few plugin menus appeared in navigation sidebar
- Updated navigation template tag to call get_menus() dynamically

* Fix ruff linting errors

- Add missing blank line before get_menus() function definition
- Remove trailing whitespace

* Add @cache decorator to get_menus() for performance optimization

Per reviewer feedback, the menu list is now cached since it doesn't change
without a Django restart. This eliminates redundant list building on each request.

---------

Co-authored-by: adionit7 <adionit7@users.noreply.github.com>
This commit is contained in:
Aditya Sharma
2026-01-27 00:04:57 +05:30
committed by GitHub
parent a9e50238eb
commit aa69e96818
2 changed files with 47 additions and 36 deletions
+22 -11
View File
@@ -1,3 +1,5 @@
from functools import cache
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from netbox.registry import registry from netbox.registry import registry
@@ -501,7 +503,15 @@ ADMIN_MENU = Menu(
), ),
) )
MENUS = [
@cache
def get_menus():
"""
Dynamically build and return the list of navigation menus.
This ensures plugin menus registered during app initialization are included.
The result is cached since menus don't change without a Django restart.
"""
menus = [
ORGANIZATION_MENU, ORGANIZATION_MENU,
RACKS_MENU, RACKS_MENU,
DEVICES_MENU, DEVICES_MENU,
@@ -515,15 +525,14 @@ MENUS = [
PROVISIONING_MENU, PROVISIONING_MENU,
CUSTOMIZATION_MENU, CUSTOMIZATION_MENU,
OPERATIONS_MENU, OPERATIONS_MENU,
] ]
# Add top-level plugin menus # Add top-level plugin menus
for menu in registry['plugins']['menus']: for menu in registry['plugins']['menus']:
MENUS.append(menu) menus.append(menu)
# Add the default "plugins" menu
if registry['plugins']['menu_items']:
# Add the default "plugins" menu
if registry['plugins']['menu_items']:
# Build the default plugins menu # Build the default plugins menu
groups = [ groups = [
MenuGroup(label=label, items=items) MenuGroup(label=label, items=items)
@@ -534,7 +543,9 @@ if registry['plugins']['menu_items']:
icon_class="mdi mdi-puzzle", icon_class="mdi mdi-puzzle",
groups=groups groups=groups
) )
MENUS.append(plugins_menu) menus.append(plugins_menu)
# Add the admin menu last # Add the admin menu last
MENUS.append(ADMIN_MENU) menus.append(ADMIN_MENU)
return menus
+2 -2
View File
@@ -1,6 +1,6 @@
from django import template from django import template
from netbox.navigation.menu import MENUS from netbox.navigation.menu import get_menus
__all__ = ( __all__ = (
'nav', 'nav',
@@ -19,7 +19,7 @@ def nav(context):
nav_items = [] nav_items = []
# Construct the navigation menu based upon the current user's permissions # Construct the navigation menu based upon the current user's permissions
for menu in MENUS: for menu in get_menus():
groups = [] groups = []
for group in menu.groups: for group in menu.groups:
items = [] items = []