Make url a property on MenuItem/PluginMenuItem etc, overridable via a setter
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled

This commit is contained in:
Brian Tiemann 2025-07-13 20:19:46 -04:00
parent 7338898ccb
commit 0c95ac6b1a
3 changed files with 56 additions and 7 deletions

View File

@ -1,6 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Sequence, Optional from typing import Sequence, Optional
from django.urls import reverse
__all__ = ( __all__ = (
'get_model_item', 'get_model_item',
@ -22,20 +24,46 @@ class MenuItemButton:
link: str link: str
title: str title: str
icon_class: str icon_class: str
_url: Optional[str] = None
permissions: Optional[Sequence[str]] = () permissions: Optional[Sequence[str]] = ()
color: Optional[str] = None 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 @dataclass
class MenuItem: class MenuItem:
link: str link: str
link_text: str link_text: str
_url: Optional[str] = None
permissions: Optional[Sequence[str]] = () permissions: Optional[Sequence[str]] = ()
auth_required: Optional[bool] = False auth_required: Optional[bool] = False
staff_only: Optional[bool] = False staff_only: Optional[bool] = False
buttons: Optional[Sequence[MenuItemButton]] = () 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 @dataclass
class MenuGroup: class MenuGroup:

View File

@ -1,3 +1,4 @@
from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext as _ 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. 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 %}. 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. Buttons are each specified as a list of PluginMenuButton instances.
""" """
permissions = [] permissions = []
buttons = [] buttons = []
_url = None
def __init__( 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 = link
self.link_text = link_text self.link_text = link_text
self.url = url
self.auth_required = auth_required self.auth_required = auth_required
self.staff_only = staff_only self.staff_only = staff_only
if link:
self._url = reverse(link)
if permissions is not None: if permissions is not None:
if type(permissions) not in (list, tuple): if type(permissions) not in (list, tuple):
raise TypeError(_("Permissions must be passed as a tuple or list.")) 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.")) raise TypeError(_("Buttons must be passed as a tuple or list."))
self.buttons = buttons self.buttons = buttons
@property
def url(self):
return self._url
@url.setter
def url(self, value):
self._url = value
class PluginMenuButton: class PluginMenuButton:
""" """
@ -64,12 +75,14 @@ class PluginMenuButton:
""" """
color = ButtonColorChoices.DEFAULT color = ButtonColorChoices.DEFAULT
permissions = [] 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.link = link
self.title = title self.title = title
self.icon_class = icon_class self.icon_class = icon_class
self.url = url if link:
self._url = reverse(link)
if permissions is not None: if permissions is not None:
if type(permissions) not in (list, tuple): if type(permissions) not in (list, tuple):
raise TypeError(_("Permissions must be passed as a tuple or list.")) raise TypeError(_("Permissions must be passed as a tuple or list."))
@ -78,3 +91,11 @@ class PluginMenuButton:
if color not in ButtonColorChoices.values(): if color not in ButtonColorChoices.values():
raise ValueError(_("Button color must be a choice within ButtonColorChoices.")) raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
self.color = color self.color = color
@property
def url(self):
return self._url
@url.setter
def url(self, value):
self._url = value

View File

@ -41,11 +41,11 @@
</div> </div>
{% for item, buttons in items %} {% for item, buttons in items %}
<div class="dropdown-item d-flex justify-content-between ps-3 py-0"> <div class="dropdown-item d-flex justify-content-between ps-3 py-0">
<a href="{% if item.url %}{{ item.url }}{% else %}{% url item.link %}{% endif %}" 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 %} {% if buttons %}
<div class="btn-group ms-1"> <div class="btn-group ms-1">
{% for button in buttons %} {% for button in buttons %}
<a href="{% if button.url %}{{ button.url }}{% else %}{% url button.link %}{% endif %}" 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> <i class="{{ button.icon_class }}"></i>
</a> </a>
{% endfor %} {% endfor %}