Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions netbox/netbox/navigation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from dataclasses import dataclass
from typing import Sequence, Optional

from django.urls import reverse_lazy


__all__ = (
'get_model_item',
Expand All @@ -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:
Expand Down
30 changes: 28 additions & 2 deletions netbox/netbox/plugins/navigation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.urls import reverse_lazy
from django.utils.text import slugify
from django.utils.translation import gettext as _

Expand Down Expand Up @@ -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."))
Expand All @@ -52,6 +59,14 @@ def __init__(self, link, link_text, auth_required=False, staff_only=False, permi
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:
"""
Expand All @@ -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."))
Expand All @@ -73,3 +91,11 @@ def __init__(self, link, title, icon_class, color=None, permissions=None):
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
4 changes: 2 additions & 2 deletions netbox/utilities/templates/navigation/menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down