diff --git a/netbox/netbox/navigation/__init__.py b/netbox/netbox/navigation/__init__.py index 75ca8f440..9b2962e50 100644 --- a/netbox/netbox/navigation/__init__.py +++ b/netbox/netbox/navigation/__init__.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from typing import Sequence, Optional +from django.urls import reverse + __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(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(self.link) + + @property + def url(self): + return self._url + + @url.setter + def url(self, value): + self._url = value + @dataclass class MenuGroup: diff --git a/netbox/netbox/plugins/navigation.py b/netbox/netbox/plugins/navigation.py index 35c9fa062..4dca117c8 100644 --- a/netbox/netbox/plugins/navigation.py +++ b/netbox/netbox/plugins/navigation.py @@ -1,3 +1,4 @@ +from django.urls import reverse from django.utils.text import slugify from django.utils.translation import gettext as _ @@ -33,20 +34,22 @@ class PluginMenuItem: specifying additional link buttons that appear to the right of the item in the van menu. Links are specified as Django reverse URL strings suitable for rendering via {% url item.link %}. - Alternatively, a pre-generated url can be specified which will be rendered literally. + 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, url=None, auth_required=False, staff_only=False, permissions=None, buttons=None + self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None ): self.link = link self.link_text = link_text - self.url = url self.auth_required = auth_required self.staff_only = staff_only + if link: + self._url = reverse(link) if permissions is not None: if type(permissions) not in (list, tuple): raise TypeError(_("Permissions must be passed as a tuple or list.")) @@ -56,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: """ @@ -64,12 +75,14 @@ class PluginMenuButton: """ color = ButtonColorChoices.DEFAULT permissions = [] + _url = None - def __init__(self, link, title, icon_class, url=None, color=None, permissions=None): + def __init__(self, link, title, icon_class, color=None, permissions=None): self.link = link self.title = title self.icon_class = icon_class - self.url = url + if link: + self._url = reverse(link) if permissions is not None: if type(permissions) not in (list, tuple): raise TypeError(_("Permissions must be passed as a tuple or list.")) @@ -78,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 diff --git a/netbox/utilities/templates/navigation/menu.html b/netbox/utilities/templates/navigation/menu.html index 97861857e..8becc568b 100644 --- a/netbox/utilities/templates/navigation/menu.html +++ b/netbox/utilities/templates/navigation/menu.html @@ -41,11 +41,11 @@ {% for item, buttons in items %}