From 0dee55885b7186094e3eef5f7c32736acd356259 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 8 Nov 2018 09:51:13 -0500 Subject: [PATCH] Fixes #2567: Introduced proxy models to represent console/power/interface connections --- CHANGELOG.md | 1 + netbox/dcim/models.py | 137 +++++++++++++++++++++++++++++++++++++++--- netbox/dcim/tables.py | 59 +++++++++--------- netbox/dcim/views.py | 15 ++--- 4 files changed, 170 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77150c67f..9ddf38c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ NetBox now supports modeling physical cables for console, power, and interface c ## Bug Fixes (from v2.5-beta1) * [#2563](https://github.com/digitalocean/netbox/issues/2563) - Enable export templates for cables +* [#2567](https://github.com/digitalocean/netbox/issues/2567) - Introduced proxy models to represent console/power/interface connections ## API Changes diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index be1aca7f0..e9eb80276 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1698,7 +1698,7 @@ class ConsolePort(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() - csv_headers = ['console_server', 'connected_endpoint', 'device', 'console_port', 'connection_status'] + csv_headers = ['device', 'name'] class Meta: ordering = ['device', 'name'] @@ -1712,11 +1712,8 @@ class ConsolePort(CableTermination, ComponentModel): def to_csv(self): return ( - self.connected_endpoint.device.identifier if self.connected_endpoint else None, - self.connected_endpoint.name if self.connected_endpoint else None, self.device.identifier, self.name, - self.get_connection_status_display(), ) @@ -1740,6 +1737,8 @@ class ConsoleServerPort(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() + csv_headers = ['device', 'name'] + class Meta: unique_together = ['device', 'name'] @@ -1749,6 +1748,12 @@ class ConsoleServerPort(CableTermination, ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() + def to_csv(self): + return ( + self.device.identifier, + self.name, + ) + # # Power ports @@ -1781,7 +1786,7 @@ class PowerPort(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() - csv_headers = ['pdu', 'connected_endpoint', 'device', 'power_port', 'connection_status'] + csv_headers = ['device', 'name'] class Meta: ordering = ['device', 'name'] @@ -1795,11 +1800,8 @@ class PowerPort(CableTermination, ComponentModel): def to_csv(self): return ( - self.connected_endpoint.device.identifier if self.connected_endpoint else None, - self.connected_endpoint.name if self.connected_endpoint else None, self.device.identifier, self.name, - self.get_connection_status_display(), ) @@ -1823,6 +1825,8 @@ class PowerOutlet(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() + csv_headers = ['device', 'name'] + class Meta: unique_together = ['device', 'name'] @@ -1832,6 +1836,12 @@ class PowerOutlet(CableTermination, ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() + def to_csv(self): + return ( + self.device.identifier, + self.name, + ) + # # Interfaces @@ -1935,6 +1945,11 @@ class Interface(CableTermination, ComponentModel): objects = InterfaceManager() tags = TaggableManager() + csv_headers = [ + 'device', 'virtual_machine', 'name', 'lag', 'form_factor', 'enabled', 'mac_address', 'mtu', 'mgmt_only', + 'description', 'mode', + ] + class Meta: ordering = ['device', 'name'] unique_together = ['device', 'name'] @@ -1945,6 +1960,21 @@ class Interface(CableTermination, ComponentModel): def get_absolute_url(self): return reverse('dcim:interface', kwargs={'pk': self.pk}) + def to_csv(self): + return ( + self.device.identifier if self.device else None, + self.virtual_machine.name if self.virtual_machine else None, + self.name, + self.lag.name if self.lag else None, + self.get_form_factor_display(), + self.enabled, + self.mac_address, + self.mtu, + self.mgmt_only, + self.description, + self.get_mode_display(), + ) + def clean(self): # An Interface must belong to a Device *or* to a VirtualMachine @@ -2112,6 +2142,8 @@ class FrontPort(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() + csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position'] + class Meta: ordering = ['device', 'name'] unique_together = [ @@ -2122,6 +2154,15 @@ class FrontPort(CableTermination, ComponentModel): def __str__(self): return self.name + def to_csv(self): + return ( + self.device.identifier, + self.name, + self.get_type_display(), + self.rear_port.name, + self.rear_port_position, + ) + def clean(self): # Validate rear port assignment @@ -2162,6 +2203,8 @@ class RearPort(CableTermination, ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() + csv_headers = ['device', 'name', 'type', 'positions'] + class Meta: ordering = ['device', 'name'] unique_together = ['device', 'name'] @@ -2169,6 +2212,14 @@ class RearPort(CableTermination, ComponentModel): def __str__(self): return self.name + def to_csv(self): + return ( + self.device.identifier, + self.name, + self.get_type_display(), + self.positions, + ) + # # Device bays @@ -2198,6 +2249,8 @@ class DeviceBay(ComponentModel): objects = DeviceComponentManager() tags = TaggableManager() + csv_headers = ['device', 'name', 'installed_device'] + class Meta: ordering = ['device', 'name'] unique_together = ['device', 'name'] @@ -2208,6 +2261,13 @@ class DeviceBay(ComponentModel): def get_absolute_url(self): return self.device.get_absolute_url() + def to_csv(self): + return ( + self.device.identifier, + self.name, + self.installed_device.identifier if self.installed_device else None, + ) + def clean(self): # Validate that the parent Device can have DeviceBays @@ -2535,3 +2595,64 @@ class Cable(ChangeLoggedModel): return trace_cable(far_end, position) return trace_cable(self.termination_a), trace_cable(self.termination_b) + + +# +# Connection proxy models +# + +class ConsoleConnection(ConsolePort): + + csv_headers = [ + 'console_server', 'port', 'device', 'console_port', 'connection_status', + ] + + class Meta: + proxy = True + + def to_csv(self): + return ( + self.connected_endpoint.device.identifier if self.connected_endpoint else None, + self.connected_endpoint.name if self.connected_endpoint else None, + self.device.identifier, + self.name, + self.get_connection_status_display(), + ) + + +class PowerConnection(PowerPort): + + csv_headers = [ + 'pdu', 'outlet', 'device', 'power_port', 'connection_status', + ] + + class Meta: + proxy = True + + def to_csv(self): + return ( + self.connected_endpoint.device.identifier if self.connected_endpoint else None, + self.connected_endpoint.name if self.connected_endpoint else None, + self.device.identifier, + self.name, + self.get_connection_status_display(), + ) + + +class InterfaceConnection(Interface): + + csv_headers = [ + 'device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status', + ] + + class Meta: + proxy = True + + def to_csv(self): + return ( + self.connected_endpoint.device.identifier if self.connected_endpoint else None, + self.connected_endpoint.name if self.connected_endpoint else None, + self.device.identifier, + self.name, + self.get_connection_status_display(), + ) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 361bfcebd..6402958c2 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -4,10 +4,11 @@ from django_tables2.utils import Accessor from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn from .models import ( - Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, - InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, - RackGroup, RackReservation, RearPort, RearPortTemplate, Region, Site, VirtualChassis, + Cable, ConsoleConnection, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, + DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerConnection, PowerOutlet, PowerOutletTemplate, + PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RearPort, RearPortTemplate, Region, Site, + VirtualChassis, ) REGION_LINK = """ @@ -668,19 +669,22 @@ class ConsoleConnectionTable(BaseTable): viewname='dcim:device', accessor=Accessor('connected_endpoint.device'), args=[Accessor('connected_endpoint.device.pk')], - verbose_name='Console server' + verbose_name='Console Server' ) - connected_endpoint = tables.Column(verbose_name='Port') - device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device') - name = tables.Column(verbose_name='Console port') - cable = tables.LinkColumn( - viewname='dcim:cable', - args=[Accessor('cable.pk')] + connected_endpoint = tables.Column( + verbose_name='Port' + ) + device = tables.LinkColumn( + viewname='dcim:device', + args=[Accessor('device.pk')] + ) + name = tables.Column( + verbose_name='Console Port' ) class Meta(BaseTable.Meta): - model = ConsolePort - fields = ('console_server', 'connected_endpoint', 'device', 'name', 'cable') + model = ConsoleConnection + fields = ('console_server', 'connected_endpoint', 'device', 'name', 'connection_status') class PowerConnectionTable(BaseTable): @@ -690,17 +694,20 @@ class PowerConnectionTable(BaseTable): args=[Accessor('connected_endpoint.device.pk')], verbose_name='PDU' ) - connected_endpoint = tables.Column(verbose_name='Outlet') - device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device') - name = tables.Column(verbose_name='Power Port') - cable = tables.LinkColumn( - viewname='dcim:cable', - args=[Accessor('cable.pk')] + connected_endpoint = tables.Column( + verbose_name='Outlet' + ) + device = tables.LinkColumn( + viewname='dcim:device', + args=[Accessor('device.pk')] + ) + name = tables.Column( + verbose_name='Power Port' ) class Meta(BaseTable.Meta): - model = PowerPort - fields = ('pdu', 'connected_endpoint', 'device', 'name', 'cable') + model = PowerConnection + fields = ('pdu', 'connected_endpoint', 'device', 'name', 'connection_status') class InterfaceConnectionTable(BaseTable): @@ -736,14 +743,12 @@ class InterfaceConnectionTable(BaseTable): accessor=Accessor('connected_endpoint.description'), verbose_name='Description' ) - cable = tables.LinkColumn( - viewname='dcim:cable', - args=[Accessor('cable.pk')] - ) class Meta(BaseTable.Meta): - model = Interface - fields = ('device_a', 'interface_a', 'description_a', 'device_b', 'interface_b', 'description_b', 'cable') + model = InterfaceConnection + fields = ( + 'device_a', 'interface_a', 'description_a', 'device_b', 'interface_b', 'description_b', 'connection_status', + ) # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index c67962f01..d61cd8bd5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -24,10 +24,11 @@ from utilities.views import ( from virtualization.models import VirtualMachine from . import filters, forms, tables from .models import ( - Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, - Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, - RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, + Cable, ConsoleConnection, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, + DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceConnection, + InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerConnection, PowerOutlet, PowerOutletTemplate, + PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, + VirtualChassis, ) @@ -1686,7 +1687,7 @@ class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): # class ConsoleConnectionsListView(ObjectListView): - queryset = ConsolePort.objects.select_related( + queryset = ConsoleConnection.objects.select_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False @@ -1700,7 +1701,7 @@ class ConsoleConnectionsListView(ObjectListView): class PowerConnectionsListView(ObjectListView): - queryset = PowerPort.objects.select_related( + queryset = PowerConnection.objects.select_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False @@ -1714,7 +1715,7 @@ class PowerConnectionsListView(ObjectListView): class InterfaceConnectionsListView(ObjectListView): - queryset = Interface.objects.select_related( + queryset = InterfaceConnection.objects.select_related( 'device', 'cable', '_connected_interface__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair