#20048 action_url template tag

This commit is contained in:
Arthur 2025-08-07 16:59:57 -07:00
parent a47746d708
commit d3ef9bdf8d
2 changed files with 122 additions and 2 deletions

View File

@ -5,7 +5,7 @@
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item"><a href="{% url object.assigned_object|viewname:'journal' pk=object.assigned_object.pk %}">{{ object.assigned_object }}</a></li>
<li class="breadcrumb-item"><a href="{% action_url object.assigned_object 'journal' pk=object.assigned_object.pk %}">{{ object.assigned_object }}</a></li>
{% endblock %}
{% block content %}

View File

@ -4,10 +4,11 @@ from urllib.parse import quote
from django import template
from django.urls import NoReverseMatch, reverse
from django.utils.html import conditional_escape
from core.models import ObjectType
from utilities.forms import get_selected_values, TableConfigForm
from utilities.views import get_viewname
from utilities.views import get_viewname, get_action_url
from netbox.settings import DISK_BASE_UNIT, RAM_BASE_UNIT
__all__ = (
@ -63,6 +64,125 @@ def validated_viewname(model, action):
return None
@register.tag
def action_url(parser, token):
"""
Return an absolute URL matching the given model and action.
This is a way to define links that aren't tied to a particular URL
configuration::
{% action_url model "action_name" %}
or
{% action_url model "action_name" pk=object.pk %}
or
{% action_url model "action_name" pk=object.pk as variable_name %}
The first argument is a model instance. The second argument is the action name.
Additional keyword arguments can be passed for URL parameters.
For example, if you have a Device model and want to link to its edit action::
{% action_url device "edit" %}
This will generate a URL like ``/dcim/devices/123/edit/``.
You can also pass additional parameters::
{% action_url device "journal" pk=device.pk %}
Or assign the URL to a variable::
{% action_url device "edit" as edit_url %}
"""
class ActionURLNode(template.Node):
"""Template node for the {% action_url %} template tag."""
child_nodelists = ()
def __init__(self, model, action, kwargs, asvar=None):
self.model = model
self.action = action
self.kwargs = kwargs
self.asvar = asvar
def __repr__(self):
return (
f"<{self.__class__.__qualname__} "
f"model='{self.model}' "
f"action='{self.action}' "
f"kwargs={repr(self.kwargs)} "
f"as={repr(self.asvar)}>"
)
def render(self, context):
"""
Render the action URL node.
Args:
context: The template context
Returns:
The resolved URL or empty string if using 'as' syntax
Raises:
NoReverseMatch: If the URL cannot be resolved and not using 'as' syntax
"""
# Resolve model and kwargs from context
model = self.model.resolve(context)
kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
# Get the action URL using the utility function
try:
url = get_action_url(model, action=self.action, kwargs=kwargs)
except NoReverseMatch:
if self.asvar is None:
raise
url = ""
# Handle variable assignment or return escaped URL
if self.asvar:
context[self.asvar] = url
return ""
return conditional_escape(url) if context.autoescape else url
# Parse the token contents
bits = token.split_contents()
if len(bits) < 3:
raise template.TemplateSyntaxError(
f"'{bits[0]}' takes at least two arguments, a model and an action."
)
# Extract model and action
model = parser.compile_filter(bits[1])
action = bits[2].strip('"\'') # Remove quotes from literal string
kwargs = {}
asvar = None
bits = bits[3:]
# Handle 'as' syntax for variable assignment
if len(bits) >= 2 and bits[-2] == "as":
asvar = bits[-1]
bits = bits[:-2]
# Parse remaining arguments as kwargs
for bit in bits:
if '=' not in bit:
raise template.TemplateSyntaxError(
f"'{token.contents.split()[0]}' keyword arguments must be in the format 'name=value'"
)
name, value = bit.split('=', 1)
kwargs[name] = parser.compile_filter(value)
return ActionURLNode(model, action, kwargs, asvar)
@register.filter()
def humanize_speed(speed):
"""