From f134a6ec63020b27b2baa4eb2d7c77049cb145c0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Oct 2018 15:21:16 -0400 Subject: [PATCH] Introduced CableTermination abstract model to ptovide Cable access from termination points --- netbox/dcim/models.py | 51 +++++++++++++++--------- netbox/dcim/querysets.py | 16 +++++++- netbox/templates/dcim/device.html | 2 + netbox/templates/dcim/inc/frontport.html | 9 +++++ netbox/templates/dcim/inc/rearport.html | 9 +++++ 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 75ab9dbe8..26bfc5a32 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -16,7 +16,6 @@ from taggit.managers import TaggableManager from timezone_field import TimeZoneField from circuits.models import Circuit -from extras.constants import OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange from utilities.fields import ColorField, NullableCharField from utilities.managers import NaturalOrderByManager @@ -24,7 +23,7 @@ from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object from .constants import * from .fields import ASNField, MACAddressField -from .querysets import InterfaceQuerySet +from .querysets import CableQuerySet, InterfaceQuerySet class ComponentTemplateModel(models.Model): @@ -66,6 +65,22 @@ class ComponentModel(models.Model): ).save() +class CableTermination(models.Model): + + class Meta: + abstract = True + + def get_connected_cable(self): + """ + Return the connected cable if one exists; else None. Assign the far end of the connection on the Cable instance. + """ + cable = Cable.objects.get_for_termination(self) + if cable is None: + return None + cable.far_end = cable.termination_b if cable.termination_a == self else cable.termination_a + return cable + + # # Regions # @@ -1603,7 +1618,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): # Console ports # -class ConsolePort(ComponentModel): +class ConsolePort(CableTermination, ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. """ @@ -1665,7 +1680,7 @@ class ConsoleServerPortManager(models.Manager): }).order_by('device', 'name_padded') -class ConsoleServerPort(ComponentModel): +class ConsoleServerPort(CableTermination, ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. """ @@ -1706,7 +1721,7 @@ class ConsoleServerPort(ComponentModel): # Power ports # -class PowerPort(ComponentModel): +class PowerPort(CableTermination, ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. """ @@ -1768,7 +1783,7 @@ class PowerOutletManager(models.Manager): }).order_by('device', 'name_padded') -class PowerOutlet(ComponentModel): +class PowerOutlet(CableTermination, ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. """ @@ -1809,7 +1824,7 @@ class PowerOutlet(ComponentModel): # Interfaces # -class Interface(ComponentModel): +class Interface(CableTermination, ComponentModel): """ A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other Interface. @@ -2035,7 +2050,7 @@ class Interface(ComponentModel): # Pass-through ports # -class FrontPort(ComponentModel): +class FrontPort(CableTermination, ComponentModel): """ A pass-through port on the front of a Device. """ @@ -2089,7 +2104,7 @@ class FrontPort(ComponentModel): ) -class RearPort(ComponentModel): +class RearPort(CableTermination, ComponentModel): """ A pass-through port on the rear of a Device. """ @@ -2352,12 +2367,19 @@ class Cable(ChangeLoggedModel): blank=True ) + objects = CableQuerySet.as_manager() + class Meta: unique_together = ( ('termination_a_type', 'termination_a_id'), ('termination_b_type', 'termination_b_id'), ) + def __str__(self): + if self.label: + return '{} (#{})'.format(self.label, self.pk) + return '#{}'.format(self.pk) + def get_path_endpoints(self): """ Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be @@ -2387,20 +2409,13 @@ class Cable(ChangeLoggedModel): return termination # Find the cable (if any) attached to the peer port - port_type = ContentType.objects.get_for_model(peer_port) - next_cable = Cable.objects.filter( - Q(termination_a_type=port_type, termination_a_id=peer_port.pk) | - Q(termination_b_type=port_type, termination_b_id=peer_port.pk) - ).first() + next_cable, far_end = peer_port.get_connection() # If no cable exists, return None if next_cable is None: return None # Return the far side termination of the cable - if next_cable.termination_a == peer_port: - return trace_cable(next_cable.termination_b, position) - else: - return trace_cable(next_cable.termination_a, position) + return trace_cable(far_end, position) return trace_cable(self.termination_a), trace_cable(self.termination_b) diff --git a/netbox/dcim/querysets.py b/netbox/dcim/querysets.py index 9b735dfa6..375699bd7 100644 --- a/netbox/dcim/querysets.py +++ b/netbox/dcim/querysets.py @@ -1,4 +1,5 @@ -from django.db.models import QuerySet +from django.contrib.contenttypes.models import ContentType +from django.db.models import Q, QuerySet from django.db.models.expressions import RawSQL from .constants import IFACE_ORDERING_NAME, IFACE_ORDERING_POSITION, NONCONNECTABLE_IFACE_TYPES @@ -68,3 +69,16 @@ class InterfaceQuerySet(QuerySet): wireless). """ return self.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES) + + +class CableQuerySet(QuerySet): + + def get_for_termination(self, termination): + """ + Return the Cable (or None) connected to a given termination point. + """ + content_type = ContentType.objects.get_for_model(termination) + return self.filter( + Q(termination_a_type=content_type, termination_a_id=termination.pk) | + Q(termination_b_type=content_type, termination_b_id=termination.pk) + ).first() diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 7a81266cc..261b7e1f2 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -706,6 +706,7 @@ Type Rear Port Position + Connected Cable @@ -758,6 +759,7 @@ Name Type Positions + Connected Cable diff --git a/netbox/templates/dcim/inc/frontport.html b/netbox/templates/dcim/inc/frontport.html index afa7e8764..38fd29b34 100644 --- a/netbox/templates/dcim/inc/frontport.html +++ b/netbox/templates/dcim/inc/frontport.html @@ -10,6 +10,15 @@ {{ frontport.get_type_display }} {{ frontport.rear_port }} {{ frontport.rear_port_position }} + {% with cable=frontport.get_connected_cable %} + + {% if cable %} + {{ cable }} to {{ cable.far_end.device }} {{ cable.far_end }} + {% else %} + Not connected + {% endif %} + + {% endwith %} {% if perms.dcim.change_frontport %} diff --git a/netbox/templates/dcim/inc/rearport.html b/netbox/templates/dcim/inc/rearport.html index e4610e4a5..aa130b883 100644 --- a/netbox/templates/dcim/inc/rearport.html +++ b/netbox/templates/dcim/inc/rearport.html @@ -9,6 +9,15 @@ {{ rearport.get_type_display }} {{ rearport.positions }} + {% with cable=rearport.get_connected_cable %} + + {% if cable %} + {{ cable }} to {{ cable.far_end.device }} {{ cable.far_end }} + {% else %} + Not connected + {% endif %} + + {% endwith %} {% if perms.dcim.change_rearport %}