mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge pull request #8303 from netbox-community/7679-table-actions
Closes #7679: Object table actions menus
This commit is contained in:
commit
17aa37ae21
@ -57,6 +57,7 @@ Inventory item templates can be arranged hierarchically within a device type, an
|
|||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#7650](https://github.com/netbox-community/netbox/issues/7650) - Add support for local account password validation
|
* [#7650](https://github.com/netbox-community/netbox/issues/7650) - Add support for local account password validation
|
||||||
|
* [#7679](https://github.com/netbox-community/netbox/issues/7679) - Add actions menu to all object tables
|
||||||
* [#7681](https://github.com/netbox-community/netbox/issues/7681) - Add `service_id` field for provider networks
|
* [#7681](https://github.com/netbox-community/netbox/issues/7681) - Add `service_id` field for provider networks
|
||||||
* [#7759](https://github.com/netbox-community/netbox/issues/7759) - Improved the user preferences form
|
* [#7759](https://github.com/netbox-community/netbox/issues/7759) - Improved the user preferences form
|
||||||
* [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts
|
* [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts
|
||||||
|
@ -2,7 +2,7 @@ import django_tables2 as tables
|
|||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, MarkdownColumn, TagColumn, ToggleColumn
|
from utilities.tables import BaseTable, ChoiceFieldColumn, MarkdownColumn, TagColumn, ToggleColumn
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
@ -88,12 +88,11 @@ class CircuitTypeTable(BaseTable):
|
|||||||
circuit_count = tables.Column(
|
circuit_count = tables.Column(
|
||||||
verbose_name='Circuits'
|
verbose_name='Circuits'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(CircuitType)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
|
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -7,7 +7,7 @@ from dcim.models import (
|
|||||||
)
|
)
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
|
ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
|
||||||
MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn,
|
MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn,
|
||||||
)
|
)
|
||||||
from .template_code import *
|
from .template_code import *
|
||||||
@ -94,7 +94,6 @@ class DeviceRoleTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:devicerole_list'
|
url_name='dcim:devicerole_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(DeviceRole)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
@ -102,7 +101,7 @@ class DeviceRoleTable(BaseTable):
|
|||||||
'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
|
'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
|
||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'actions')
|
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -127,7 +126,6 @@ class PlatformTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:platform_list'
|
url_name='dcim:platform_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(Platform)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Platform
|
model = Platform
|
||||||
@ -136,7 +134,7 @@ class PlatformTable(BaseTable):
|
|||||||
'description', 'tags', 'actions',
|
'description', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'napalm_driver', 'description', 'actions',
|
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'napalm_driver', 'description',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -324,10 +322,8 @@ class DeviceConsolePortTable(ConsolePortTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ConsolePort,
|
extra_buttons=CONSOLEPORT_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=CONSOLEPORT_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -336,7 +332,7 @@ class DeviceConsolePortTable(ConsolePortTable):
|
|||||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions'
|
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions'
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
}
|
}
|
||||||
@ -369,10 +365,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ConsoleServerPort,
|
extra_buttons=CONSOLESERVERPORT_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=CONSOLESERVERPORT_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -381,7 +375,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
|||||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
}
|
}
|
||||||
@ -414,10 +408,8 @@ class DevicePowerPortTable(PowerPortTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=PowerPort,
|
extra_buttons=POWERPORT_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=POWERPORT_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -428,7 +420,6 @@ class DevicePowerPortTable(PowerPortTable):
|
|||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
|
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
|
||||||
'actions',
|
|
||||||
)
|
)
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
@ -464,10 +455,8 @@ class DevicePowerOutletTable(PowerOutletTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=PowerOutlet,
|
extra_buttons=POWEROUTLET_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=POWEROUTLET_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -477,7 +466,7 @@ class DevicePowerOutletTable(PowerOutletTable):
|
|||||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions',
|
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection',
|
||||||
)
|
)
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
@ -557,10 +546,8 @@ class DeviceInterfaceTable(InterfaceTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name='LAG'
|
verbose_name='LAG'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=Interface,
|
extra_buttons=INTERFACE_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=INTERFACE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -575,7 +562,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
|||||||
order_by = ('name',)
|
order_by = ('name',)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
||||||
'cable', 'connection', 'actions',
|
'cable', 'connection',
|
||||||
)
|
)
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_interface_row_class,
|
'class': get_interface_row_class,
|
||||||
@ -620,10 +607,8 @@ class DeviceFrontPortTable(FrontPortTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=FrontPort,
|
extra_buttons=FRONTPORT_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=FRONTPORT_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -634,7 +619,6 @@ class DeviceFrontPortTable(FrontPortTable):
|
|||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer',
|
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer',
|
||||||
'actions',
|
|
||||||
)
|
)
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
@ -669,10 +653,8 @@ class DeviceRearPortTable(RearPortTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=RearPort,
|
extra_buttons=REARPORT_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=REARPORT_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -682,7 +664,7 @@ class DeviceRearPortTable(RearPortTable):
|
|||||||
'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer', 'actions',
|
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer',
|
||||||
)
|
)
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': get_cabletermination_row_class
|
'class': get_cabletermination_row_class
|
||||||
@ -720,10 +702,8 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=DeviceBay,
|
extra_buttons=DEVICEBAY_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=DEVICEBAY_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
@ -731,9 +711,7 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
|||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = ('pk', 'name', 'label', 'status', 'installed_device', 'description')
|
||||||
'pk', 'name', 'label', 'status', 'installed_device', 'description', 'actions',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTable(DeviceComponentTable):
|
class ModuleBayTable(DeviceComponentTable):
|
||||||
@ -758,16 +736,14 @@ class ModuleBayTable(DeviceComponentTable):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceModuleBayTable(ModuleBayTable):
|
class DeviceModuleBayTable(ModuleBayTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=DeviceBay,
|
extra_buttons=MODULEBAY_BUTTONS
|
||||||
buttons=('edit', 'delete'),
|
|
||||||
prepend_template=MODULEBAY_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = ('pk', 'id', 'name', 'label', 'description', 'installed_module', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'label', 'description', 'installed_module', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'label', 'description', 'installed_module', 'actions')
|
default_columns = ('pk', 'name', 'label', 'description', 'installed_module')
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTable(DeviceComponentTable):
|
class InventoryItemTable(DeviceComponentTable):
|
||||||
@ -812,10 +788,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
|||||||
order_by=Accessor('_name'),
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn()
|
||||||
model=InventoryItem,
|
|
||||||
buttons=('edit', 'delete')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
@ -824,7 +797,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
|||||||
'description', 'discovered', 'tags', 'actions',
|
'description', 'discovered', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component', 'actions',
|
'pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -842,14 +815,13 @@ class InventoryItemRoleTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:inventoryitemrole_list'
|
url_name='dcim:inventoryitemrole_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(InventoryItemRole)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = InventoryItemRole
|
model = InventoryItemRole
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
|
'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'inventoryitem_count', 'color', 'description', 'actions')
|
default_columns = ('pk', 'name', 'inventoryitem_count', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -6,7 +6,7 @@ from dcim.models import (
|
|||||||
InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||||
)
|
)
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
|
ActionsColumn, BaseTable, BooleanColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
|
||||||
)
|
)
|
||||||
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
|
|
||||||
@ -48,7 +48,6 @@ class ManufacturerTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:manufacturer_list'
|
url_name='dcim:manufacturer_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(Manufacturer)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
@ -57,7 +56,7 @@ class ManufacturerTable(BaseTable):
|
|||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', 'actions',
|
'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -113,10 +112,9 @@ class ComponentTemplateTable(BaseTable):
|
|||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateTable(ComponentTemplateTable):
|
class ConsolePortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ConsolePortTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -126,10 +124,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ConsoleServerPortTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -139,10 +136,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateTable(ComponentTemplateTable):
|
class PowerPortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=PowerPortTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -152,10 +148,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateTable(ComponentTemplateTable):
|
class PowerOutletTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=PowerOutletTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -168,10 +163,9 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
|||||||
mgmt_only = BooleanColumn(
|
mgmt_only = BooleanColumn(
|
||||||
verbose_name='Management Only'
|
verbose_name='Management Only'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=InterfaceTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -185,10 +179,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
|
|||||||
verbose_name='Position'
|
verbose_name='Position'
|
||||||
)
|
)
|
||||||
color = ColorColumn()
|
color = ColorColumn()
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=FrontPortTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -199,10 +192,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
class RearPortTemplateTable(ComponentTemplateTable):
|
class RearPortTemplateTable(ComponentTemplateTable):
|
||||||
color = ColorColumn()
|
color = ColorColumn()
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=RearPortTemplate,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||||
prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -212,9 +204,8 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateTable(ComponentTemplateTable):
|
class ModuleBayTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ModuleBayTemplate,
|
sequence=('edit', 'delete')
|
||||||
buttons=('edit', 'delete')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -224,9 +215,8 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateTable(ComponentTemplateTable):
|
class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=DeviceBayTemplate,
|
sequence=('edit', 'delete')
|
||||||
buttons=('edit', 'delete')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -236,9 +226,8 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
|
|
||||||
class InventoryItemTemplateTable(ComponentTemplateTable):
|
class InventoryItemTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=InventoryItemTemplate,
|
sequence=('edit', 'delete')
|
||||||
buttons=('edit', 'delete')
|
|
||||||
)
|
)
|
||||||
role = tables.Column(
|
role = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
|
@ -4,8 +4,8 @@ from django_tables2.utils import Accessor
|
|||||||
from dcim.models import Rack, RackReservation, RackRole
|
from dcim.models import Rack, RackReservation, RackRole
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn,
|
BaseTable, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
|
||||||
TagColumn, ToggleColumn, UtilizationColumn,
|
ToggleColumn, UtilizationColumn,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -27,12 +27,11 @@ class RackRoleTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:rackrole_list'
|
url_name='dcim:rackrole_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(RackRole)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ('pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'rack_count', 'color', 'description', 'actions')
|
default_columns = ('pk', 'name', 'rack_count', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -121,7 +120,6 @@ class RackReservationTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:rackreservation_list'
|
url_name='dcim:rackreservation_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(RackReservation)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
@ -129,6 +127,4 @@ class RackReservationTable(BaseTable):
|
|||||||
'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
|
'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
|
||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description')
|
||||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description', 'actions',
|
|
||||||
)
|
|
||||||
|
@ -3,9 +3,9 @@ import django_tables2 as tables
|
|||||||
from dcim.models import Location, Region, Site, SiteGroup
|
from dcim.models import Location, Region, Site, SiteGroup
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
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__ = (
|
__all__ = (
|
||||||
'LocationTable',
|
'LocationTable',
|
||||||
@ -32,12 +32,11 @@ class RegionTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:region_list'
|
url_name='dcim:region_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(Region)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Region
|
model = Region
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -57,12 +56,11 @@ class SiteGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:sitegroup_list'
|
url_name='dcim:sitegroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(SiteGroup)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -98,6 +96,7 @@ class SiteTable(BaseTable):
|
|||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn_count', 'time_zone',
|
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn_count', 'time_zone',
|
||||||
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
|
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
|
||||||
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'description')
|
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'description')
|
||||||
|
|
||||||
@ -128,9 +127,8 @@ class LocationTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='dcim:location_list'
|
url_name='dcim:location_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=Location,
|
extra_buttons=LOCATION_BUTTONS
|
||||||
prepend_template=LOCATION_ELEVATIONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -139,4 +137,4 @@ class LocationTable(BaseTable):
|
|||||||
'pk', 'id', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'tags',
|
'pk', 'id', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'tags',
|
||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description')
|
||||||
|
@ -87,7 +87,7 @@ POWERFEED_CABLETERMINATION = """
|
|||||||
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOCATION_ELEVATIONS = """
|
LOCATION_BUTTONS = """
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&location_id={{ record.pk }}" class="btn btn-sm btn-primary" title="View elevations">
|
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&location_id={{ record.pk }}" class="btn btn-sm btn-primary" title="View elevations">
|
||||||
<i class="mdi mdi-server"></i>
|
<i class="mdi mdi-server"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -99,8 +99,8 @@ LOCATION_ELEVATIONS = """
|
|||||||
|
|
||||||
MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
|
MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% if perms.dcim.add_invnetoryitemtemplate %}
|
{% if perms.dcim.add_inventoryitemtemplate %}
|
||||||
<a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type.pk }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -2,7 +2,7 @@ import django_tables2 as tables
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ContentTypeColumn, ContentTypesColumn,
|
ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ColorColumn, ContentTypeColumn, ContentTypesColumn,
|
||||||
MarkdownColumn, ToggleColumn,
|
MarkdownColumn, ToggleColumn,
|
||||||
)
|
)
|
||||||
from .models import *
|
from .models import *
|
||||||
@ -152,12 +152,11 @@ class TagTable(BaseTable):
|
|||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
color = ColorColumn()
|
color = ColorColumn()
|
||||||
actions = ButtonsColumn(Tag)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
||||||
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
class TaggedItemTable(BaseTable):
|
class TaggedItemTable(BaseTable):
|
||||||
@ -215,6 +214,7 @@ class ObjectChangeTable(BaseTable):
|
|||||||
template_code=OBJECTCHANGE_REQUEST_ID,
|
template_code=OBJECTCHANGE_REQUEST_ID,
|
||||||
verbose_name='Request ID'
|
verbose_name='Request ID'
|
||||||
)
|
)
|
||||||
|
actions = ActionsColumn(sequence=())
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ObjectChange
|
model = ObjectChange
|
||||||
@ -233,9 +233,6 @@ class ObjectJournalTable(BaseTable):
|
|||||||
comments = tables.TemplateColumn(
|
comments = tables.TemplateColumn(
|
||||||
template_code='{% load helpers %}{{ value|render_markdown|truncatewords_html:50 }}'
|
template_code='{% load helpers %}{{ value|render_markdown|truncatewords_html:50 }}'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
|
||||||
model=JournalEntry
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
@ -261,6 +258,5 @@ class JournalEntryTable(ObjectJournalTable):
|
|||||||
'comments', 'actions'
|
'comments', 'actions'
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind',
|
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
|
||||||
'comments', 'actions'
|
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from utilities.tables import BaseTable, ButtonsColumn, MarkdownColumn, TagColumn, ToggleColumn
|
from utilities.tables import ActionsColumn, BaseTable, MarkdownColumn, TagColumn, ToggleColumn
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -58,9 +58,8 @@ class FHRPGroupAssignmentTable(BaseTable):
|
|||||||
group = tables.Column(
|
group = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=FHRPGroupAssignment,
|
sequence=('edit', 'delete')
|
||||||
buttons=('edit', 'delete', 'foo')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
|
@ -2,12 +2,11 @@ import django_tables2 as tables
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
|
from ipam.models import *
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, TagColumn,
|
BaseTable, BooleanColumn, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn, UtilizationColumn,
|
||||||
ToggleColumn, UtilizationColumn,
|
|
||||||
)
|
)
|
||||||
from ipam.models import *
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateTable',
|
'AggregateTable',
|
||||||
@ -89,12 +88,11 @@ class RIRTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='ipam:rir_list'
|
url_name='ipam:rir_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(RIR)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -111,12 +109,11 @@ class ASNTable(BaseTable):
|
|||||||
url_params={'asn_id': 'pk'},
|
url_params={'asn_id': 'pk'},
|
||||||
verbose_name='Sites'
|
verbose_name='Sites'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(ASN)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ASN
|
model = ASN
|
||||||
fields = ('pk', 'asn', 'rir', 'site_count', 'tenant', 'description', 'actions')
|
fields = ('pk', 'asn', 'rir', 'site_count', 'tenant', 'description', 'actions')
|
||||||
default_columns = ('pk', 'asn', 'rir', 'site_count', 'sites', 'tenant', 'actions')
|
default_columns = ('pk', 'asn', 'rir', 'site_count', 'sites', 'tenant')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -173,12 +170,11 @@ class RoleTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='ipam:role_list'
|
url_name='ipam:role_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(Role)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Role
|
model = Role
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -405,9 +401,6 @@ class AssignedIPAddressesTable(BaseTable):
|
|||||||
)
|
)
|
||||||
status = ChoiceFieldColumn()
|
status = ChoiceFieldColumn()
|
||||||
tenant = TenantColumn()
|
tenant = TenantColumn()
|
||||||
actions = ButtonsColumn(
|
|
||||||
model=IPAddress
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
@ -5,7 +5,7 @@ from django_tables2.utils import Accessor
|
|||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
|
ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
|
||||||
TemplateColumn, ToggleColumn,
|
TemplateColumn, ToggleColumn,
|
||||||
)
|
)
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VMInterface
|
||||||
@ -38,7 +38,7 @@ VLAN_PREFIXES = """
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VLANGROUP_ADD_VLAN = """
|
VLANGROUP_BUTTONS = """
|
||||||
{% with next_vid=record.get_next_available_vid %}
|
{% with next_vid=record.get_next_available_vid %}
|
||||||
{% if next_vid and perms.ipam.add_vlan %}
|
{% if next_vid and perms.ipam.add_vlan %}
|
||||||
<a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
|
<a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
|
||||||
@ -77,9 +77,8 @@ class VLANGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='ipam:vlangroup_list'
|
url_name='ipam:vlangroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=VLANGroup,
|
extra_buttons=VLANGROUP_BUTTONS
|
||||||
prepend_template=VLANGROUP_ADD_VLAN
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -88,7 +87,7 @@ class VLANGroupTable(BaseTable):
|
|||||||
'pk', 'id', 'name', 'scope_type', 'scope', 'min_vid', 'max_vid', 'vlan_count', 'slug', 'description',
|
'pk', 'id', 'name', 'scope_type', 'scope', 'min_vid', 'max_vid', 'vlan_count', 'slug', 'description',
|
||||||
'tags', 'actions',
|
'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -153,7 +152,9 @@ class VLANDevicesTable(VLANMembersTable):
|
|||||||
device = tables.Column(
|
device = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(Interface, buttons=['edit'])
|
actions = ActionsColumn(
|
||||||
|
sequence=('edit',)
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Interface
|
model = Interface
|
||||||
@ -165,7 +166,9 @@ class VLANVirtualMachinesTable(VLANMembersTable):
|
|||||||
virtual_machine = tables.Column(
|
virtual_machine = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(VMInterface, buttons=['edit'])
|
actions = ActionsColumn(
|
||||||
|
sequence=('edit',)
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
|
@ -203,7 +203,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|||||||
:param table: The Table instance to export
|
:param table: The Table instance to export
|
||||||
:param columns: A list of specific columns to include. If not specified, all columns will be exported.
|
:param columns: A list of specific columns to include. If not specified, all columns will be exported.
|
||||||
"""
|
"""
|
||||||
exclude_columns = {'pk'}
|
exclude_columns = {'pk', 'actions'}
|
||||||
if columns:
|
if columns:
|
||||||
all_columns = [col_name for col_name, _ in table.selected_columns + table.available_columns]
|
all_columns = [col_name for col_name, _ in table.selected_columns + table.available_columns]
|
||||||
exclude_columns.update({
|
exclude_columns.update({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, ButtonsColumn, ContentTypeColumn, LinkedCountColumn, linkify_phone, MarkdownColumn, MPTTColumn,
|
ActionsColumn, BaseTable, ContentTypeColumn, LinkedCountColumn, linkify_phone, MarkdownColumn, MPTTColumn,
|
||||||
TagColumn, ToggleColumn,
|
TagColumn, ToggleColumn,
|
||||||
)
|
)
|
||||||
from .models import *
|
from .models import *
|
||||||
@ -59,12 +59,11 @@ class TenantGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='tenancy:tenantgroup_list'
|
url_name='tenancy:tenantgroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(TenantGroup)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = ('pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'tenant_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
class TenantTable(BaseTable):
|
class TenantTable(BaseTable):
|
||||||
@ -103,12 +102,11 @@ class ContactGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='tenancy:contactgroup_list'
|
url_name='tenancy:contactgroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(ContactGroup)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = ('pk', 'name', 'contact_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'name', 'contact_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'contact_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'contact_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ContactRoleTable(BaseTable):
|
class ContactRoleTable(BaseTable):
|
||||||
@ -116,12 +114,11 @@ class ContactRoleTable(BaseTable):
|
|||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(ContactRole)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ContactRole
|
model = ContactRole
|
||||||
fields = ('pk', 'name', 'description', 'slug', 'actions')
|
fields = ('pk', 'name', 'description', 'slug', 'actions')
|
||||||
default_columns = ('pk', 'name', 'description', 'actions')
|
default_columns = ('pk', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ContactTable(BaseTable):
|
class ContactTable(BaseTable):
|
||||||
@ -164,12 +161,11 @@ class ContactAssignmentTable(BaseTable):
|
|||||||
role = tables.Column(
|
role = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=ContactAssignment,
|
sequence=('edit', 'delete')
|
||||||
buttons=('edit', 'delete')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ContactAssignment
|
model = ContactAssignment
|
||||||
fields = ('pk', 'content_type', 'object', 'contact', 'role', 'priority', 'actions')
|
fields = ('pk', 'content_type', 'object', 'contact', 'role', 'priority', 'actions')
|
||||||
default_columns = ('pk', 'content_type', 'object', 'contact', 'role', 'priority', 'actions')
|
default_columns = ('pk', 'content_type', 'object', 'contact', 'role', 'priority')
|
||||||
|
30
netbox/utilities/tables/__init__.py
Normal file
30
netbox/utilities/tables/__init__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
from .columns import *
|
||||||
|
from .tables import *
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Pagination
|
||||||
|
#
|
||||||
|
|
||||||
|
def paginate_table(table, request):
|
||||||
|
"""
|
||||||
|
Paginate a table given a request context.
|
||||||
|
"""
|
||||||
|
paginate = {
|
||||||
|
'paginator_class': EnhancedPaginator,
|
||||||
|
'per_page': get_paginate_count(request)
|
||||||
|
}
|
||||||
|
RequestConfig(request, paginate).configure(table)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Callables
|
||||||
|
#
|
||||||
|
|
||||||
|
def linkify_phone(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return f"tel:{value}"
|
@ -1,149 +1,36 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.template import Context, Template
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
|
||||||
from django.db.models.fields.related import RelatedField
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django_tables2 import RequestConfig
|
|
||||||
from django_tables2.data import TableQuerysetData
|
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
from extras.choices import CustomFieldTypeChoices
|
from extras.choices import CustomFieldTypeChoices
|
||||||
from extras.models import CustomField, CustomLink
|
from utilities.utils import content_type_identifier, content_type_name
|
||||||
from .utils import content_type_identifier, content_type_name
|
|
||||||
from .paginator import EnhancedPaginator, get_paginate_count
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'ActionsColumn',
|
||||||
|
'BooleanColumn',
|
||||||
|
'ChoiceFieldColumn',
|
||||||
|
'ColorColumn',
|
||||||
|
'ColoredLabelColumn',
|
||||||
|
'ContentTypeColumn',
|
||||||
|
'ContentTypesColumn',
|
||||||
|
'CustomFieldColumn',
|
||||||
|
'CustomLinkColumn',
|
||||||
|
'LinkedCountColumn',
|
||||||
|
'MarkdownColumn',
|
||||||
|
'MPTTColumn',
|
||||||
|
'TagColumn',
|
||||||
|
'TemplateColumn',
|
||||||
|
'ToggleColumn',
|
||||||
|
'UtilizationColumn',
|
||||||
|
)
|
||||||
|
|
||||||
class BaseTable(tables.Table):
|
|
||||||
"""
|
|
||||||
Default table for object lists
|
|
||||||
|
|
||||||
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
|
||||||
"""
|
|
||||||
id = tables.Column(
|
|
||||||
linkify=True,
|
|
||||||
verbose_name='ID'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover object-list',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, user=None, extra_columns=None, **kwargs):
|
|
||||||
if extra_columns is None:
|
|
||||||
extra_columns = []
|
|
||||||
|
|
||||||
# Add custom field columns
|
|
||||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
|
||||||
cf_columns = [
|
|
||||||
(f'cf_{cf.name}', CustomFieldColumn(cf)) for cf in CustomField.objects.filter(content_types=obj_type)
|
|
||||||
]
|
|
||||||
cl_columns = [
|
|
||||||
(f'cl_{cl.name}', CustomLinkColumn(cl)) for cl in CustomLink.objects.filter(content_type=obj_type)
|
|
||||||
]
|
|
||||||
extra_columns.extend([*cf_columns, *cl_columns])
|
|
||||||
|
|
||||||
super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
|
||||||
|
|
||||||
# Set default empty_text if none was provided
|
|
||||||
if self.empty_text is None:
|
|
||||||
self.empty_text = f"No {self._meta.model._meta.verbose_name_plural} found"
|
|
||||||
|
|
||||||
# Hide non-default columns
|
|
||||||
default_columns = getattr(self.Meta, 'default_columns', list())
|
|
||||||
if default_columns:
|
|
||||||
for column in self.columns:
|
|
||||||
if column.name not in default_columns:
|
|
||||||
self.columns.hide(column.name)
|
|
||||||
|
|
||||||
# Apply custom column ordering for user
|
|
||||||
if user is not None and not isinstance(user, AnonymousUser):
|
|
||||||
selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
|
||||||
if selected_columns:
|
|
||||||
|
|
||||||
# Show only persistent or selected columns
|
|
||||||
for name, column in self.columns.items():
|
|
||||||
if name in ['pk', 'actions', *selected_columns]:
|
|
||||||
self.columns.show(name)
|
|
||||||
else:
|
|
||||||
self.columns.hide(name)
|
|
||||||
|
|
||||||
# Rearrange the sequence to list selected columns first, followed by all remaining columns
|
|
||||||
# TODO: There's probably a more clever way to accomplish this
|
|
||||||
self.sequence = [
|
|
||||||
*[c for c in selected_columns if c in self.columns.names()],
|
|
||||||
*[c for c in self.columns.names() if c not in selected_columns]
|
|
||||||
]
|
|
||||||
|
|
||||||
# PK column should always come first
|
|
||||||
if 'pk' in self.sequence:
|
|
||||||
self.sequence.remove('pk')
|
|
||||||
self.sequence.insert(0, 'pk')
|
|
||||||
|
|
||||||
# Actions column should always come last
|
|
||||||
if 'actions' in self.sequence:
|
|
||||||
self.sequence.remove('actions')
|
|
||||||
self.sequence.append('actions')
|
|
||||||
|
|
||||||
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
|
|
||||||
if isinstance(self.data, TableQuerysetData):
|
|
||||||
|
|
||||||
prefetch_fields = []
|
|
||||||
for column in self.columns:
|
|
||||||
if column.visible:
|
|
||||||
model = getattr(self.Meta, 'model')
|
|
||||||
accessor = column.accessor
|
|
||||||
prefetch_path = []
|
|
||||||
for field_name in accessor.split(accessor.SEPARATOR):
|
|
||||||
try:
|
|
||||||
field = model._meta.get_field(field_name)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
break
|
|
||||||
if isinstance(field, RelatedField):
|
|
||||||
# Follow ForeignKeys to the related model
|
|
||||||
prefetch_path.append(field_name)
|
|
||||||
model = field.remote_field.model
|
|
||||||
elif isinstance(field, GenericForeignKey):
|
|
||||||
# Can't prefetch beyond a GenericForeignKey
|
|
||||||
prefetch_path.append(field_name)
|
|
||||||
break
|
|
||||||
if prefetch_path:
|
|
||||||
prefetch_fields.append('__'.join(prefetch_path))
|
|
||||||
self.data.data = self.data.data.prefetch_related(None).prefetch_related(*prefetch_fields)
|
|
||||||
|
|
||||||
def _get_columns(self, visible=True):
|
|
||||||
columns = []
|
|
||||||
for name, column in self.columns.items():
|
|
||||||
if column.visible == visible and name not in ['pk', 'actions']:
|
|
||||||
columns.append((name, column.verbose_name))
|
|
||||||
return columns
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available_columns(self):
|
|
||||||
return self._get_columns(visible=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def selected_columns(self):
|
|
||||||
return self._get_columns(visible=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def objects_count(self):
|
|
||||||
"""
|
|
||||||
Return the total number of real objects represented by the Table. This is useful when dealing with
|
|
||||||
prefixes/IP addresses/etc., where some table rows may represent available address space.
|
|
||||||
"""
|
|
||||||
if not hasattr(self, '_objects_count'):
|
|
||||||
self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
|
|
||||||
return self._objects_count
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Table columns
|
|
||||||
#
|
|
||||||
|
|
||||||
class ToggleColumn(tables.CheckBoxColumn):
|
class ToggleColumn(tables.CheckBoxColumn):
|
||||||
"""
|
"""
|
||||||
@ -205,59 +92,78 @@ class TemplateColumn(tables.TemplateColumn):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class ButtonsColumn(tables.TemplateColumn):
|
@dataclass
|
||||||
"""
|
class ActionsItem:
|
||||||
Render edit, delete, and changelog buttons for an object.
|
title: str
|
||||||
|
icon: str
|
||||||
|
permission: Optional[str] = None
|
||||||
|
|
||||||
:param model: Model class to use for calculating URL view names
|
|
||||||
:param prepend_content: Additional template content to render in the column (optional)
|
class ActionsColumn(tables.Column):
|
||||||
|
"""
|
||||||
|
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
|
||||||
"""
|
"""
|
||||||
buttons = ('changelog', 'edit', 'delete')
|
|
||||||
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
|
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
|
||||||
# Note that braces are escaped to allow for string formatting prior to template rendering
|
empty_values = ()
|
||||||
template_code = """
|
actions = {
|
||||||
{{% if "changelog" in buttons %}}
|
'edit': ActionsItem('Edit', 'pencil', 'change'),
|
||||||
<a href="{{% url '{app_label}:{model_name}_changelog' pk=record.pk %}}" class="btn btn-outline-dark btn-sm" title="Change log">
|
'delete': ActionsItem('Delete', 'trash-can-outline', 'delete'),
|
||||||
<i class="mdi mdi-history"></i>
|
'changelog': ActionsItem('Changelog', 'history'),
|
||||||
</a>
|
}
|
||||||
{{% endif %}}
|
|
||||||
{{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
|
|
||||||
<a href="{{% url '{app_label}:{model_name}_edit' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-warning" title="Edit">
|
|
||||||
<i class="mdi mdi-pencil"></i>
|
|
||||||
</a>
|
|
||||||
{{% endif %}}
|
|
||||||
{{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
|
|
||||||
<a href="{{% url '{app_label}:{model_name}_delete' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-danger" title="Delete">
|
|
||||||
<i class="mdi mdi-trash-can-outline"></i>
|
|
||||||
</a>
|
|
||||||
{{% endif %}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, model, *args, buttons=None, prepend_template=None, **kwargs):
|
def __init__(self, *args, sequence=('edit', 'delete', 'changelog'), extra_buttons='', **kwargs):
|
||||||
if prepend_template:
|
super().__init__(*args, **kwargs)
|
||||||
prepend_template = prepend_template.replace('{', '{{')
|
|
||||||
prepend_template = prepend_template.replace('}', '}}')
|
|
||||||
self.template_code = prepend_template + self.template_code
|
|
||||||
|
|
||||||
template_code = self.template_code.format(
|
self.extra_buttons = extra_buttons
|
||||||
app_label=model._meta.app_label,
|
|
||||||
model_name=model._meta.model_name,
|
|
||||||
buttons=buttons
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(template_code=template_code, *args, **kwargs)
|
# Determine which actions to enable
|
||||||
|
self.actions = {
|
||||||
# Exclude from export by default
|
name: self.actions[name] for name in sequence
|
||||||
if 'exclude_from_export' not in kwargs:
|
}
|
||||||
self.exclude_from_export = True
|
|
||||||
|
|
||||||
self.extra_context.update({
|
|
||||||
'buttons': buttons or self.buttons,
|
|
||||||
})
|
|
||||||
|
|
||||||
def header(self):
|
def header(self):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def render(self, record, table, **kwargs):
|
||||||
|
# Skip dummy records (e.g. available VLANs) or those with no actions
|
||||||
|
if not hasattr(record, 'pk') or not self.actions:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
model = table.Meta.model
|
||||||
|
viewname_base = f'{model._meta.app_label}:{model._meta.model_name}'
|
||||||
|
request = getattr(table, 'context', {}).get('request')
|
||||||
|
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 user.has_perm(permission):
|
||||||
|
url = reverse(f'{viewname_base}_{action}', kwargs={'pk': record.pk})
|
||||||
|
links.append(f'<li><a class="dropdown-item" href="{url}{url_appendix}">'
|
||||||
|
f'<i class="mdi mdi-{attrs.icon}"></i> {attrs.title}</a></li>')
|
||||||
|
|
||||||
|
if not links:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
menu = f'<span class="dropdown">' \
|
||||||
|
f'<a class="btn btn-sm btn-secondary dropdown-toggle" href="#" type="button" data-bs-toggle="dropdown">' \
|
||||||
|
f'<i class="mdi mdi-wrench"></i></a>' \
|
||||||
|
f'<ul class="dropdown-menu">{"".join(links)}</ul></span>'
|
||||||
|
|
||||||
|
# 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 ChoiceFieldColumn(tables.Column):
|
class ChoiceFieldColumn(tables.Column):
|
||||||
"""
|
"""
|
||||||
@ -509,34 +415,3 @@ class MarkdownColumn(tables.TemplateColumn):
|
|||||||
|
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Pagination
|
|
||||||
#
|
|
||||||
|
|
||||||
def paginate_table(table, request):
|
|
||||||
"""
|
|
||||||
Paginate a table given a request context.
|
|
||||||
"""
|
|
||||||
paginate = {
|
|
||||||
'paginator_class': EnhancedPaginator,
|
|
||||||
'per_page': get_paginate_count(request)
|
|
||||||
}
|
|
||||||
RequestConfig(request, paginate).configure(table)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Callables
|
|
||||||
#
|
|
||||||
|
|
||||||
def linkify_email(value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
return f"mailto:{value}"
|
|
||||||
|
|
||||||
|
|
||||||
def linkify_phone(value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
return f"tel:{value}"
|
|
138
netbox/utilities/tables/tables.py
Normal file
138
netbox/utilities/tables/tables.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
|
from django.db.models.fields.related import RelatedField
|
||||||
|
from django_tables2.data import TableQuerysetData
|
||||||
|
|
||||||
|
from extras.models import CustomField, CustomLink
|
||||||
|
from . import columns
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BaseTable',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTable(tables.Table):
|
||||||
|
"""
|
||||||
|
Default table for object lists
|
||||||
|
|
||||||
|
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
||||||
|
"""
|
||||||
|
id = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
|
actions = columns.ActionsColumn()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-hover object-list',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, user=None, extra_columns=None, **kwargs):
|
||||||
|
if extra_columns is None:
|
||||||
|
extra_columns = []
|
||||||
|
|
||||||
|
# Add custom field columns
|
||||||
|
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||||
|
cf_columns = [
|
||||||
|
(f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in CustomField.objects.filter(content_types=obj_type)
|
||||||
|
]
|
||||||
|
cl_columns = [
|
||||||
|
(f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in CustomLink.objects.filter(content_type=obj_type)
|
||||||
|
]
|
||||||
|
extra_columns.extend([*cf_columns, *cl_columns])
|
||||||
|
|
||||||
|
super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
||||||
|
|
||||||
|
# Set default empty_text if none was provided
|
||||||
|
if self.empty_text is None:
|
||||||
|
self.empty_text = f"No {self._meta.model._meta.verbose_name_plural} found"
|
||||||
|
|
||||||
|
# Hide non-default columns (except for actions)
|
||||||
|
default_columns = [*getattr(self.Meta, 'default_columns', self.Meta.fields), 'actions']
|
||||||
|
for column in self.columns:
|
||||||
|
if column.name not in default_columns:
|
||||||
|
self.columns.hide(column.name)
|
||||||
|
|
||||||
|
# Apply custom column ordering for user
|
||||||
|
if user is not None and not isinstance(user, AnonymousUser):
|
||||||
|
selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
|
||||||
|
if selected_columns:
|
||||||
|
|
||||||
|
# Show only persistent or selected columns
|
||||||
|
for name, column in self.columns.items():
|
||||||
|
if name in ['pk', 'actions', *selected_columns]:
|
||||||
|
self.columns.show(name)
|
||||||
|
else:
|
||||||
|
self.columns.hide(name)
|
||||||
|
|
||||||
|
# Rearrange the sequence to list selected columns first, followed by all remaining columns
|
||||||
|
# TODO: There's probably a more clever way to accomplish this
|
||||||
|
self.sequence = [
|
||||||
|
*[c for c in selected_columns if c in self.columns.names()],
|
||||||
|
*[c for c in self.columns.names() if c not in selected_columns]
|
||||||
|
]
|
||||||
|
|
||||||
|
# PK column should always come first
|
||||||
|
if 'pk' in self.sequence:
|
||||||
|
self.sequence.remove('pk')
|
||||||
|
self.sequence.insert(0, 'pk')
|
||||||
|
|
||||||
|
# Actions column should always come last
|
||||||
|
if 'actions' in self.sequence:
|
||||||
|
self.sequence.remove('actions')
|
||||||
|
self.sequence.append('actions')
|
||||||
|
|
||||||
|
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
|
||||||
|
if isinstance(self.data, TableQuerysetData):
|
||||||
|
|
||||||
|
prefetch_fields = []
|
||||||
|
for column in self.columns:
|
||||||
|
if column.visible:
|
||||||
|
model = getattr(self.Meta, 'model')
|
||||||
|
accessor = column.accessor
|
||||||
|
prefetch_path = []
|
||||||
|
for field_name in accessor.split(accessor.SEPARATOR):
|
||||||
|
try:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
break
|
||||||
|
if isinstance(field, RelatedField):
|
||||||
|
# Follow ForeignKeys to the related model
|
||||||
|
prefetch_path.append(field_name)
|
||||||
|
model = field.remote_field.model
|
||||||
|
elif isinstance(field, GenericForeignKey):
|
||||||
|
# Can't prefetch beyond a GenericForeignKey
|
||||||
|
prefetch_path.append(field_name)
|
||||||
|
break
|
||||||
|
if prefetch_path:
|
||||||
|
prefetch_fields.append('__'.join(prefetch_path))
|
||||||
|
self.data.data = self.data.data.prefetch_related(None).prefetch_related(*prefetch_fields)
|
||||||
|
|
||||||
|
def _get_columns(self, visible=True):
|
||||||
|
columns = []
|
||||||
|
for name, column in self.columns.items():
|
||||||
|
if column.visible == visible and name not in ['pk', 'actions']:
|
||||||
|
columns.append((name, column.verbose_name))
|
||||||
|
return columns
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_columns(self):
|
||||||
|
return self._get_columns(visible=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_columns(self):
|
||||||
|
return self._get_columns(visible=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def objects_count(self):
|
||||||
|
"""
|
||||||
|
Return the total number of real objects represented by the Table. This is useful when dealing with
|
||||||
|
prefixes/IP addresses/etc., where some table rows may represent available address space.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, '_objects_count'):
|
||||||
|
self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
|
||||||
|
return self._objects_count
|
@ -30,7 +30,8 @@ class TagColumnTest(TestCase):
|
|||||||
|
|
||||||
def test_tagcolumn(self):
|
def test_tagcolumn(self):
|
||||||
template = Template('{% load render_table from django_tables2 %}{% render_table table %}')
|
template = Template('{% load render_table from django_tables2 %}{% render_table table %}')
|
||||||
|
table = TagColumnTable(Site.objects.all(), orderable=False)
|
||||||
context = Context({
|
context = Context({
|
||||||
'table': TagColumnTable(Site.objects.all(), orderable=False)
|
'table': table
|
||||||
})
|
})
|
||||||
template.render(context)
|
template.render(context)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from dcim.tables.devices import BaseInterfaceTable
|
from dcim.tables.devices import BaseInterfaceTable
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
|
ActionsColumn, BaseTable, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
|
||||||
ToggleColumn,
|
ToggleColumn,
|
||||||
)
|
)
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
@ -40,12 +41,11 @@ class ClusterTypeTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='virtualization:clustertype_list'
|
url_name='virtualization:clustertype_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(ClusterType)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ClusterType
|
model = ClusterType
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'cluster_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -63,12 +63,11 @@ class ClusterGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='virtualization:clustergroup_list'
|
url_name='virtualization:clustergroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(ClusterGroup)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ClusterGroup
|
model = ClusterGroup
|
||||||
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'cluster_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -184,10 +183,9 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
|
|||||||
bridge = tables.Column(
|
bridge = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ActionsColumn(
|
||||||
model=VMInterface,
|
sequence=('edit', 'delete'),
|
||||||
buttons=('edit', 'delete'),
|
extra_buttons=VMINTERFACE_BUTTONS
|
||||||
prepend_template=VMINTERFACE_BUTTONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -196,9 +194,7 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
|
|||||||
'pk', 'id', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
'pk', 'id', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
||||||
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
|
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = ('pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses')
|
||||||
'pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses', 'actions',
|
|
||||||
)
|
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'data-name': lambda record: record.name,
|
'data-name': lambda record: record.name,
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from utilities.tables import (
|
from utilities.tables import BaseTable, ChoiceFieldColumn, LinkedCountColumn, MPTTColumn, TagColumn, ToggleColumn
|
||||||
BaseTable, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, MPTTColumn, TagColumn, ToggleColumn,
|
|
||||||
)
|
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -26,12 +24,11 @@ class WirelessLANGroupTable(BaseTable):
|
|||||||
tags = TagColumn(
|
tags = TagColumn(
|
||||||
url_name='wireless:wirelesslangroup_list'
|
url_name='wireless:wirelesslangroup_list'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(WirelessLANGroup)
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fields = ('pk', 'name', 'wirelesslan_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'name', 'wirelesslan_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'wirelesslan_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'wirelesslan_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANTable(BaseTable):
|
class WirelessLANTable(BaseTable):
|
||||||
|
Loading…
Reference in New Issue
Block a user