Skip to content

Commit f5d32b1

Browse files
authored
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
1 parent f05897d commit f5d32b1

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

netbox/netbox/navigation/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from dataclasses import dataclass
22
from typing import Sequence, Optional
33

4+
from django.urls import reverse_lazy
5+
46

57
__all__ = (
68
'get_model_item',
@@ -22,20 +24,46 @@ class MenuItemButton:
2224
link: str
2325
title: str
2426
icon_class: str
27+
_url: Optional[str] = None
2528
permissions: Optional[Sequence[str]] = ()
2629
color: Optional[str] = None
2730

31+
def __post_init__(self):
32+
if self.link:
33+
self._url = reverse_lazy(self.link)
34+
35+
@property
36+
def url(self):
37+
return self._url
38+
39+
@url.setter
40+
def url(self, value):
41+
self._url = value
42+
2843

2944
@dataclass
3045
class MenuItem:
3146

3247
link: str
3348
link_text: str
49+
_url: Optional[str] = None
3450
permissions: Optional[Sequence[str]] = ()
3551
auth_required: Optional[bool] = False
3652
staff_only: Optional[bool] = False
3753
buttons: Optional[Sequence[MenuItemButton]] = ()
3854

55+
def __post_init__(self):
56+
if self.link:
57+
self._url = reverse_lazy(self.link)
58+
59+
@property
60+
def url(self):
61+
return self._url
62+
63+
@url.setter
64+
def url(self, value):
65+
self._url = value
66+
3967

4068
@dataclass
4169
class MenuGroup:

netbox/netbox/plugins/navigation.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.urls import reverse_lazy
12
from django.utils.text import slugify
23
from django.utils.translation import gettext as _
34

@@ -32,17 +33,23 @@ class PluginMenuItem:
3233
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
3334
specifying additional link buttons that appear to the right of the item in the van menu.
3435
35-
Links are specified as Django reverse URL strings.
36+
Links are specified as Django reverse URL strings suitable for rendering via {% url item.link %}.
37+
Alternatively, a pre-generated url can be set on the object which will be rendered literally.
3638
Buttons are each specified as a list of PluginMenuButton instances.
3739
"""
3840
permissions = []
3941
buttons = []
42+
_url = None
4043

41-
def __init__(self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None):
44+
def __init__(
45+
self, link, link_text, auth_required=False, staff_only=False, permissions=None, buttons=None
46+
):
4247
self.link = link
4348
self.link_text = link_text
4449
self.auth_required = auth_required
4550
self.staff_only = staff_only
51+
if link:
52+
self._url = reverse_lazy(link)
4653
if permissions is not None:
4754
if type(permissions) not in (list, tuple):
4855
raise TypeError(_("Permissions must be passed as a tuple or list."))
@@ -52,6 +59,14 @@ def __init__(self, link, link_text, auth_required=False, staff_only=False, permi
5259
raise TypeError(_("Buttons must be passed as a tuple or list."))
5360
self.buttons = buttons
5461

62+
@property
63+
def url(self):
64+
return self._url
65+
66+
@url.setter
67+
def url(self, value):
68+
self._url = value
69+
5570

5671
class PluginMenuButton:
5772
"""
@@ -60,11 +75,14 @@ class PluginMenuButton:
6075
"""
6176
color = ButtonColorChoices.DEFAULT
6277
permissions = []
78+
_url = None
6379

6480
def __init__(self, link, title, icon_class, color=None, permissions=None):
6581
self.link = link
6682
self.title = title
6783
self.icon_class = icon_class
84+
if link:
85+
self._url = reverse_lazy(link)
6886
if permissions is not None:
6987
if type(permissions) not in (list, tuple):
7088
raise TypeError(_("Permissions must be passed as a tuple or list."))
@@ -73,3 +91,11 @@ def __init__(self, link, title, icon_class, color=None, permissions=None):
7391
if color not in ButtonColorChoices.values():
7492
raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
7593
self.color = color
94+
95+
@property
96+
def url(self):
97+
return self._url
98+
99+
@url.setter
100+
def url(self, value):
101+
self._url = value

netbox/utilities/templates/navigation/menu.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@
4141
</div>
4242
{% for item, buttons in items %}
4343
<div class="dropdown-item d-flex justify-content-between ps-3 py-0">
44-
<a href="{% url item.link %}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
44+
<a href="{{ item.url }}" class="d-inline-flex flex-fill py-1">{{ item.link_text }}</a>
4545
{% if buttons %}
4646
<div class="btn-group ms-1">
4747
{% for button in buttons %}
48-
<a href="{% url button.link %}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
48+
<a href="{{ button.url }}" class="btn btn-sm btn-{{ button.color|default:"outline" }} lh-2 px-2" title="{{ button.title }}">
4949
<i class="{{ button.icon_class }}"></i>
5050
</a>
5151
{% endfor %}

0 commit comments

Comments
 (0)