Jelajahi Sumber

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>
Aditya Sharma 2 minggu lalu
induk
melakukan
aa69e96818

+ 44 - 33
netbox/netbox/navigation/menu.py

@@ -1,3 +1,5 @@
+from functools import cache
+
 from django.utils.translation import gettext_lazy as _
 
 from netbox.registry import registry
@@ -501,40 +503,49 @@ ADMIN_MENU = Menu(
     ),
 )
 
-MENUS = [
-    ORGANIZATION_MENU,
-    RACKS_MENU,
-    DEVICES_MENU,
-    CONNECTIONS_MENU,
-    WIRELESS_MENU,
-    IPAM_MENU,
-    VPN_MENU,
-    VIRTUALIZATION_MENU,
-    CIRCUITS_MENU,
-    POWER_MENU,
-    PROVISIONING_MENU,
-    CUSTOMIZATION_MENU,
-    OPERATIONS_MENU,
-]
 
-# Add top-level plugin menus
-for menu in registry['plugins']['menus']:
-    MENUS.append(menu)
+@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,
+        RACKS_MENU,
+        DEVICES_MENU,
+        CONNECTIONS_MENU,
+        WIRELESS_MENU,
+        IPAM_MENU,
+        VPN_MENU,
+        VIRTUALIZATION_MENU,
+        CIRCUITS_MENU,
+        POWER_MENU,
+        PROVISIONING_MENU,
+        CUSTOMIZATION_MENU,
+        OPERATIONS_MENU,
+    ]
+
+    # Add top-level plugin menus
+    for menu in registry['plugins']['menus']:
+        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
+        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)
 
-    # 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)
+    # Add the admin menu last
+    menus.append(ADMIN_MENU)
 
-# Add the admin menu last
-MENUS.append(ADMIN_MENU)
+    return menus

+ 2 - 2
netbox/utilities/templatetags/navigation.py

@@ -1,6 +1,6 @@
 from django import template
 
-from netbox.navigation.menu import MENUS
+from netbox.navigation.menu import get_menus
 
 __all__ = (
     'nav',
@@ -19,7 +19,7 @@ def nav(context):
     nav_items = []
 
     # Construct the navigation menu based upon the current user's permissions
-    for menu in MENUS:
+    for menu in get_menus():
         groups = []
         for group in menu.groups:
             items = []