From 367bf25618d1be55c10b7e707101f2759711e855 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 3 Aug 2022 12:46:16 -0400 Subject: [PATCH] Fixes #9778: Fix exception during cable deletion after deleting a connected termination --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/models/cables.py | 24 +++++++++++++----------- netbox/dcim/signals.py | 5 ++++- netbox/dcim/svg/cables.py | 5 ++++- netbox/dcim/utils.py | 5 +++-- netbox/templates/dcim/interface.html | 2 +- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 5d043a777..6099e8a61 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -104,6 +104,7 @@ Custom field UI visibility has no impact on API operation. * [#9730](https://github.com/netbox-community/netbox/issues/9730) - Fix validation error when creating a new cable via UI form * [#9733](https://github.com/netbox-community/netbox/issues/9733) - Handle split paths during trace when fanning out to front ports with differing cables * [#9765](https://github.com/netbox-community/netbox/issues/9765) - Report correct segment count under cable trace UI view +* [#9778](https://github.com/netbox-community/netbox/issues/9778) - Fix exception during cable deletion after deleting a connected termination * [#9788](https://github.com/netbox-community/netbox/issues/9788) - Ensure denormalized fields on CableTermination are kept in sync with related objects * [#9789](https://github.com/netbox-community/netbox/issues/9789) - Fix rendering of cable traces ending at provider networks * [#9794](https://github.com/netbox-community/netbox/issues/9794) - Fix link to connect a rear port to a circuit termination diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index e0a489f5b..321d808ff 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -431,11 +431,7 @@ class CablePath(models.Model): """ Return the list of originating objects. """ - if hasattr(self, '_path_objects'): - return self.path_objects[0] - return [ - path_node_to_object(node) for node in self.path[0] - ] + return self.path_objects[0] @property def destinations(self): @@ -444,11 +440,7 @@ class CablePath(models.Model): """ if not self.is_complete: return [] - if hasattr(self, '_path_objects'): - return self.path_objects[-1] - return [ - path_node_to_object(node) for node in self.path[-1] - ] + return self.path_objects[-1] @property def segment_count(self): @@ -463,6 +455,9 @@ class CablePath(models.Model): """ from circuits.models import CircuitTermination + if not terminations: + return None + # Ensure all originating terminations are attached to the same link if len(terminations) > 1: assert all(t.link == terminations[0].link for t in terminations[1:]) @@ -529,6 +524,9 @@ class CablePath(models.Model): ]) # Step 6: Determine the "next hop" terminations, if applicable + if not remote_terminations: + break + if isinstance(remote_terminations[0], FrontPort): # Follow FrontPorts to their corresponding RearPorts rear_ports = RearPort.objects.filter( @@ -640,7 +638,11 @@ class CablePath(models.Model): nodes = [] for node in step: ct_id, object_id = decompile_path_node(node) - nodes.append(prefetched[ct_id][object_id]) + try: + nodes.append(prefetched[ct_id][object_id]) + except KeyError: + # Ignore stale (deleted) object IDs + pass path.append(nodes) return path diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 2293f8840..b990daf1a 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -116,7 +116,10 @@ def retrace_cable_paths(instance, **kwargs): @receiver(post_delete, sender=CableTermination) def nullify_connected_endpoints(instance, **kwargs): """ - Disassociate the Cable from the termination object. + Disassociate the Cable from the termination object, and retrace any affected CablePaths. """ model = instance.termination_type.model_class() model.objects.filter(pk=instance.termination_id).update(cable=None, cable_end='') + + for cablepath in CablePath.objects.filter(_nodes__contains=instance.cable): + cablepath.retrace() diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py index 3b259eca2..26d16fafe 100644 --- a/netbox/dcim/svg/cables.py +++ b/netbox/dcim/svg/cables.py @@ -362,8 +362,11 @@ class CableTraceSVG: terminations = self.draw_terminations(far_ends) for term in terminations: self.draw_fanout(term, cable) - else: + elif far_ends: self.draw_terminations(far_ends) + else: + # Link is not connected to anything + break # Far end parent parent_objects = set(end.parent_object for end in far_ends) diff --git a/netbox/dcim/utils.py b/netbox/dcim/utils.py index 26b6e2e25..eadd2da96 100644 --- a/netbox/dcim/utils.py +++ b/netbox/dcim/utils.py @@ -24,11 +24,12 @@ def object_to_path_node(obj): def path_node_to_object(repr): """ - Given the string representation of a path node, return the corresponding instance. + Given the string representation of a path node, return the corresponding instance. If the object no longer + exists, return None. """ ct_id, object_id = decompile_path_node(repr) ct = ContentType.objects.get_for_id(ct_id) - return ct.model_class().objects.get(pk=object_id) + return ct.model_class().objects.filter(pk=object_id).first() def create_cablepath(terminations): diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 3a7fe986a..11e776872 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -219,7 +219,7 @@ Path Status - {% if object.path.is_active %} + {% if object.path.is_complete and object.path.is_active %} Reachable {% else %} Not Reachable