Merge pull request #4555 from netbox-community/492-table-column-ordering

Closes #492: Table column ordering
This commit is contained in:
Jeremy Stretch 2020-05-04 15:12:29 -04:00 committed by GitHub
commit 80f08e6830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 751 additions and 201 deletions

View File

@ -8,3 +8,4 @@ The `users.UserConfig` model holds individual preferences for each user in the f
| ---- | ----------- |
| extras.configcontext.format | Preferred format when rendering config context data (JSON or YAML) |
| pagination.per_page | The number of items to display per page of a paginated table |
| tables.${table_name}.columns | The ordered list of columns to display when viewing the table |

View File

@ -27,18 +27,15 @@ STATUS_LABEL = """
class ProviderTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
circuit_count = tables.Column(
accessor=Accessor('count_circuits'),
verbose_name='Circuits'
)
class Meta(BaseTable.Meta):
model = Provider
fields = ('pk', 'name', 'asn', 'account',)
class ProviderDetailTable(ProviderTable):
circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
class Meta(ProviderTable.Meta):
model = Provider
fields = ('pk', 'name', 'asn', 'account', 'circuit_count')
fields = ('pk', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count')
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
#
@ -58,6 +55,7 @@ class CircuitTypeTable(BaseTable):
class Meta(BaseTable.Meta):
model = CircuitType
fields = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
#
@ -79,4 +77,8 @@ class CircuitTable(BaseTable):
class Meta(BaseTable.Meta):
model = Circuit
fields = ('pk', 'cid', 'status', 'type', 'provider', 'tenant', 'a_side', 'z_side', 'description')
fields = (
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'install_date', 'commit_rate',
'description',
)
default_columns = ('pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'description')

View File

@ -28,7 +28,7 @@ class ProviderListView(PermissionRequiredMixin, ObjectListView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
filterset = filters.ProviderFilterSet
filterset_form = forms.ProviderFilterForm
table = tables.ProviderDetailTable
table = tables.ProviderTable
class ProviderView(PermissionRequiredMixin, View):
@ -87,7 +87,7 @@ class ProviderBulkImportView(PermissionRequiredMixin, BulkImportView):
class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'circuits.change_provider'
queryset = Provider.objects.all()
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
filterset = filters.ProviderFilterSet
table = tables.ProviderTable
form = forms.ProviderBulkEditForm
@ -96,7 +96,7 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_provider'
queryset = Provider.objects.all()
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
filterset = filters.ProviderFilterSet
table = tables.ProviderTable
default_return_url = 'circuits:provider_list'

View File

@ -205,9 +205,13 @@ def get_component_template_actions(model_name):
class RegionTable(BaseTable):
pk = ToggleColumn()
name = tables.TemplateColumn(template_code=MPTT_LINK, orderable=False)
site_count = tables.Column(verbose_name='Sites')
slug = tables.Column(verbose_name='Slug')
name = tables.TemplateColumn(
template_code=MPTT_LINK,
orderable=False
)
site_count = tables.Column(
verbose_name='Sites'
)
actions = tables.TemplateColumn(
template_code=REGION_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -216,7 +220,8 @@ class RegionTable(BaseTable):
class Meta(BaseTable.Meta):
model = Region
fields = ('pk', 'name', 'site_count', 'description', 'slug', 'actions')
fields = ('pk', 'name', 'slug', 'site_count', 'description', 'actions')
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
#
@ -225,14 +230,27 @@ class RegionTable(BaseTable):
class SiteTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(order_by=('_name',))
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
tenant = tables.TemplateColumn(template_code=COL_TENANT)
name = tables.LinkColumn(
order_by=('_name',)
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
region = tables.TemplateColumn(
template_code=SITE_REGION_LINK
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
class Meta(BaseTable.Meta):
model = Site
fields = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn', 'description')
fields = (
'pk', 'name', 'slug', 'status', 'facility', 'region', 'tenant', 'asn', 'time_zone', 'description',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email',
)
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn', 'description')
#
@ -253,7 +271,6 @@ class RackGroupTable(BaseTable):
rack_count = tables.Column(
verbose_name='Racks'
)
slug = tables.Column()
actions = tables.TemplateColumn(
template_code=RACKGROUP_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -263,6 +280,7 @@ class RackGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = RackGroup
fields = ('pk', 'name', 'site', 'rack_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'site', 'rack_count', 'description', 'actions')
#
@ -282,6 +300,7 @@ class RackRoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = RackRole
fields = ('pk', 'name', 'rack_count', 'color', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'rack_count', 'color', 'description', 'actions')
#
@ -290,17 +309,34 @@ class RackRoleTable(BaseTable):
class RackTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(order_by=('_name',))
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(STATUS_LABEL)
role = tables.TemplateColumn(RACK_ROLE)
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
name = tables.LinkColumn(
order_by=('_name',)
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
role = tables.TemplateColumn(
template_code=RACK_ROLE
)
u_height = tables.TemplateColumn(
template_code="{{ record.u_height }}U",
verbose_name='Height'
)
class Meta(BaseTable.Meta):
model = Rack
fields = ('pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height')
fields = (
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
'width', 'u_height',
)
default_columns = ('pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height')
class RackDetailTable(RackTable):
@ -321,6 +357,10 @@ class RackDetailTable(RackTable):
class Meta(RackTable.Meta):
fields = (
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
'width', 'u_height', 'device_count', 'get_utilization', 'get_power_utilization',
)
default_columns = (
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
'get_utilization', 'get_power_utilization',
)
@ -364,6 +404,9 @@ class RackReservationTable(BaseTable):
fields = (
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'actions',
)
default_columns = (
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description', 'actions',
)
#
@ -416,9 +459,12 @@ class DeviceTypeTable(BaseTable):
class Meta(BaseTable.Meta):
model = DeviceType
fields = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'instance_count',
)
default_columns = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',
)
#
@ -427,7 +473,9 @@ class DeviceTypeTable(BaseTable):
class ConsolePortTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('consoleporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -441,7 +489,10 @@ class ConsolePortTemplateTable(BaseTable):
class ConsolePortImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = ConsolePort
@ -451,7 +502,9 @@ class ConsolePortImportTable(BaseTable):
class ConsoleServerPortTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('consoleserverporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -465,7 +518,10 @@ class ConsoleServerPortTemplateTable(BaseTable):
class ConsoleServerPortImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = ConsoleServerPort
@ -475,7 +531,9 @@ class ConsoleServerPortImportTable(BaseTable):
class PowerPortTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('powerporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -489,7 +547,10 @@ class PowerPortTemplateTable(BaseTable):
class PowerPortImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = PowerPort
@ -499,7 +560,9 @@ class PowerPortImportTable(BaseTable):
class PowerOutletTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('poweroutlettemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -513,7 +576,10 @@ class PowerOutletTemplateTable(BaseTable):
class PowerOutletImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = PowerOutlet
@ -523,7 +589,9 @@ class PowerOutletImportTable(BaseTable):
class InterfaceTemplateTable(BaseTable):
pk = ToggleColumn()
mgmt_only = tables.TemplateColumn("{% if value %}OOB Management{% endif %}")
mgmt_only = tables.TemplateColumn(
template_code="{% if value %}OOB Management{% endif %}"
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('interfacetemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -537,18 +605,30 @@ class InterfaceTemplateTable(BaseTable):
class InterfaceImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
virtual_machine = tables.LinkColumn('virtualization:virtualmachine', args=[Accessor('virtual_machine.pk')], verbose_name='Virtual Machine')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
virtual_machine = tables.LinkColumn(
viewname='virtualization:virtualmachine',
args=[Accessor('virtual_machine.pk')],
verbose_name='Virtual Machine'
)
class Meta(BaseTable.Meta):
model = Interface
fields = ('device', 'virtual_machine', 'name', 'description', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'mode')
fields = (
'device', 'virtual_machine', 'name', 'description', 'lag', 'type', 'enabled', 'mac_address', 'mtu',
'mgmt_only', 'mode',
)
empty_text = False
class FrontPortTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
rear_port_position = tables.Column(
verbose_name='Position'
)
@ -565,7 +645,10 @@ class FrontPortTemplateTable(BaseTable):
class FrontPortImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = FrontPort
@ -575,7 +658,9 @@ class FrontPortImportTable(BaseTable):
class RearPortTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('rearporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -589,7 +674,10 @@ class RearPortTemplateTable(BaseTable):
class RearPortImportTable(BaseTable):
device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device')
device = tables.LinkColumn(
viewname='dcim:device',
args=[Accessor('device.pk')]
)
class Meta(BaseTable.Meta):
model = RearPort
@ -599,7 +687,9 @@ class RearPortImportTable(BaseTable):
class DeviceBayTemplateTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(order_by=('_name',))
name = tables.Column(
order_by=('_name',)
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('devicebaytemplate'),
attrs={'td': {'class': 'text-right noprint'}},
@ -630,8 +720,10 @@ class DeviceRoleTable(BaseTable):
orderable=False,
verbose_name='VMs'
)
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Label')
slug = tables.Column(verbose_name='Slug')
color = tables.TemplateColumn(
template_code=COLOR_LABEL,
verbose_name='Label'
)
actions = tables.TemplateColumn(
template_code=DEVICEROLE_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -641,6 +733,7 @@ class DeviceRoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = DeviceRole
fields = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'actions')
#
@ -670,7 +763,11 @@ class PlatformTable(BaseTable):
class Meta(BaseTable.Meta):
model = Platform
fields = (
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'description', 'actions',
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
'description', 'actions',
)
default_columns = (
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'napalm_driver', 'description', 'actions',
)
@ -684,40 +781,96 @@ class DeviceTable(BaseTable):
order_by=('_name',),
template_code=DEVICE_LINK
)
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
rack = tables.LinkColumn(
viewname='dcim:rack',
args=[Accessor('rack.pk')]
)
device_role = tables.TemplateColumn(
template_code=DEVICE_ROLE,
verbose_name='Role'
)
device_type = tables.LinkColumn(
'dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
viewname='dcim:devicetype',
args=[Accessor('device_type.pk')],
verbose_name='Type',
text=lambda record: record.device_type.display_name
)
primary_ip = tables.TemplateColumn(
template_code=DEVICE_PRIMARY_IP,
orderable=False,
verbose_name='IP Address'
)
primary_ip4 = tables.LinkColumn(
viewname='ipam:ipaddress',
args=[Accessor('primary_ip4.pk')],
verbose_name='IPv4 Address'
)
primary_ip6 = tables.LinkColumn(
viewname='ipam:ipaddress',
args=[Accessor('primary_ip6.pk')],
verbose_name='IPv6 Address'
)
cluster = tables.LinkColumn(
viewname='virtualization:cluster',
args=[Accessor('cluster.pk')]
)
virtual_chassis = tables.LinkColumn(
viewname='dcim:virtualchassis',
args=[Accessor('virtual_chassis.pk')]
)
vc_position = tables.Column(
verbose_name='VC Position'
)
vc_priority = tables.Column(
verbose_name='VC Priority'
)
class Meta(BaseTable.Meta):
model = Device
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type')
class DeviceDetailTable(DeviceTable):
primary_ip = tables.TemplateColumn(
orderable=False, verbose_name='IP Address', template_code=DEVICE_PRIMARY_IP
)
class Meta(DeviceTable.Meta):
model = Device
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
fields = (
'pk', 'name', 'status', 'tenant', 'device_role', 'device_type', 'platform', 'serial', 'asset_tag', 'site',
'rack', 'position', 'face', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis',
'vc_position', 'vc_priority',
)
default_columns = (
'pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip',
)
class DeviceImportTable(BaseTable):
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
position = tables.Column(verbose_name='Position')
device_role = tables.Column(verbose_name='Role')
device_type = tables.Column(verbose_name='Type')
name = tables.TemplateColumn(
template_code=DEVICE_LINK
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
rack = tables.LinkColumn(
viewname='dcim:rack',
args=[Accessor('rack.pk')]
)
device_role = tables.Column(
verbose_name='Role'
)
device_type = tables.Column(
verbose_name='Type'
)
class Meta(BaseTable.Meta):
model = Device
@ -893,23 +1046,23 @@ class CableTable(BaseTable):
template_code=CABLE_TERMINATION_PARENT,
accessor=Accessor('termination_a'),
orderable=False,
verbose_name='Termination A'
verbose_name='Side A'
)
termination_a = tables.LinkColumn(
accessor=Accessor('termination_a'),
orderable=False,
verbose_name=''
verbose_name='Termination A'
)
termination_b_parent = tables.TemplateColumn(
template_code=CABLE_TERMINATION_PARENT,
accessor=Accessor('termination_b'),
orderable=False,
verbose_name='Termination B'
verbose_name='Side B'
)
termination_b = tables.LinkColumn(
accessor=Accessor('termination_b'),
orderable=False,
verbose_name=''
verbose_name='Termination B'
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
@ -926,6 +1079,10 @@ class CableTable(BaseTable):
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
'status', 'type', 'color', 'length',
)
default_columns = (
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
'status', 'type',
)
#
@ -993,10 +1150,6 @@ class InterfaceConnectionTable(BaseTable):
args=[Accessor('pk')],
verbose_name='Interface A'
)
description_a = tables.Column(
accessor=Accessor('description'),
verbose_name='Description'
)
device_b = tables.LinkColumn(
viewname='dcim:device',
accessor=Accessor('_connected_interface.device'),
@ -1009,15 +1162,11 @@ class InterfaceConnectionTable(BaseTable):
args=[Accessor('_connected_interface.pk')],
verbose_name='Interface B'
)
description_b = tables.Column(
accessor=Accessor('_connected_interface.description'),
verbose_name='Description'
)
class Meta(BaseTable.Meta):
model = Interface
fields = (
'device_a', 'interface_a', 'description_a', 'device_b', 'interface_b', 'description_b', 'connection_status',
'device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status',
)
@ -1027,12 +1176,21 @@ class InterfaceConnectionTable(BaseTable):
class InventoryItemTable(BaseTable):
pk = ToggleColumn()
device = tables.LinkColumn('dcim:device_inventory', args=[Accessor('device.pk')])
manufacturer = tables.Column(accessor=Accessor('manufacturer.name'), verbose_name='Manufacturer')
device = tables.LinkColumn(
viewname='dcim:device_inventory',
args=[Accessor('device.pk')]
)
manufacturer = tables.Column(
accessor=Accessor('manufacturer.name')
)
discovered = BooleanColumn()
class Meta(BaseTable.Meta):
model = InventoryItem
fields = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description')
fields = (
'pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered'
)
default_columns = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag')
#
@ -1052,6 +1210,7 @@ class VirtualChassisTable(BaseTable):
class Meta(BaseTable.Meta):
model = VirtualChassis
fields = ('pk', 'name', 'domain', 'member_count')
default_columns = ('pk', 'name', 'domain', 'member_count')
#
@ -1073,6 +1232,7 @@ class PowerPanelTable(BaseTable):
class Meta(BaseTable.Meta):
model = PowerPanel
fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
default_columns = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
#
@ -1096,7 +1256,19 @@ class PowerFeedTable(BaseTable):
type = tables.TemplateColumn(
template_code=TYPE_LABEL
)
max_utilization = tables.TemplateColumn(
template_code="{{ value }}%"
)
available_power = tables.Column(
verbose_name='Available power (VA)'
)
class Meta(BaseTable.Meta):
model = PowerFeed
fields = ('pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase')
fields = (
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
'max_utilization', 'available_power',
)
default_columns = (
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
)

View File

@ -1095,7 +1095,7 @@ class DeviceListView(PermissionRequiredMixin, ObjectListView):
)
filterset = filters.DeviceFilterSet
filterset_form = forms.DeviceFilterForm
table = tables.DeviceDetailTable
table = tables.DeviceTable
template_name = 'dcim/device_list.html'
@ -2278,19 +2278,15 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
csv_data = [
# Headers
','.join([
'device_a', 'interface_a', 'interface_a_description',
'device_b', 'interface_b', 'interface_b_description',
'connection_status'
'device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'
])
]
for obj in self.queryset:
csv = csv_format([
obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
obj.connected_endpoint.name if obj.connected_endpoint else None,
obj.connected_endpoint.description if obj.connected_endpoint else None,
obj.device.identifier,
obj.name,
obj.description,
obj.get_connection_status_display(),
])
csv_data.append(csv)

View File

@ -104,7 +104,11 @@ class ConfigContextTable(BaseTable):
class Meta(BaseTable.Meta):
model = ConfigContext
fields = ('pk', 'name', 'weight', 'is_active', 'description')
fields = (
'pk', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles', 'platforms',
'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
)
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
class ObjectChangeTable(BaseTable):

View File

@ -190,12 +190,20 @@ TENANT_LINK = """
class VRFTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
rd = tables.Column(verbose_name='RD')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
rd = tables.Column(
verbose_name='RD'
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
enforce_unique = BooleanColumn(
verbose_name='Unique'
)
class Meta(BaseTable.Meta):
model = VRF
fields = ('pk', 'name', 'rd', 'tenant', 'description')
fields = ('pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description')
default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
#
@ -204,14 +212,23 @@ class VRFTable(BaseTable):
class RIRTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
is_private = BooleanColumn(verbose_name='Private')
aggregate_count = tables.Column(verbose_name='Aggregates')
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
name = tables.LinkColumn()
is_private = BooleanColumn(
verbose_name='Private'
)
aggregate_count = tables.Column(
verbose_name='Aggregates'
)
actions = tables.TemplateColumn(
template_code=RIR_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
)
class Meta(BaseTable.Meta):
model = RIR
fields = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
fields = ('pk', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'actions')
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
class RIRDetailTable(RIRTable):
@ -247,6 +264,10 @@ class RIRDetailTable(RIRTable):
class Meta(RIRTable.Meta):
fields = (
'pk', 'name', 'slug', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved',
'stats_deprecated', 'stats_available', 'utilization', 'actions',
)
default_columns = (
'pk', 'name', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved',
'stats_deprecated', 'stats_available', 'utilization', 'actions',
)
@ -258,8 +279,13 @@ class RIRDetailTable(RIRTable):
class AggregateTable(BaseTable):
pk = ToggleColumn()
prefix = tables.LinkColumn(verbose_name='Aggregate')
date_added = tables.DateColumn(format="Y-m-d", verbose_name='Added')
prefix = tables.LinkColumn(
verbose_name='Aggregate'
)
date_added = tables.DateColumn(
format="Y-m-d",
verbose_name='Added'
)
class Meta(BaseTable.Meta):
model = Aggregate
@ -267,8 +293,13 @@ class AggregateTable(BaseTable):
class AggregateDetailTable(AggregateTable):
child_count = tables.Column(verbose_name='Prefixes')
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
child_count = tables.Column(
verbose_name='Prefixes'
)
utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH,
orderable=False
)
class Meta(AggregateTable.Meta):
fields = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description')
@ -300,7 +331,8 @@ class RoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = Role
fields = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'slug', 'weight', 'actions')
fields = ('pk', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'actions')
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions')
#
@ -309,28 +341,61 @@ class RoleTable(BaseTable):
class PrefixTable(BaseTable):
pk = ToggleColumn()
prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
status = tables.TemplateColumn(STATUS_LABEL)
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
role = tables.TemplateColumn(PREFIX_ROLE_LINK)
prefix = tables.TemplateColumn(
template_code=PREFIX_LINK,
attrs={'th': {'style': 'padding-left: 17px'}}
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
vrf = tables.TemplateColumn(
template_code=VRF_LINK,
verbose_name='VRF'
)
tenant = tables.TemplateColumn(
template_code=TENANT_LINK
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
vlan = tables.LinkColumn(
viewname='ipam:vlan',
args=[Accessor('vlan.pk')],
verbose_name='VLAN'
)
role = tables.TemplateColumn(
template_code=PREFIX_ROLE_LINK
)
is_pool = BooleanColumn(
verbose_name='Pool'
)
class Meta(BaseTable.Meta):
model = Prefix
fields = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
fields = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'description')
default_columns = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
row_attrs = {
'class': lambda record: 'success' if not record.pk else '',
}
class PrefixDetailTable(PrefixTable):
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
tenant = tables.TemplateColumn(template_code=COL_TENANT)
utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH,
orderable=False
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
class Meta(PrefixTable.Meta):
fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
fields = (
'pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'description',
)
default_columns = (
'pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
)
#
@ -339,12 +404,27 @@ class PrefixDetailTable(PrefixTable):
class IPAddressTable(BaseTable):
pk = ToggleColumn()
address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
status = tables.TemplateColumn(STATUS_LABEL)
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
interface = tables.Column(orderable=False)
address = tables.TemplateColumn(
template_code=IPADDRESS_LINK,
verbose_name='IP Address'
)
vrf = tables.TemplateColumn(
template_code=VRF_LINK,
verbose_name='VRF'
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
tenant = tables.TemplateColumn(
template_code=TENANT_LINK
)
parent = tables.TemplateColumn(
template_code=IPADDRESS_PARENT,
orderable=False
)
interface = tables.Column(
orderable=False
)
class Meta(BaseTable.Meta):
model = IPAddress
@ -358,22 +438,40 @@ class IPAddressTable(BaseTable):
class IPAddressDetailTable(IPAddressTable):
nat_inside = tables.LinkColumn(
'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
viewname='ipam:ipaddress',
args=[Accessor('nat_inside.pk')],
orderable=False,
verbose_name='NAT (Inside)'
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
tenant = tables.TemplateColumn(template_code=COL_TENANT)
class Meta(IPAddressTable.Meta):
fields = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'dns_name',
'description',
)
default_columns = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
)
class IPAddressAssignTable(BaseTable):
address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address')
status = tables.TemplateColumn(STATUS_LABEL)
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
interface = tables.Column(orderable=False)
address = tables.TemplateColumn(
template_code=IPADDRESS_ASSIGN_LINK,
verbose_name='IP Address'
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
parent = tables.TemplateColumn(
template_code=IPADDRESS_PARENT,
orderable=False
)
interface = tables.Column(
orderable=False
)
class Meta(BaseTable.Meta):
model = IPAddress
@ -385,10 +483,19 @@ class InterfaceIPAddressTable(BaseTable):
"""
List IP addresses assigned to a specific Interface.
"""
address = tables.LinkColumn(verbose_name='IP Address')
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
status = tables.TemplateColumn(STATUS_LABEL)
tenant = tables.TemplateColumn(template_code=TENANT_LINK)
address = tables.LinkColumn(
verbose_name='IP Address'
)
vrf = tables.TemplateColumn(
template_code=VRF_LINK,
verbose_name='VRF'
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
tenant = tables.TemplateColumn(
template_code=TENANT_LINK
)
class Meta(BaseTable.Meta):
model = IPAddress
@ -401,16 +508,24 @@ class InterfaceIPAddressTable(BaseTable):
class VLANGroupTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
vlan_count = tables.Column(verbose_name='VLANs')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
verbose_name='')
name = tables.LinkColumn()
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
vlan_count = tables.Column(
verbose_name='VLANs'
)
actions = tables.TemplateColumn(
template_code=VLANGROUP_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
)
class Meta(BaseTable.Meta):
model = VLANGroup
fields = ('pk', 'name', 'site', 'vlan_count', 'slug', 'description', 'actions')
default_columns = ('pk', 'name', 'site', 'vlan_count', 'description', 'actions')
#
@ -419,12 +534,27 @@ class VLANGroupTable(BaseTable):
class VLANTable(BaseTable):
pk = ToggleColumn()
vid = tables.TemplateColumn(VLAN_LINK, verbose_name='ID')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.LinkColumn('ipam:vlangroup_vlans', args=[Accessor('group.pk')], verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(STATUS_LABEL)
role = tables.TemplateColumn(VLAN_ROLE_LINK)
vid = tables.TemplateColumn(
template_code=VLAN_LINK,
verbose_name='ID'
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
group = tables.LinkColumn(
viewname='ipam:vlangroup_vlans',
args=[Accessor('group.pk')]
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
role = tables.TemplateColumn(
template_code=VLAN_ROLE_LINK
)
class Meta(BaseTable.Meta):
model = VLAN
@ -435,16 +565,26 @@ class VLANTable(BaseTable):
class VLANDetailTable(VLANTable):
prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
prefixes = tables.TemplateColumn(
template_code=VLAN_PREFIXES,
orderable=False,
verbose_name='Prefixes'
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
class Meta(VLANTable.Meta):
fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
class VLANMemberTable(BaseTable):
parent = tables.LinkColumn(order_by=['device', 'virtual_machine'])
name = tables.LinkColumn(verbose_name='Interface')
parent = tables.LinkColumn(
order_by=['device', 'virtual_machine']
)
name = tables.LinkColumn(
verbose_name='Interface'
)
untagged = tables.TemplateColumn(
template_code=VLAN_MEMBER_UNTAGGED,
orderable=False
@ -464,13 +604,29 @@ class InterfaceVLANTable(BaseTable):
"""
List VLANs assigned to a specific Interface.
"""
vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
vid = tables.LinkColumn(
viewname='ipam:vlan',
args=[Accessor('pk')],
verbose_name='ID'
)
tagged = BooleanColumn()
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(STATUS_LABEL)
role = tables.TemplateColumn(VLAN_ROLE_LINK)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
group = tables.Column(
accessor=Accessor('group.name'),
verbose_name='Group'
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
status = tables.TemplateColumn(
template_code=STATUS_LABEL
)
role = tables.TemplateColumn(
template_code=VLAN_ROLE_LINK
)
class Meta(BaseTable.Meta):
model = VLAN
@ -494,4 +650,5 @@ class ServiceTable(BaseTable):
class Meta(BaseTable.Meta):
model = Service
fields = ('pk', 'name', 'parent', 'protocol', 'port', 'description')
fields = ('pk', 'name', 'parent', 'protocol', 'port', 'ipaddresses', 'description')
default_columns = ('pk', 'name', 'parent', 'protocol', 'port', 'description')

View File

@ -20,7 +20,7 @@ from dcim.models import (
Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, RackGroup, Site, VirtualChassis
)
from dcim.tables import (
CableTable, DeviceDetailTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
VirtualChassisTable,
)
from extras.models import ObjectChange, ReportResult
@ -44,7 +44,7 @@ SEARCH_TYPES = OrderedDict((
# Circuits
('provider', {
'permission': 'circuits.view_provider',
'queryset': Provider.objects.all(),
'queryset': Provider.objects.annotate(count_circuits=Count('circuits')),
'filterset': ProviderFilterSet,
'table': ProviderTable,
'url': 'circuits:provider_list',
@ -93,7 +93,7 @@ SEARCH_TYPES = OrderedDict((
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6',
),
'filterset': DeviceFilterSet,
'table': DeviceDetailTable,
'table': DeviceTable,
'url': 'dcim:device_list',
}),
('virtualchassis', {

View File

@ -448,4 +448,33 @@ $(document).ready(function() {
$('a.image-preview').on('mouseout', function() {
$('#image-preview-window').fadeOut('fast');
});
// Rearrange options within a <select> list
$('#move-option-up').bind('click', function() {
var select_id = '#' + $(this).attr('data-target');
$(select_id + ' option:selected').each(function () {
var newPos = $(select_id + ' option').index(this) - 1;
if (newPos > -1) {
$(select_id + ' option').eq(newPos).before("<option value='" + $(this).val() + "' selected='selected'>" + $(this).text() + "</option>");
$(this).remove();
}
});
});
$('#move-option-down').bind('click', function() {
var select_id = '#' + $(this).attr('data-target');
var countOptions = $(select_id + ' option').length;
var countSelectedOptions = $(select_id + ' option:selected').length;
$(select_id + ' option:selected').each(function () {
var newPos = $(select_id + ' option').index(this) + countSelectedOptions;
if (newPos < countOptions) {
$(select_id + ' option').eq(newPos).after("<option value='" + $(this).val() + "' selected='selected'>" + $(this).text() + "</option>");
$(this).remove();
}
});
});
$('#select-all-options').bind('click', function() {
var select_id = '#' + $(this).attr('data-target');
$(select_id + ' option').prop('selected',true);
});
});

View File

@ -20,14 +20,19 @@ SECRETROLE_ACTIONS = """
class SecretRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
secret_count = tables.Column(verbose_name='Secrets')
secret_count = tables.Column(
verbose_name='Secrets'
)
actions = tables.TemplateColumn(
template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
template_code=SECRETROLE_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
)
class Meta(BaseTable.Meta):
model = SecretRole
fields = ('pk', 'name', 'secret_count', 'description', 'slug', 'actions')
fields = ('pk', 'name', 'secret_count', 'description', 'slug', 'users', 'groups', 'actions')
default_columns = ('pk', 'name', 'secret_count', 'description', 'actions')
#
@ -40,4 +45,5 @@ class SecretTable(BaseTable):
class Meta(BaseTable.Meta):
model = Secret
fields = ('pk', 'device', 'role', 'name', 'last_updated')
fields = ('pk', 'device', 'role', 'name', 'last_updated', 'hash')
default_columns = ('pk', 'device', 'role', 'name', 'last_updated')

View File

@ -0,0 +1,28 @@
{% load form_helpers %}
<div class="modal fade" tabindex="-1" id="tableconfig">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Table Configuration</h4>
</div>
<div class="modal-body">
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% render_form table_config_form %}
<div class="row">
<div class="col-md-9 col-md-offset-3">
<a class="btn btn-primary btn-xs" id="move-option-up" data-target="id_columns"><i class="fa fa-arrow-up"></i> Move up</a>
<a class="btn btn-primary btn-xs" id="move-option-down" data-target="id_columns"><i class="fa fa-arrow-down"></i> Move down</a>
<a class="btn btn-success btn-xs" id="select-all-options" data-target="id_columns"><i class="fa fa-ellipsis-v"></i> Select all</a>
</div>
</div>
<div class="text-right">
<input type="submit" class="btn btn-primary" name="set" value="Save" />
<input type="submit" class="btn btn-danger" name="clear" value="Reset" />
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -5,6 +5,9 @@
{% block content %}
<div class="pull-right noprint">
{% block buttons %}{% endblock %}
{% if table_config_form %}
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#tableconfig" title="Configure table"><i class="fa fa-cog"></i> Configure</button>
{% endif %}
{% if permissions.add and 'add' in action_buttons %}
{% add_button content_type.model_class|url_name:"add" %}
{% endif %}
@ -68,6 +71,9 @@
{% endwith %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
<div class="clearfix"></div>
{% if table_config_form %}
{% include 'inc/table_config_form.html' %}
{% endif %}
</div>
{% if filter_form %}
<div class="col-md-3 noprint">

View File

@ -44,7 +44,6 @@ class TenantGroupTable(BaseTable):
tenant_count = tables.Column(
verbose_name='Tenants'
)
slug = tables.Column()
actions = tables.TemplateColumn(
template_code=TENANTGROUP_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -54,6 +53,7 @@ class TenantGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = TenantGroup
fields = ('pk', 'name', 'tenant_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions')
#
@ -66,4 +66,5 @@ class TenantTable(BaseTable):
class Meta(BaseTable.Meta):
model = Tenant
fields = ('pk', 'name', 'group', 'description')
fields = ('pk', 'name', 'slug', 'group', 'description')
default_columns = ('pk', 'name', 'group', 'description')

View File

@ -108,7 +108,7 @@ class UserConfig(models.Model):
userconfig.clear('foo.bar.baz')
A KeyError is raised in the event any key along the path does not exist.
Invalid keys will be ignored silently.
:param path: Dotted path to the configuration key. For example, 'foo.bar' deletes self.data['foo']['bar'].
:param commit: If true, the UserConfig instance will be saved once the new value has been applied.
@ -117,11 +117,13 @@ class UserConfig(models.Model):
keys = path.split('.')
for key in keys[:-1]:
if key in d and type(d[key]) is dict:
if key not in d:
break
if type(d[key]) is dict:
d = d[key]
key = keys[-1]
del(d[key])
d.pop(key, None) # Avoid a KeyError on invalid keys
if commit:
self.save()

View File

View File

@ -104,6 +104,5 @@ class UserConfigTest(TestCase):
self.assertTrue('foo' not in userconfig.data['b'])
self.assertEqual(userconfig.data['b']['bar'], 102)
# Clear an invalid value
with self.assertRaises(KeyError):
userconfig.clear('invalid')
# Clear a non-existing value; should fail silently
userconfig.clear('invalid')

View File

@ -665,7 +665,10 @@ class BootstrapMixin(forms.BaseForm):
super().__init__(*args, **kwargs)
exempt_widgets = [
forms.CheckboxInput, forms.ClearableFileInput, forms.FileInput, forms.RadioSelect
forms.CheckboxInput,
forms.ClearableFileInput,
forms.FileInput,
forms.RadioSelect
]
for field_name, field in self.fields.items():
@ -752,3 +755,23 @@ class ImportForm(BootstrapMixin, forms.Form):
raise forms.ValidationError({
'data': "Invalid YAML data: {}".format(err)
})
class TableConfigForm(BootstrapMixin, forms.Form):
"""
Form for configuring user's table preferences.
"""
columns = forms.MultipleChoiceField(
choices=[],
widget=forms.SelectMultiple(
attrs={'size': 10}
),
help_text="Use the buttons below to arrange columns in the desired order, then select all columns to display."
)
def __init__(self, table, *args, **kwargs):
super().__init__(*args, **kwargs)
# Initialize columns field based on table attributes
self.fields['columns'].choices = table.configurable_columns
self.fields['columns'].initial = table.visible_columns

View File

@ -1,4 +1,7 @@
import django_tables2 as tables
from django.core.exceptions import FieldDoesNotExist
from django.db.models import ForeignKey
from django_tables2.data import TableQuerysetData
from django.utils.safestring import mark_safe
@ -6,17 +9,73 @@ class BaseTable(tables.Table):
"""
Default table for object lists
"""
def __init__(self, *args, **kwargs):
class Meta:
attrs = {
'class': 'table table-hover table-headings',
}
def __init__(self, *args, columns=None, **kwargs):
super().__init__(*args, **kwargs)
# Set default empty_text if none was provided
if self.empty_text is None:
self.empty_text = 'No {} found'.format(self._meta.model._meta.verbose_name_plural)
class Meta:
attrs = {
'class': 'table table-hover table-headings',
}
# 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
if columns is not None:
pk = self.base_columns.pop('pk', None)
actions = self.base_columns.pop('actions', None)
for name, column in self.base_columns.items():
if name in columns:
self.columns.show(name)
else:
self.columns.hide(name)
self.sequence = columns
# Always include PK and actions column, if defined on the table
if pk:
self.base_columns['pk'] = pk
self.sequence.insert(0, 'pk')
if actions:
self.base_columns['actions'] = actions
self.sequence.append('actions')
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
if isinstance(self.data, TableQuerysetData):
model = getattr(self.Meta, 'model')
prefetch_fields = []
for column in self.columns:
if column.visible:
field_path = column.accessor.split('.')
try:
model_field = model._meta.get_field(field_path[0])
if isinstance(model_field, ForeignKey):
prefetch_fields.append('__'.join(field_path))
except FieldDoesNotExist:
pass
self.data.data = self.data.data.prefetch_related(None).prefetch_related(*prefetch_fields)
@property
def configurable_columns(self):
selected_columns = [
(name, self.columns[name].verbose_name) for name in self.sequence if name not in ['pk', 'actions']
]
available_columns = [
(name, column.verbose_name) for name, column in self.columns.items() if name not in self.sequence and name not in ['pk', 'actions']
]
return selected_columns + available_columns
@property
def visible_columns(self):
return [name for name in self.sequence if self.columns[name].visible]
class ToggleColumn(tables.CheckBoxColumn):

View File

@ -24,7 +24,7 @@ from django_tables2 import RequestConfig
from extras.models import CustomField, CustomFieldValue, ExportTemplate
from extras.querysets import CustomFieldQueryset
from utilities.exceptions import AbortTransaction
from utilities.forms import BootstrapMixin, CSVDataField
from utilities.forms import BootstrapMixin, CSVDataField, TableConfigForm
from utilities.utils import csv_format, prepare_cloned_fields
from .error_handlers import handle_protectederror
from .forms import ConfirmationForm, ImportForm
@ -164,7 +164,8 @@ class ObjectListView(View):
permissions[action] = request.user.has_perm(perm_name)
# Construct the table based on the user's permissions
table = self.table(self.queryset)
columns = request.user.config.get(f"tables.{self.table.__name__}.columns")
table = self.table(self.queryset, columns=columns)
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
table.columns.show('pk')
@ -180,12 +181,29 @@ class ObjectListView(View):
'table': table,
'permissions': permissions,
'action_buttons': self.action_buttons,
'table_config_form': TableConfigForm(table=table),
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
}
context.update(self.extra_context())
return render(request, self.template_name, context)
def post(self, request):
# Update the user's table configuration
table = self.table(self.queryset)
form = TableConfigForm(table=table, data=request.POST)
preference_name = f"tables.{self.table.__name__}.columns"
if form.is_valid():
if 'set' in request.POST:
request.user.config.set(preference_name, form.cleaned_data['columns'], commit=True)
elif 'clear' in request.POST:
request.user.config.clear(preference_name, commit=True)
messages.success(request, "Your preferences have been updated.")
return redirect(request.get_full_path())
def alter_queryset(self, request):
# .all() is necessary to avoid caching queries
return self.queryset.all()

View File

@ -46,7 +46,9 @@ VIRTUALMACHINE_PRIMARY_IP = """
class ClusterTypeTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
cluster_count = tables.Column(verbose_name='Clusters')
cluster_count = tables.Column(
verbose_name='Clusters'
)
actions = tables.TemplateColumn(
template_code=CLUSTERTYPE_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -55,7 +57,8 @@ class ClusterTypeTable(BaseTable):
class Meta(BaseTable.Meta):
model = ClusterType
fields = ('pk', 'name', 'cluster_count', 'description', 'actions')
fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'actions')
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
#
@ -65,7 +68,9 @@ class ClusterTypeTable(BaseTable):
class ClusterGroupTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
cluster_count = tables.Column(verbose_name='Clusters')
cluster_count = tables.Column(
verbose_name='Clusters'
)
actions = tables.TemplateColumn(
template_code=CLUSTERGROUP_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
@ -74,7 +79,8 @@ class ClusterGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = ClusterGroup
fields = ('pk', 'name', 'cluster_count', 'description', 'actions')
fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'actions')
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
#
@ -84,10 +90,24 @@ class ClusterGroupTable(BaseTable):
class ClusterTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
device_count = tables.Column(accessor=Accessor('devices.count'), orderable=False, verbose_name='Devices')
vm_count = tables.Column(accessor=Accessor('virtual_machines.count'), orderable=False, verbose_name='VMs')
tenant = tables.LinkColumn(
viewname='tenancy:tenant',
args=[Accessor('tenant.slug')]
)
site = tables.LinkColumn(
viewname='dcim:site',
args=[Accessor('site.slug')]
)
device_count = tables.Column(
accessor=Accessor('devices.count'),
orderable=False,
verbose_name='Devices'
)
vm_count = tables.Column(
accessor=Accessor('virtual_machines.count'),
orderable=False,
verbose_name='VMs'
)
class Meta(BaseTable.Meta):
model = Cluster
@ -101,10 +121,19 @@ class ClusterTable(BaseTable):
class VirtualMachineTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
role = tables.TemplateColumn(VIRTUALMACHINE_ROLE)
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(
template_code=VIRTUALMACHINE_STATUS
)
cluster = tables.LinkColumn(
viewname='virtualization:cluster',
args=[Accessor('cluster.pk')]
)
role = tables.TemplateColumn(
template_code=VIRTUALMACHINE_ROLE
)
tenant = tables.TemplateColumn(
template_code=COL_TENANT
)
class Meta(BaseTable.Meta):
model = VirtualMachine
@ -112,13 +141,31 @@ class VirtualMachineTable(BaseTable):
class VirtualMachineDetailTable(VirtualMachineTable):
primary_ip4 = tables.LinkColumn(
viewname='ipam:ipaddress',
args=[Accessor('primary_ip4.pk')],
verbose_name='IPv4 Address'
)
primary_ip6 = tables.LinkColumn(
viewname='ipam:ipaddress',
args=[Accessor('primary_ip6.pk')],
verbose_name='IPv6 Address'
)
primary_ip = tables.TemplateColumn(
orderable=False, verbose_name='IP Address', template_code=VIRTUALMACHINE_PRIMARY_IP
orderable=False,
verbose_name='IP Address',
template_code=VIRTUALMACHINE_PRIMARY_IP
)
class Meta(BaseTable.Meta):
model = VirtualMachine
fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip')
fields = (
'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4',
'primary_ip6', 'primary_ip',
)
default_columns = (
'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',
)
#