Переглянути джерело

Closes: #19793 - Nav menu link customization (#19794)

* Support menu items that are callables

* Fix quote on add button

* Clarify docstring to differentiate link and url

* Back out support for callables but keep alternate prerendered url param

* Make url a property on MenuItem/PluginMenuItem etc, overridable via a setter

* Use reverse_lazy instead of reverse

* Use reverse_lazy instead of reverse
bctiemann 7 місяців тому
батько
коміт
f5d32b1bf1

+ 28 - 0
netbox/netbox/navigation/__init__.py

@@ -1,6 +1,8 @@
 from dataclasses import dataclass
 from typing import Sequence, Optional
 
+from django.urls import reverse_lazy
+
 
 __all__ = (
     'get_model_item',
@@ -22,20 +24,46 @@ class MenuItemButton:
     link: str
     title: str
     icon_class: str
+    _url: Optional[str] = None
     permissions: Optional[Sequence[str]] = ()
     color: Optional[str] = None
 
+    def __post_init__(self):
+        if self.link:
+            self._url = reverse_lazy(self.link)
+
+    @property
+    def url(self):
+        return self._url
+
+    @url.setter
+    def url(self, value):
+        self._url = value
+
 
 @dataclass
 class MenuItem:
 
     link: str
     link_text: str
+    _url: Optional[str] = None
     permissions: Optional[Sequence[str]] = ()
     auth_required: Optional[bool] = False
     staff_only: Optional[bool] = False
     buttons: Optional[Sequence[MenuItemButton]] = ()
 
+    def __post_init__(self):
+        if self.link:
+            self._url = reverse_lazy(self.link)
+
+    @property
+    def url(self):
+        return self._url
+
+    @url.setter
+    def url(self, value):
+        self._url = value
+
 
 @dataclass
 class MenuGroup:

+ 28 - 2
netbox/netbox/plugins/navigation.py

@@ -1,3 +1,4 @@
+from django.urls import reverse_lazy
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
 
@@ -32,17 +33,23 @@ class PluginMenuItem:
     This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
     specifying additional link buttons that appear to the right of the item in the van menu.
 
-    Links are specified as Django reverse URL strings.
+    Links are specified as Django reverse URL strings suitable for rendering via {% url item.link %}.
+    Alternatively, a pre-generated url can be set on the object which will be rendered literally.
     Buttons are each specified as a list of PluginMenuButton instances.
     """
     permissions = []
     buttons = []
+    _url = None
 
-    def __init__(self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None):
+    def __init__(
+        self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None
+    ):
         self.link = link
         self.link_text = link_text
         self.auth_required = auth_required
         self.staff_only = staff_only
+        if link:
+            self._url = reverse_lazy(link)
         if permissions is not None:
             if type(permissions) not in (list, tuple):
                 raise TypeError(_("Permissions must be passed as a tuple or list."))
@@ -52,6 +59,14 @@ class PluginMenuItem:
                 raise TypeError(_("Buttons must be passed as a tuple or list."))
             self.buttons = buttons
 
+    @property
+    def url(self):
+        return self._url
+
+    @url.setter
+    def url(self, value):
+        self._url = value
+
 
 class PluginMenuButton:
     """
@@ -60,11 +75,14 @@ class PluginMenuButton:
     """
     color = ButtonColorChoices.DEFAULT
     permissions = []
+    _url = None
 
     def __init__(self, link, title, icon_class, color=None, permissions=None):
         self.link = link
         self.title = title
         self.icon_class = icon_class
+        if link:
+            self._url = reverse_lazy(link)
         if permissions is not None:
             if type(permissions) not in (list, tuple):
                 raise TypeError(_("Permissions must be passed as a tuple or list."))
@@ -73,3 +91,11 @@ class PluginMenuButton:
             if color not in ButtonColorChoices.values():
                 raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
             self.color = color
+
+    @property
+    def url(self):
+        return self._url
+
+    @url.setter
+    def url(self, value):
+        self._url = value

+ 2 - 2
netbox/utilities/templates/navigation/menu.html

@@ -41,11 +41,11 @@
               </div>
               {% for item, buttons in items %}
                 <div class="dropdown-item d-flex justify-content-between ps-3 py-0">
-                  <a href="{% url item.link %}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
+                  <a href="{{ item.url }}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
                   {% if buttons %}
                     <div class="btn-group ms-1">
                       {% for button in buttons %}
-                        <a href="{% url button.link %}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
+                        <a href="{{ button.url }}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
                           <i class="{{ button.icon_class }}"></i>
                         </a>
                       {% endfor %}