diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py
index 424cc6cd9..7532700c7 100644
--- a/netbox/utilities/tables.py
+++ b/netbox/utilities/tables.py
@@ -149,20 +149,15 @@ class ButtonsColumn(tables.TemplateColumn):
attrs = {'td': {'class': 'text-right text-nowrap noprint'}}
# Note that braces are escaped to allow for string formatting prior to template rendering
template_code = """
+ {{% load buttons %}}
{{% if "changelog" in buttons %}}
-
-
-
+ {{% tr_changelog_button record %}}
{{% endif %}}
{{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
-
-
-
+ {{% tr_edit_button record return_url_extra %}}
{{% endif %}}
{{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
-
-
-
+ {{% tr_delete_button record return_url_extra %}}
{{% endif %}}
"""
diff --git a/netbox/utilities/templates/buttons/tr_changelog.html b/netbox/utilities/templates/buttons/tr_changelog.html
new file mode 100644
index 000000000..7a2b808b1
--- /dev/null
+++ b/netbox/utilities/templates/buttons/tr_changelog.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/netbox/utilities/templates/buttons/tr_delete.html b/netbox/utilities/templates/buttons/tr_delete.html
new file mode 100644
index 000000000..923eb911f
--- /dev/null
+++ b/netbox/utilities/templates/buttons/tr_delete.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/netbox/utilities/templates/buttons/tr_edit.html b/netbox/utilities/templates/buttons/tr_edit.html
new file mode 100644
index 000000000..f60d7b407
--- /dev/null
+++ b/netbox/utilities/templates/buttons/tr_edit.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py
index c486dd2e5..d47a6f622 100644
--- a/netbox/utilities/templatetags/buttons.py
+++ b/netbox/utilities/templatetags/buttons.py
@@ -4,22 +4,71 @@ from django.urls import reverse
from extras.models import ExportTemplate
from utilities.utils import prepare_cloned_fields
+from .helpers import _resolve_namespace
+
register = template.Library()
+def _get_listviewname(instance):
+ app_label = _resolve_namespace(instance)
+ return f'{app_label}:{instance._meta.model_name}_list'
+
+
def _get_viewname(instance, action):
"""
- Return the appropriate viewname for adding, editing, or deleting an instance.
+ Return the appropriate viewname for adding, editing, or deleting an instance or viewing.
"""
# Validate action
assert action in ('add', 'edit', 'delete')
- viewname = "{}:{}_{}".format(
- instance._meta.app_label, instance._meta.model_name, action
- )
+ app_label = _resolve_namespace(instance)
+ viewname = f'{app_label}:{instance._meta.model_name}_{action}'
return viewname
+#
+# Table buttons
+#
+
+@register.inclusion_tag('buttons/tr_edit.html')
+def tr_edit_button(instance, extra=None):
+ viewname = _get_viewname(instance, 'edit')
+ base_url = reverse(_get_listviewname(instance))
+ url = reverse(viewname, kwargs={'pk': instance.pk})
+ url = f'{url}?return_url={base_url}'
+
+ if extra is not None:
+ url = f'{url}{extra}'
+
+ return {
+ 'url': url,
+ }
+
+@register.inclusion_tag('buttons/tr_delete.html')
+def tr_delete_button(instance, extra=None):
+ viewname = _get_viewname(instance, 'delete')
+ base_url = reverse(_get_listviewname(instance))
+ url = reverse(viewname, kwargs={'pk': instance.pk})
+ url = f'{url}?return_url={base_url}'
+
+ if extra is not None:
+ url = f'{url}{extra}'
+
+ return {
+ 'url': url,
+ }
+
+@register.inclusion_tag('buttons/tr_changelog.html')
+def tr_changelog_button(instance):
+ app_label = _resolve_namespace(instance)
+ viewname = f'{app_label}:{instance._meta.model_name}_changelog'
+ url = reverse(viewname, kwargs={'pk': instance.pk})
+
+ return {
+ 'url': url,
+ }
+
+
#
# Instance buttons
diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py
index 01dce8479..cf85d4ac5 100644
--- a/netbox/utilities/templatetags/helpers.py
+++ b/netbox/utilities/templatetags/helpers.py
@@ -4,6 +4,7 @@ import re
import yaml
from django import template
+from django.apps import apps
from django.conf import settings
from django.urls import NoReverseMatch, reverse
from django.utils.html import strip_tags
@@ -13,9 +14,21 @@ from markdown import markdown
from utilities.forms import TableConfigForm
from utilities.utils import foreground_color
+from extras.plugins import PluginConfig
+
register = template.Library()
+def _resolve_namespace(instance):
+ """
+ Get the appropriate namespace for the app based on whether it is a Plugin or base application
+ """
+ app = apps.get_app_config(instance._meta.app_label)
+ if isinstance(app, PluginConfig):
+ return f'plugins:{app.label}'
+ return f'{app.label}'
+
+
#
# Filters
#
@@ -80,7 +93,7 @@ def viewname(model, action):
"""
Return the view name for the given model and action. Does not perform any validation.
"""
- return f'{model._meta.app_label}:{model._meta.model_name}_{action}'
+ return f'{_resolve_namespace(model)}:{model._meta.model_name}_{action}'
@register.filter()
@@ -88,7 +101,7 @@ def validated_viewname(model, action):
"""
Return the view name for the given model and action if valid, or None if invalid.
"""
- viewname = f'{model._meta.app_label}:{model._meta.model_name}_{action}'
+ viewname = f'{_resolve_namespace(model)}:{model._meta.model_name}_{action}'
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 %}.