From 35f80f5085e8ac66cc478a29426549d66a825d3e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 24 Oct 2018 16:17:01 -0400 Subject: [PATCH] First stab at cable path tracing and automatic endpoint connections --- netbox/dcim/models.py | 46 ++++++++++++++++++++++-- netbox/dcim/signals.py | 9 ++--- netbox/templates/dcim/inc/interface.html | 2 +- netbox/utilities/views.py | 7 ++++ 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 8da7c102b..2b426b285 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2372,9 +2372,49 @@ class Cable(ChangeLoggedModel): ('termination_b_type', 'termination_b_id'), ) - # TODO: This should follow all cables in a path def get_path_endpoints(self): """ - Return the endpoints connected by this cable path. + Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be + None. """ - return (self.termination_a, self.termination_b) + def trace_cable(termination, position=None): + + # Given a front port, follow the cable connected to the corresponding rear port/position + if isinstance(termination, FrontPanelPort): + rear_port = termination.rear_port + port_type = ContentType.objects.get_for_model(rear_port) + next_cable = Cable.objects.filter( + Q(termination_a_type=port_type, termination_a_id=rear_port.pk) | + Q(termination_b_type=port_type, termination_b_id=rear_port.pk) + ).first() + if next_cable is None: + return None + if next_cable.termination_a == termination.rear_port: + return trace_cable(next_cable.termination_b, termination.rear_port_position) + else: + return trace_cable(next_cable.termination_a, termination.rear_port_position) + + # Given a rear port/position, follow the cable connected to the corresponding front port + if isinstance(termination, RearPanelPort): + if position is None: + raise Exception("Must specify a position when tracing a path from a rear panel port") + front_port = FrontPanelPort.objects.get( + rear_port=termination, + rear_port_position=position, + ) + port_type = ContentType.objects.get_for_model(front_port) + next_cable = Cable.objects.filter( + Q(termination_a_type=port_type, termination_a_id=front_port.pk) | + Q(termination_b_type=port_type, termination_b_id=front_port.pk) + ).first() + if next_cable is None: + return None + if next_cable.termination_a == front_port: + return trace_cable(next_cable.termination_b) + else: + return trace_cable(next_cable.termination_a) + + # Termination is not a panel port, so we've reached the end of the path + return termination + + return trace_cable(self.termination_a), trace_cable(self.termination_b) diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 2320ba2d5..1d1094d66 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -27,10 +27,11 @@ def update_connected_endpoints(instance, **kwargs): When a Cable is saved, update its connected endpoints. """ termination_a, termination_b = instance.get_path_endpoints() - termination_a.connected_endpoint = termination_b - termination_a.save() - termination_b.connected_endpoint = termination_a - termination_b.save() + if termination_a is not None and termination_b is not None: + termination_a.connected_endpoint = termination_b + termination_a.save() + termination_b.connected_endpoint = termination_a + termination_b.save() @receiver(post_delete, sender=Cable) diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 287410617..72a56f367 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -106,7 +106,7 @@ {% else %} - + {% endif %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 0e36f3220..dcc0905f9 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -212,6 +212,13 @@ class ObjectEditView(GetReturnURLMixin, View): obj_created = not form.instance.pk obj = form.save() + print("Connecting {} {} to {} {}".format( + obj.termination_a.device, + obj.termination_a, + obj.termination_b.device, + obj.termination_b + )) + msg = '{} {}'.format( 'Created' if obj_created else 'Modified', self.model._meta.verbose_name