Jelajahi Sumber

Fixes #9778: Fix exception during cable deletion after deleting a connected termination

jeremystretch 3 tahun lalu
induk
melakukan
367bf25618

+ 1 - 0
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

+ 13 - 11
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

+ 4 - 1
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()

+ 4 - 1
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)

+ 3 - 2
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):

+ 1 - 1
netbox/templates/dcim/interface.html

@@ -219,7 +219,7 @@
                 <tr>
                   <th scope="row">Path Status</th>
                   <td>
-                    {% if object.path.is_active %}
+                    {% if object.path.is_complete and object.path.is_active %}
                       <span class="badge bg-success">Reachable</span>
                     {% else %}
                       <span class="badge bg-danger">Not Reachable</span>