diff --git a/netbox/core/api/serializers_/object_types.py b/netbox/core/api/serializers_/object_types.py index e272509ec..1c17c6e44 100644 --- a/netbox/core/api/serializers_/object_types.py +++ b/netbox/core/api/serializers_/object_types.py @@ -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): diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 2173dc786..2497e9c80 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.cache import cache from django.db.models import Model from django.template.loader import render_to_string -from django.urls import NoReverseMatch, resolve, reverse +from django.urls import NoReverseMatch, resolve from django.utils.translation import gettext as _ from core.models import ObjectType @@ -21,7 +21,7 @@ from utilities.permissions import get_permission_for_model from utilities.proxy import resolve_proxies from utilities.querydict import dict_to_querydict from utilities.templatetags.builtins.filters import render_markdown -from utilities.views import get_viewname +from utilities.views import get_action_url from .utils import register_widget __all__ = ( @@ -53,9 +53,9 @@ def object_list_widget_supports_model(model: Model) -> bool: """ def can_resolve_model_list_view(model: Model) -> bool: try: - reverse(get_viewname(model, action='list')) + get_action_url(model, action='list') return True - except Exception: + except NoReverseMatch: return False tests = [ @@ -206,7 +206,7 @@ class ObjectCountsWidget(DashboardWidget): permission = get_permission_for_model(model, 'view') if request.user.has_perm(permission): try: - url = reverse(get_viewname(model, 'list')) + url = get_action_url(model, action='list') except NoReverseMatch: url = None qs = model.objects.restrict(request.user, 'view') @@ -275,15 +275,13 @@ class ObjectListWidget(DashboardWidget): logger.debug(f"Dashboard Widget model_class not found: {app_label}:{model_name}") return - viewname = get_viewname(model, action='list') - # Evaluate user's permission. Note that this controls only whether the HTMX element is # embedded on the page: The view itself will also evaluate permissions separately. permission = get_permission_for_model(model, 'view') has_permission = request.user.has_perm(permission) try: - htmx_url = reverse(viewname) + htmx_url = get_action_url(model, action='list') except NoReverseMatch: htmx_url = None parameters = self.config.get('url_params') or {} @@ -297,7 +295,7 @@ class ObjectListWidget(DashboardWidget): except ValueError: pass return render_to_string(self.template_name, { - 'viewname': viewname, + 'model_name': model_name, 'has_permission': has_permission, 'htmx_url': htmx_url, }) diff --git a/netbox/extras/tests/test_dashboard.py b/netbox/extras/tests/test_dashboard.py index 19ce5a43d..fb94710c7 100644 --- a/netbox/extras/tests/test_dashboard.py +++ b/netbox/extras/tests/test_dashboard.py @@ -45,4 +45,4 @@ class ObjectListWidgetTests(TestCase): mock_request = Request() widget = ObjectListWidget(id='2829fd9b-5dee-4c9a-81f2-5bd84c350a27', **config) rendered = widget.render(mock_request) - self.assertTrue('Unable to load content. Invalid view name:' in rendered) + self.assertTrue('Unable to load content. Could not resolve list URL for:' in rendered) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index a2664a2c2..cf197b55e 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -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) diff --git a/netbox/netbox/object_actions.py b/netbox/netbox/object_actions.py index 51687eee1..4bb1630a0 100644 --- a/netbox/netbox/object_actions.py +++ b/netbox/netbox/object_actions.py @@ -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 diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 3d36ec8f8..84722512b 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -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(model, 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): diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 63fd0ea0e..89a9c1ac2 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -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 '' diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index aa77507de..1a5ee15e5 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -12,7 +12,6 @@ from django.db.models.fields.reverse_related import ManyToManyRel from django.forms import ModelMultipleChoiceField, MultipleHiddenInput from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ from mptt.models import MPTTModel @@ -34,7 +33,7 @@ from utilities.permissions import get_permission_for_model from utilities.query import reapply_model_ordering from utilities.request import safe_for_redirect from utilities.tables import get_table_configs -from utilities.views import GetReturnURLMixin, get_viewname +from utilities.views import GetReturnURLMixin, get_action_url from .base import BaseMultiObjectView from .mixins import ActionsMixin, TableMixin from .utils import get_prerequisite_model @@ -130,7 +129,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): redirect_url = f'{request.path}?{query_params.urlencode()}' if safe_for_redirect(redirect_url): return redirect(redirect_url) - return redirect(get_viewname(self.queryset.model, 'list')) + return redirect(get_action_url(self.queryset.model, action='list')) # # Request handlers @@ -513,7 +512,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): if form.is_valid(): logger.debug("Import form validation was successful") - redirect_url = reverse(get_viewname(model, action='list')) + redirect_url = get_action_url(model, action='list') # If indicated, defer this request to a background job & redirect the user if form.cleaned_data['background_job']: diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index efb1b58d0..f45d75adc 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -25,7 +25,7 @@ from utilities.permissions import get_permission_for_model from utilities.querydict import normalize_querydict, prepare_cloned_fields from utilities.request import safe_for_redirect from utilities.tables import get_table_configs -from utilities.views import GetReturnURLMixin, get_viewname +from utilities.views import GetReturnURLMixin, get_action_url from .base import BaseObjectView from .mixins import ActionsMixin, TableMixin from .utils import get_prerequisite_model @@ -436,8 +436,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView): # If this is an HTMX request, return only the rendered deletion form as modal content if htmx_partial(request): - viewname = get_viewname(self.queryset.model, action='delete') - form_url = reverse(viewname, kwargs={'pk': obj.pk}) + form_url = get_action_url(self.queryset.model, action='delete', kwargs={'pk': obj.pk}) return render(request, 'htmx/delete_form.html', { 'object': obj, 'object_type': self.queryset.model._meta.verbose_name, diff --git a/netbox/templates/core/job/base.html b/netbox/templates/core/job/base.html index a31c53a8a..151702946 100644 --- a/netbox/templates/core/job/base.html +++ b/netbox/templates/core/job/base.html @@ -10,11 +10,9 @@