diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py
index ea17031a1..ce3368f31 100644
--- a/netbox/circuits/tables.py
+++ b/netbox/circuits/tables.py
@@ -2,19 +2,9 @@ import django_tables2 as tables
from django_tables2.utils import Accessor
from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, TagColumn, ToggleColumn
+from utilities.tables import BaseTable, ButtonsColumn, TagColumn, ToggleColumn
from .models import Circuit, CircuitType, Provider
-CIRCUITTYPE_ACTIONS = """
-
-
-
-{% if perms.circuit.change_circuittype %}
-
-{% endif %}
-"""
-
STATUS_LABEL = """
{{ record.get_status_display }}
"""
@@ -53,11 +43,7 @@ class CircuitTypeTable(BaseTable):
circuit_count = tables.Column(
verbose_name='Circuits'
)
- actions = tables.TemplateColumn(
- template_code=CIRCUITTYPE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(CircuitType, pk_field='slug')
class Meta(BaseTable.Meta):
model = CircuitType
diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py
index dd6b96406..871679fc7 100644
--- a/netbox/dcim/tables.py
+++ b/netbox/dcim/tables.py
@@ -2,7 +2,9 @@ import django_tables2 as tables
from django_tables2.utils import Accessor
from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn
+from utilities.tables import (
+ BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn,
+)
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
@@ -40,69 +42,16 @@ DEVICE_LINK = """
"""
-REGION_ACTIONS = """
-
-
-
-{% if perms.dcim.change_region %}
-
-{% endif %}
-"""
-
-RACKGROUP_ACTIONS = """
-
-
-
+RACKGROUP_ELEVATIONS = """
-{% if perms.dcim.change_rackgroup %}
-
-
-
-{% endif %}
-"""
-
-RACKROLE_ACTIONS = """
-
-
-
-{% if perms.dcim.change_rackrole %}
-
-{% endif %}
"""
RACK_DEVICE_COUNT = """
{{ value }}
"""
-RACKRESERVATION_ACTIONS = """
-
-
-
-{% if perms.dcim.change_rackreservation %}
-
-{% endif %}
-"""
-
-MANUFACTURER_ACTIONS = """
-
-
-
-{% if perms.dcim.change_manufacturer %}
-
-{% endif %}
-"""
-
-DEVICEROLE_ACTIONS = """
-
-
-
-{% if perms.dcim.change_devicerole %}
-
-{% endif %}
-"""
-
DEVICEROLE_DEVICE_COUNT = """
{{ value }}
"""
@@ -119,15 +68,6 @@ PLATFORM_VM_COUNT = """
{{ value }}
"""
-PLATFORM_ACTIONS = """
-
-
-
-{% if perms.dcim.change_platform %}
-
-{% endif %}
-"""
-
STATUS_LABEL = """
{{ record.get_status_display }}
"""
@@ -198,11 +138,7 @@ class RegionTable(BaseTable):
site_count = tables.Column(
verbose_name='Sites'
)
- actions = tables.TemplateColumn(
- template_code=REGION_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(Region)
class Meta(BaseTable.Meta):
model = Region
@@ -260,10 +196,9 @@ class RackGroupTable(BaseTable):
rack_count = tables.Column(
verbose_name='Racks'
)
- actions = tables.TemplateColumn(
- template_code=RACKGROUP_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
+ actions = ButtonsColumn(
+ model=RackGroup,
+ prepend_template=RACKGROUP_ELEVATIONS
)
class Meta(BaseTable.Meta):
@@ -280,11 +215,7 @@ class RackRoleTable(BaseTable):
pk = ToggleColumn()
rack_count = tables.Column(verbose_name='Racks')
color = tables.TemplateColumn(COLOR_LABEL)
- actions = tables.TemplateColumn(
- template_code=RACKROLE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(RackRole)
class Meta(BaseTable.Meta):
model = RackRole
@@ -386,11 +317,7 @@ class RackReservationTable(BaseTable):
tags = TagColumn(
url_name='dcim:rackreservation_list'
)
- actions = tables.TemplateColumn(
- template_code=RACKRESERVATION_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(RackReservation)
class Meta(BaseTable.Meta):
model = RackReservation
@@ -420,11 +347,7 @@ class ManufacturerTable(BaseTable):
verbose_name='Platforms'
)
slug = tables.Column()
- actions = tables.TemplateColumn(
- template_code=MANUFACTURER_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(Manufacturer, pk_field='slug')
class Meta(BaseTable.Meta):
model = Manufacturer
@@ -609,11 +532,8 @@ class DeviceRoleTable(BaseTable):
template_code=COLOR_LABEL,
verbose_name='Label'
)
- actions = tables.TemplateColumn(
- template_code=DEVICEROLE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ vm_role = BooleanColumn()
+ actions = ButtonsColumn(DeviceRole, pk_field='slug')
class Meta(BaseTable.Meta):
model = DeviceRole
@@ -639,11 +559,7 @@ class PlatformTable(BaseTable):
orderable=False,
verbose_name='VMs'
)
- actions = tables.TemplateColumn(
- template_code=PLATFORM_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(Platform, pk_field='slug')
class Meta(BaseTable.Meta):
model = Platform
diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py
index 7a78d4b19..79a529059 100644
--- a/netbox/extras/tables.py
+++ b/netbox/extras/tables.py
@@ -1,21 +1,9 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
-from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
+from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, ToggleColumn
from .models import ConfigContext, ObjectChange, Tag, TaggedItem
-TAG_ACTIONS = """
-
-
-
-{% if perms.taggit.change_tag %}
-
-{% endif %}
-{% if perms.taggit.delete_tag %}
-
-{% endif %}
-"""
-
TAGGED_ITEM = """
{% if value.get_absolute_url %}
{{ value }}
@@ -68,12 +56,8 @@ class TagTable(BaseTable):
viewname='extras:tag',
args=[Accessor('slug')]
)
- actions = tables.TemplateColumn(
- template_code=TAG_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
color = ColorColumn()
+ actions = ButtonsColumn(Tag, pk_field='slug')
class Meta(BaseTable.Meta):
model = Tag
diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py
index f1855327b..72ac3eb45 100644
--- a/netbox/ipam/tables.py
+++ b/netbox/ipam/tables.py
@@ -3,7 +3,7 @@ from django_tables2.utils import Accessor
from dcim.models import Interface
from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn
+from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, TagColumn, ToggleColumn
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
RIR_UTILIZATION = """
@@ -25,15 +25,6 @@ RIR_UTILIZATION = """
"""
-RIR_ACTIONS = """
-
-
-
-{% if perms.ipam.change_rir %}
-
-{% endif %}
-"""
-
UTILIZATION_GRAPH = """
{% load helpers %}
{% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}—{% endif %}
@@ -47,15 +38,6 @@ ROLE_VLAN_COUNT = """
{{ value }}
"""
-ROLE_ACTIONS = """
-
-
-
-{% if perms.ipam.change_role %}
-
-{% endif %}
-"""
-
PREFIX_LINK = """
{% if record.has_children %}
@@ -136,10 +118,7 @@ VLAN_ROLE_LINK = """
{% endif %}
"""
-VLANGROUP_ACTIONS = """
-
-
-
+VLANGROUP_ADD_VLAN = """
{% with next_vid=record.get_next_available_vid %}
{% if next_vid and perms.ipam.add_vlan %}
@@ -147,9 +126,6 @@ VLANGROUP_ACTIONS = """
{% endif %}
{% endwith %}
-{% if perms.ipam.change_vlangroup %}
-
-{% endif %}
"""
VLAN_MEMBER_UNTAGGED = """
@@ -214,11 +190,7 @@ class RIRTable(BaseTable):
aggregate_count = tables.Column(
verbose_name='Aggregates'
)
- actions = tables.TemplateColumn(
- template_code=RIR_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(RIR, pk_field='slug')
class Meta(BaseTable.Meta):
model = RIR
@@ -322,11 +294,7 @@ class RoleTable(BaseTable):
orderable=False,
verbose_name='VLANs'
)
- actions = tables.TemplateColumn(
- template_code=ROLE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(Role, pk_field='slug')
class Meta(BaseTable.Meta):
model = Role
@@ -516,10 +484,9 @@ class VLANGroupTable(BaseTable):
vlan_count = tables.Column(
verbose_name='VLANs'
)
- actions = tables.TemplateColumn(
- template_code=VLANGROUP_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
+ actions = ButtonsColumn(
+ model=VLANGroup,
+ prepend_template=VLANGROUP_ADD_VLAN
)
class Meta(BaseTable.Meta):
diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py
index f92c9216b..f773a278f 100644
--- a/netbox/secrets/tables.py
+++ b/netbox/secrets/tables.py
@@ -1,17 +1,8 @@
import django_tables2 as tables
-from utilities.tables import BaseTable, TagColumn, ToggleColumn
+from utilities.tables import BaseTable, ButtonsColumn, TagColumn, ToggleColumn
from .models import SecretRole, Secret
-SECRETROLE_ACTIONS = """
-
-
-
-{% if perms.secrets.change_secretrole %}
-
-{% endif %}
-"""
-
#
# Secret roles
@@ -23,11 +14,7 @@ class SecretRoleTable(BaseTable):
secret_count = tables.Column(
verbose_name='Secrets'
)
- actions = tables.TemplateColumn(
- template_code=SECRETROLE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(SecretRole, pk_field='slug')
class Meta(BaseTable.Meta):
model = SecretRole
diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py
index 147a20707..dc96b839c 100644
--- a/netbox/tenancy/tables.py
+++ b/netbox/tenancy/tables.py
@@ -1,6 +1,6 @@
import django_tables2 as tables
-from utilities.tables import BaseTable, TagColumn, ToggleColumn
+from utilities.tables import BaseTable, ButtonsColumn, TagColumn, ToggleColumn
from .models import Tenant, TenantGroup
MPTT_LINK = """
@@ -13,15 +13,6 @@ MPTT_LINK = """
"""
-TENANTGROUP_ACTIONS = """
-
-
-
-{% if perms.tenancy.change_tenantgroup %}
-
-{% endif %}
-"""
-
COL_TENANT = """
{% if record.tenant %}
{{ record.tenant }}
@@ -44,11 +35,7 @@ class TenantGroupTable(BaseTable):
tenant_count = tables.Column(
verbose_name='Tenants'
)
- actions = tables.TemplateColumn(
- template_code=TENANTGROUP_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(TenantGroup, pk_field='slug')
class Meta(BaseTable.Meta):
model = TenantGroup
diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py
index 5e277e633..ec3d5dff5 100644
--- a/netbox/utilities/tables.py
+++ b/netbox/utilities/tables.py
@@ -123,6 +123,49 @@ class BooleanColumn(tables.Column):
return mark_safe(rendered)
+class ButtonsColumn(tables.TemplateColumn):
+ """
+ Render edit, delete, and changelog buttons for an object.
+
+ :param model: Model class to use for calculating URL view names
+ :param prepend_content: Additional template content to render in the column (optional)
+ """
+ attrs = {'td': {'class': 'text-right text-nowrap noprint'}}
+ # Note that braces are escaped to allow for string formatting prior to template rendering
+ template_code = """
+
+
+
+ {{% if perms.{app_label}.change_{model_name} %}}
+
+
+
+ {{% endif %}}
+ {{% if perms.{app_label}.delete_{model_name} %}}
+
+
+
+ {{% endif %}}
+ """
+
+ def __init__(self, model, *args, pk_field='pk', prepend_template=None, **kwargs):
+ if prepend_template:
+ prepend_template = prepend_template.replace('{', '{{')
+ prepend_template = prepend_template.replace('}', '}}')
+ self.template_code = prepend_template + self.template_code
+
+ template_code = self.template_code.format(
+ app_label=model._meta.app_label,
+ model_name=model._meta.model_name,
+ pk_field=pk_field
+ )
+
+ super().__init__(template_code=template_code, *args, **kwargs)
+
+ def header(self):
+ return ''
+
+
class ColorColumn(tables.Column):
"""
Display a color (#RRGGBB).
diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py
index de319361c..d53572583 100644
--- a/netbox/virtualization/tables.py
+++ b/netbox/virtualization/tables.py
@@ -2,27 +2,9 @@ import django_tables2 as tables
from django_tables2.utils import Accessor
from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, ColoredLabelColumn, TagColumn, ToggleColumn
+from utilities.tables import BaseTable, ButtonsColumn, ColoredLabelColumn, TagColumn, ToggleColumn
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
-CLUSTERTYPE_ACTIONS = """
-
-
-
-{% if perms.virtualization.change_clustertype %}
-
-{% endif %}
-"""
-
-CLUSTERGROUP_ACTIONS = """
-
-
-
-{% if perms.virtualization.change_clustergroup %}
-
-{% endif %}
-"""
-
VIRTUALMACHINE_STATUS = """
{{ record.get_status_display }}
"""
@@ -44,11 +26,7 @@ class ClusterTypeTable(BaseTable):
cluster_count = tables.Column(
verbose_name='Clusters'
)
- actions = tables.TemplateColumn(
- template_code=CLUSTERTYPE_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(ClusterType, pk_field='slug')
class Meta(BaseTable.Meta):
model = ClusterType
@@ -66,11 +44,7 @@ class ClusterGroupTable(BaseTable):
cluster_count = tables.Column(
verbose_name='Clusters'
)
- actions = tables.TemplateColumn(
- template_code=CLUSTERGROUP_ACTIONS,
- attrs={'td': {'class': 'text-right noprint'}},
- verbose_name=''
- )
+ actions = ButtonsColumn(ClusterGroup, pk_field='slug')
class Meta(BaseTable.Meta):
model = ClusterGroup