diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py
index fc28afe8d..284691469 100644
--- a/netbox/netbox/tables/columns.py
+++ b/netbox/netbox/tables/columns.py
@@ -10,7 +10,7 @@ from django.utils.safestring import mark_safe
from django_tables2.utils import Accessor
from extras.choices import CustomFieldTypeChoices
-from utilities.utils import content_type_identifier, content_type_name, resolve_namespace
+from utilities.utils import content_type_identifier, content_type_name, get_viewname
__all__ = (
'ActionsColumn',
@@ -134,7 +134,6 @@ class ActionsColumn(tables.Column):
return ''
model = table.Meta.model
- viewname_base = f'{resolve_namespace(model)}:{model._meta.model_name}'
request = getattr(table, 'context', {}).get('request')
url_appendix = f'?return_url={request.path}' if request else ''
@@ -143,7 +142,7 @@ class ActionsColumn(tables.Column):
for action, attrs in self.actions.items():
permission = f'{model._meta.app_label}.{attrs.permission}_{model._meta.model_name}'
if attrs.permission is None or user.has_perm(permission):
- url = reverse(f'{viewname_base}_{action}', kwargs={'pk': record.pk})
+ url = reverse(get_viewname(model, action), kwargs={'pk': record.pk})
links.append(f'
'
f' {attrs.title}')
diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py
index d8b4987ba..4b8fd931f 100644
--- a/netbox/utilities/templatetags/buttons.py
+++ b/netbox/utilities/templatetags/buttons.py
@@ -2,32 +2,18 @@ from django import template
from django.urls import reverse
from extras.models import ExportTemplate
-from utilities.utils import prepare_cloned_fields
+from utilities.utils import get_viewname, prepare_cloned_fields
register = template.Library()
-def _get_viewname(instance, action):
- """
- Return the appropriate viewname for adding, editing, or deleting an instance.
- """
-
- # Validate action
- assert action in ('add', 'edit', 'delete')
- viewname = "{}:{}_{}".format(
- instance._meta.app_label, instance._meta.model_name, action
- )
-
- return viewname
-
-
#
# Instance buttons
#
@register.inclusion_tag('buttons/clone.html')
def clone_button(instance):
- url = reverse(_get_viewname(instance, 'add'))
+ url = reverse(get_viewname(instance, 'add'))
# Populate cloned field values
param_string = prepare_cloned_fields(instance).urlencode()
@@ -41,7 +27,7 @@ def clone_button(instance):
@register.inclusion_tag('buttons/edit.html')
def edit_button(instance):
- viewname = _get_viewname(instance, 'edit')
+ viewname = get_viewname(instance, 'edit')
url = reverse(viewname, kwargs={'pk': instance.pk})
return {
@@ -51,7 +37,7 @@ def edit_button(instance):
@register.inclusion_tag('buttons/delete.html')
def delete_button(instance):
- viewname = _get_viewname(instance, 'delete')
+ viewname = get_viewname(instance, 'delete')
url = reverse(viewname, kwargs={'pk': instance.pk})
return {
diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py
index be7dc97d1..89c218b9b 100644
--- a/netbox/utilities/templatetags/helpers.py
+++ b/netbox/utilities/templatetags/helpers.py
@@ -16,10 +16,9 @@ from django.utils.safestring import mark_safe
from markdown import markdown
from netbox.config import get_config
-from netbox.settings import PLUGINS
from utilities.forms import get_selected_values, TableConfigForm
from utilities.markdown import StrikethroughExtension
-from utilities.utils import foreground_color, resolve_namespace
+from utilities.utils import foreground_color, get_viewname
register = template.Library()
@@ -116,8 +115,7 @@ def viewname(model, action):
"""
Return the view name for the given model and action. Does not perform any validation.
"""
- namespace = resolve_namespace(model)
- return f'{namespace}:{model._meta.model_name}_{action}'
+ return get_viewname(model, action)
@register.filter()
@@ -125,11 +123,10 @@ def validated_viewname(model, action):
"""
Return the view name for the given model and action if valid, or None if invalid.
"""
- namespace = resolve_namespace(model)
- viewname = f'{namespace}:{model._meta.model_name}_{action}'
+ viewname = get_viewname(model, action)
+
+ # Validate the view name
try:
- # Validate and return the view name. We don't return the actual URL yet because many of the templates
- # are written to pass a name to {% url %}.
reverse(viewname)
return viewname
except NoReverseMatch:
diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py
index 885beab10..e56da1b99 100644
--- a/netbox/utilities/utils.py
+++ b/netbox/utilities/utils.py
@@ -17,13 +17,24 @@ from extras.utils import is_taggable
from utilities.constants import HTTP_REQUEST_META_SAFE_COPY
-def resolve_namespace(instance):
+def get_viewname(model, action=None):
"""
- Get the appropriate namepsace for the app based on whether it is a Plugin or base application
+ Return the view name 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"
"""
- if isinstance(instance._meta.app_config, PluginConfig):
- return f'plugins:{instance._meta.app_label}'
- return f'{instance._meta.app_label}'
+ viewname = f'{model._meta.app_label}:{model._meta.model_name}'
+
+ # Determine whether this is a plugin view and adjust the namespace appropriately
+ if isinstance(model._meta.app_config, PluginConfig):
+ viewname = f'plugins:{viewname}'
+
+ # Append the action, if any
+ if action:
+ viewname = f'{viewname}_{action}'
+
+ return viewname
def csv_format(data):