diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py
index f21bc3204..1241143b7 100644
--- a/netbox/dcim/tables/devices.py
+++ b/netbox/dcim/tables/devices.py
@@ -7,7 +7,7 @@ from dcim.models import (
)
from tenancy.tables import TenantColumn
from utilities.tables import (
- ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
+ ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn,
)
from .template_code import *
@@ -322,10 +322,8 @@ class DeviceConsolePortTable(ConsolePortTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=ConsolePort,
- buttons=('edit', 'delete'),
- prepend_template=CONSOLEPORT_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=CONSOLEPORT_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -367,10 +365,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=ConsoleServerPort,
- buttons=('edit', 'delete'),
- prepend_template=CONSOLESERVERPORT_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=CONSOLESERVERPORT_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -412,10 +408,8 @@ class DevicePowerPortTable(PowerPortTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=PowerPort,
- buttons=('edit', 'delete'),
- prepend_template=POWERPORT_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=POWERPORT_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -461,10 +455,8 @@ class DevicePowerOutletTable(PowerOutletTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=PowerOutlet,
- buttons=('edit', 'delete'),
- prepend_template=POWEROUTLET_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=POWEROUTLET_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -551,10 +543,8 @@ class DeviceInterfaceTable(InterfaceTable):
linkify=True,
verbose_name='LAG'
)
- actions = ButtonsColumn(
- model=Interface,
- buttons=('edit', 'delete'),
- prepend_template=INTERFACE_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=INTERFACE_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -614,10 +604,8 @@ class DeviceFrontPortTable(FrontPortTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=FrontPort,
- buttons=('edit', 'delete'),
- prepend_template=FRONTPORT_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=FRONTPORT_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -662,10 +650,8 @@ class DeviceRearPortTable(RearPortTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=RearPort,
- buttons=('edit', 'delete'),
- prepend_template=REARPORT_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=REARPORT_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -713,10 +699,8 @@ class DeviceDeviceBayTable(DeviceBayTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=DeviceBay,
- buttons=('edit', 'delete'),
- prepend_template=DEVICEBAY_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=DEVICEBAY_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -749,10 +733,8 @@ class ModuleBayTable(DeviceComponentTable):
class DeviceModuleBayTable(ModuleBayTable):
- actions = ButtonsColumn(
- model=DeviceBay,
- buttons=('edit', 'delete'),
- prepend_template=MODULEBAY_BUTTONS
+ actions = ActionsColumn(
+ extra_buttons=MODULEBAY_BUTTONS
)
class Meta(DeviceComponentTable.Meta):
@@ -803,10 +785,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
- actions = ButtonsColumn(
- model=InventoryItem,
- buttons=('edit', 'delete')
- )
+ actions = ActionsColumn()
class Meta(BaseTable.Meta):
model = InventoryItem
diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py
index 29fa4d4de..ecec67f7d 100644
--- a/netbox/dcim/tables/devicetypes.py
+++ b/netbox/dcim/tables/devicetypes.py
@@ -6,8 +6,7 @@ from dcim.models import (
InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
)
from utilities.tables import (
- ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
- ToggleColumn,
+ ActionsColumn, BaseTable, BooleanColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
)
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -113,10 +112,9 @@ class ComponentTemplateTable(BaseTable):
class ConsolePortTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=ConsolePortTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -126,10 +124,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=ConsoleServerPortTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -139,10 +136,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
class PowerPortTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=PowerPortTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -152,10 +148,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
class PowerOutletTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=PowerOutletTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -168,10 +163,9 @@ class InterfaceTemplateTable(ComponentTemplateTable):
mgmt_only = BooleanColumn(
verbose_name='Management Only'
)
- actions = ButtonsColumn(
- model=InterfaceTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -185,10 +179,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
verbose_name='Position'
)
color = ColorColumn()
- actions = ButtonsColumn(
- model=FrontPortTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -199,10 +192,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
class RearPortTemplateTable(ComponentTemplateTable):
color = ColorColumn()
- actions = ButtonsColumn(
- model=RearPortTemplate,
- buttons=('edit', 'delete'),
- prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
)
class Meta(ComponentTemplateTable.Meta):
@@ -212,9 +204,8 @@ class RearPortTemplateTable(ComponentTemplateTable):
class ModuleBayTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=ModuleBayTemplate,
- buttons=('edit', 'delete')
+ actions = ActionsColumn(
+ sequence=('edit', 'delete')
)
class Meta(ComponentTemplateTable.Meta):
@@ -224,9 +215,8 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
class DeviceBayTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=DeviceBayTemplate,
- buttons=('edit', 'delete')
+ actions = ActionsColumn(
+ sequence=('edit', 'delete')
)
class Meta(ComponentTemplateTable.Meta):
@@ -236,9 +226,8 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
class InventoryItemTemplateTable(ComponentTemplateTable):
- actions = ButtonsColumn(
- model=InventoryItemTemplate,
- buttons=('edit', 'delete')
+ actions = ActionsColumn(
+ sequence=('edit', 'delete')
)
role = tables.Column(
linkify=True
diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py
index 23ffabae2..98c5e3fd3 100644
--- a/netbox/dcim/tables/sites.py
+++ b/netbox/dcim/tables/sites.py
@@ -3,9 +3,9 @@ import django_tables2 as tables
from dcim.models import Location, Region, Site, SiteGroup
from tenancy.tables import TenantColumn
from utilities.tables import (
- BaseTable, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, MarkdownColumn, MPTTColumn, TagColumn, ToggleColumn,
+ ActionsColumn, BaseTable, ChoiceFieldColumn, LinkedCountColumn, MarkdownColumn, MPTTColumn, TagColumn, ToggleColumn,
)
-from .template_code import LOCATION_ELEVATIONS
+from .template_code import LOCATION_BUTTONS
__all__ = (
'LocationTable',
@@ -127,9 +127,8 @@ class LocationTable(BaseTable):
tags = TagColumn(
url_name='dcim:location_list'
)
- actions = ButtonsColumn(
- model=Location,
- prepend_template=LOCATION_ELEVATIONS
+ actions = ActionsColumn(
+ extra_buttons=LOCATION_BUTTONS
)
class Meta(BaseTable.Meta):
diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py
index 2b6c02b82..a1baeb336 100644
--- a/netbox/dcim/tables/template_code.py
+++ b/netbox/dcim/tables/template_code.py
@@ -87,7 +87,7 @@ POWERFEED_CABLETERMINATION = """
{{ value }}
"""
-LOCATION_ELEVATIONS = """
+LOCATION_BUTTONS = """
@@ -99,8 +99,8 @@ LOCATION_ELEVATIONS = """
MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
{% load helpers %}
-{% if perms.dcim.add_invnetoryitemtemplate %}
-
+{% if perms.dcim.add_inventoryitemtemplate %}
+
{% endif %}
diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py
index 1379ad105..3454ddff4 100644
--- a/netbox/ipam/tables/vlans.py
+++ b/netbox/ipam/tables/vlans.py
@@ -5,8 +5,8 @@ from django_tables2.utils import Accessor
from dcim.models import Interface
from tenancy.tables import TenantColumn
from utilities.tables import (
- ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn,
- TagColumn, TemplateColumn, ToggleColumn,
+ ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
+ TemplateColumn, ToggleColumn,
)
from virtualization.models import VMInterface
from ipam.models import *
@@ -38,7 +38,7 @@ VLAN_PREFIXES = """
{% endfor %}
"""
-VLANGROUP_ADD_VLAN = """
+VLANGROUP_BUTTONS = """
{% with next_vid=record.get_next_available_vid %}
{% if next_vid and perms.ipam.add_vlan %}
@@ -77,9 +77,8 @@ class VLANGroupTable(BaseTable):
tags = TagColumn(
url_name='ipam:vlangroup_list'
)
- actions = ButtonsColumn(
- model=VLANGroup,
- prepend_template=VLANGROUP_ADD_VLAN
+ actions = ActionsColumn(
+ extra_buttons=VLANGROUP_BUTTONS
)
class Meta(BaseTable.Meta):
diff --git a/netbox/utilities/tables/columns.py b/netbox/utilities/tables/columns.py
index e601bd0cc..a319fc7ad 100644
--- a/netbox/utilities/tables/columns.py
+++ b/netbox/utilities/tables/columns.py
@@ -1,9 +1,10 @@
-from collections import namedtuple
from dataclasses import dataclass
from typing import Optional
import django_tables2 as tables
from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
+from django.template import Context, Template
from django.urls import reverse
from django.utils.safestring import mark_safe
from django_tables2.utils import Accessor
@@ -14,7 +15,6 @@ from utilities.utils import content_type_identifier, content_type_name
__all__ = (
'ActionsColumn',
'BooleanColumn',
- 'ButtonsColumn',
'ChoiceFieldColumn',
'ColorColumn',
'ColoredLabelColumn',
@@ -100,7 +100,14 @@ class ActionsItem:
class ActionsColumn(tables.Column):
- attrs = {'td': {'class': 'text-end noprint'}}
+ """
+ A dropdown menu which provides edit, delete, and changelog links for an object. Can optionally include
+ additional buttons rendered from a template string.
+
+ :param sequence: The ordered list of dropdown menu items to include
+ :param extra_buttons: A Django template string which renders additional buttons preceding the actions dropdown
+ """
+ attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
empty_values = ()
actions = {
'edit': ActionsItem('Edit', 'pencil', 'change'),
@@ -108,12 +115,10 @@ class ActionsColumn(tables.Column):
'changelog': ActionsItem('Changelog', 'history'),
}
- def __init__(self, *args, extra_actions=None, sequence=('edit', 'delete', 'changelog'), **kwargs):
+ def __init__(self, *args, sequence=('edit', 'delete', 'changelog'), extra_buttons='', **kwargs):
super().__init__(*args, **kwargs)
- # Add/update any extra actions passed
- if extra_actions:
- self.actions.update(extra_actions)
+ self.extra_buttons = extra_buttons
# Determine which actions to enable
self.actions = {
@@ -134,9 +139,10 @@ class ActionsColumn(tables.Column):
url_appendix = f'?return_url={request.path}' if request else ''
links = []
+ user = getattr(request, 'user', AnonymousUser())
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 request.user.has_perm(permission):
+ if attrs.permission is None or user.has_perm(permission):
url = reverse(f'{viewname_base}_{action}', kwargs={'pk': record.pk})
links.append(f''
f' {attrs.title}')
@@ -144,68 +150,21 @@ class ActionsColumn(tables.Column):
if not links:
return ''
- menu = f''
+ f''
+
+ # Render any extra buttons from template code
+ if self.extra_buttons:
+ template = Template(self.extra_buttons)
+ context = getattr(table, "context", Context())
+ context.update({'record': record})
+ menu = template.render(context) + menu
return mark_safe(menu)
-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)
- """
- buttons = ('changelog', 'edit', 'delete')
- attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
- # Note that braces are escaped to allow for string formatting prior to template rendering
- template_code = """
- {{% if "changelog" in buttons %}}
-
-
-
- {{% endif %}}
- {{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
-
-
-
- {{% endif %}}
- {{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
-
-
-
- {{% endif %}}
- """
-
- def __init__(self, model, *args, buttons=None, 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,
- buttons=buttons
- )
-
- super().__init__(template_code=template_code, *args, **kwargs)
-
- # Exclude from export by default
- if 'exclude_from_export' not in kwargs:
- self.exclude_from_export = True
-
- self.extra_context.update({
- 'buttons': buttons or self.buttons,
- })
-
- def header(self):
- return ''
-
-
class ChoiceFieldColumn(tables.Column):
"""
Render a ChoiceField value inside a indicating a particular CSS class. This is useful for displaying colored
diff --git a/netbox/utilities/tests/test_tables.py b/netbox/utilities/tests/test_tables.py
index 119587ff8..55a5e4cc7 100644
--- a/netbox/utilities/tests/test_tables.py
+++ b/netbox/utilities/tests/test_tables.py
@@ -30,7 +30,8 @@ class TagColumnTest(TestCase):
def test_tagcolumn(self):
template = Template('{% load render_table from django_tables2 %}{% render_table table %}')
+ table = TagColumnTable(Site.objects.all(), orderable=False)
context = Context({
- 'table': TagColumnTable(Site.objects.all(), orderable=False)
+ 'table': table
})
template.render(context)
diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py
index 65f9f1257..0588f51a5 100644
--- a/netbox/virtualization/tables.py
+++ b/netbox/virtualization/tables.py
@@ -3,7 +3,7 @@ import django_tables2 as tables
from dcim.tables.devices import BaseInterfaceTable
from tenancy.tables import TenantColumn
from utilities.tables import (
- BaseTable, ButtonsColumn, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
+ ActionsColumn, BaseTable, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
ToggleColumn,
)
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -183,10 +183,9 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
bridge = tables.Column(
linkify=True
)
- actions = ButtonsColumn(
- model=VMInterface,
- buttons=('edit', 'delete'),
- prepend_template=VMINTERFACE_BUTTONS
+ actions = ActionsColumn(
+ sequence=('edit', 'delete'),
+ extra_buttons=VMINTERFACE_BUTTONS
)
class Meta(BaseTable.Meta):