#20048 add get_action_url utility function

This commit is contained in:
Arthur 2025-08-07 13:40:15 -07:00
parent 33d891e67b
commit fa262adc6f
6 changed files with 30 additions and 22 deletions

View File

@ -1,13 +1,13 @@
import inspect
from django.urls import NoReverseMatch, reverse
from django.urls import NoReverseMatch
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.models import ObjectType
from netbox.api.serializers import BaseModelSerializer
from utilities.views import get_viewname
from utilities.views import get_action_url
__all__ = (
'ObjectTypeSerializer',
@ -34,11 +34,10 @@ class ObjectTypeSerializer(BaseModelSerializer):
def get_rest_api_endpoint(self, obj):
if not (model := obj.model_class()):
return
if viewname := get_viewname(model, action='list', rest_api=True):
try:
return reverse(viewname)
except NoReverseMatch:
return
try:
return get_action_url(model, action='list', rest_api=True)
except NoReverseMatch:
return
@extend_schema_field(OpenApiTypes.STR)
def get_description(self, obj):

View File

@ -31,7 +31,7 @@ from utilities.querydict import normalize_querydict
from utilities.request import copy_safe_request
from utilities.rqworker import get_workers_for_queue
from utilities.templatetags.builtins.filters import render_markdown
from utilities.views import ContentTypePermissionRequiredMixin, get_viewname, register_model_view
from utilities.views import ContentTypePermissionRequiredMixin, get_action_url, register_model_view
from virtualization.models import VirtualMachine
from . import filtersets, forms, tables
from .constants import LOG_LEVEL_RANK
@ -1103,8 +1103,7 @@ class JournalEntryEditView(generic.ObjectEditView):
if not instance.assigned_object:
return reverse('extras:journalentry_list')
obj = instance.assigned_object
viewname = get_viewname(obj, 'journal')
return reverse(viewname, kwargs={'pk': obj.pk})
return get_action_url(obj, action='journal', kwargs={'pk': obj.pk})
@register_model_view(JournalEntry, 'delete')
@ -1113,8 +1112,7 @@ class JournalEntryDeleteView(generic.ObjectDeleteView):
def get_return_url(self, request, instance):
obj = instance.assigned_object
viewname = get_viewname(obj, 'journal')
return reverse(viewname, kwargs={'pk': obj.pk})
return get_action_url(obj, action='journal', kwargs={'pk': obj.pk})
@register_model_view(JournalEntry, 'bulk_import', path='import', detail=False)

View File

@ -1,12 +1,11 @@
from django.template import loader
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.translation import gettext as _
from core.models import ObjectType
from extras.models import ExportTemplate
from utilities.querydict import prepare_cloned_fields
from utilities.views import get_viewname
from utilities.views import get_action_url
__all__ = (
'AddObject',
@ -43,12 +42,11 @@ class ObjectAction:
@classmethod
def get_url(cls, obj):
viewname = get_viewname(obj, action=cls.name)
kwargs = {
kwarg: getattr(obj, kwarg) for kwarg in cls.url_kwargs
}
try:
return reverse(viewname, kwargs=kwargs)
return get_action_url(obj, action=cls.name, kwargs=kwargs)
except NoReverseMatch:
return

View File

@ -21,7 +21,7 @@ from extras.choices import CustomFieldTypeChoices
from utilities.object_types import object_type_identifier, object_type_name
from utilities.permissions import get_permission_for_model
from utilities.templatetags.builtins.filters import render_markdown
from utilities.views import get_viewname
from utilities.views import get_action_url
__all__ = (
'ActionsColumn',
@ -285,7 +285,7 @@ class ActionsColumn(tables.Column):
for idx, (action, attrs) in enumerate(self.actions.items()):
permission = get_permission_for_model(model, attrs.permission)
if attrs.permission is None or user.has_perm(permission):
url = reverse(get_viewname(model, action), kwargs={'pk': record.pk})
url = get_action_url(record, action=action, kwargs={'pk': record.pk})
# Render a separate button if a) only one action exists, or b) if split_actions is True
if len(self.actions) == 1 or (self.split_actions and idx == 0):

View File

@ -8,7 +8,6 @@ from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import RelatedField
from django.db.models.fields.reverse_related import ManyToOneRel
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
@ -23,7 +22,7 @@ from netbox.tables import columns
from utilities.html import highlight
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.string import title
from utilities.views import get_viewname
from utilities.views import get_action_url
from .template_code import *
__all__ = (
@ -261,9 +260,8 @@ class NetBoxTable(BaseTable):
Return the base HTML request URL for embedded tables.
"""
if self.embedded:
viewname = get_viewname(self._meta.model, action='list')
try:
return reverse(viewname)
return get_action_url(self._meta.model, action='list')
except NoReverseMatch:
pass
return ''

View File

@ -282,6 +282,21 @@ def get_viewname(model, action=None, rest_api=False):
return viewname
def get_action_url(model, action=None, rest_api=False, kwargs=None):
"""
Return the url for the given model and action, if valid.
:param model: The model or instance to which the view applies
:param action: A string indicating the desired action (if any); e.g. "add" or "list"
:param rest_api: A boolean indicating whether this is a REST API view
:param kwargs: A dictionary of keyword arguments for the view to include when registering its URL path (optional).
"""
if hasattr(model, '_get_action_url'):
return model._get_action_url(action, rest_api, kwargs)
return reverse(get_viewname(model, action, rest_api), kwargs=kwargs)
def register_model_view(model, name='', path=None, detail=True, kwargs=None):
"""
This decorator can be used to "attach" a view to any model in NetBox. This is typically used to inject