diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index e14e20bfb..9835e13f8 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -657,6 +657,16 @@ class CablePath(models.Model): origin_ids = [decompile_path_node(node)[1] for node in self.path[0]] origin_model.objects.filter(pk__in=origin_ids).update(_path=self.pk) + def delete(self, *args, **kwargs): + # Mirror save() - clear _path on origins to prevent stale references + # in table views that render _path.destinations + if self.path: + origin_model = self.origin_type.model_class() + origin_ids = [decompile_path_node(node)[1] for node in self.path[0]] + origin_model.objects.filter(pk__in=origin_ids, _path=self.pk).update(_path=None) + + super().delete(*args, **kwargs) + @property def origin_type(self): if self.path: diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 97200662b..dd9b7a0a6 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -170,6 +170,8 @@ def nullify_connected_endpoints(instance, **kwargs): # Remove the deleted CableTermination if it's one of the path's originating nodes if instance.termination in cablepath.origins: cablepath.origins.remove(instance.termination) + # Clear _path on the removed origin to prevent stale connection display + model.objects.filter(pk=instance.termination_id, _path=cablepath.pk).update(_path=None) cablepath.retrace() diff --git a/netbox/dcim/tests/test_cablepaths.py b/netbox/dcim/tests/test_cablepaths.py index 1bd613e3b..08c807280 100644 --- a/netbox/dcim/tests/test_cablepaths.py +++ b/netbox/dcim/tests/test_cablepaths.py @@ -2806,7 +2806,6 @@ class LegacyCablePathTests(CablePathTestCase): interface2 = Interface.objects.create(device=self.device, name='Interface 2') interface3 = Interface.objects.create(device=self.device, name='Interface 3') - # Create cables 1 cable1 = Cable( a_terminations=[interface1], b_terminations=[interface2, interface3] @@ -2838,6 +2837,10 @@ class LegacyCablePathTests(CablePathTestCase): is_active=True ) + # Verify _path is cleared on removed interface (#21127) + interface3.refresh_from_db() + self.assertPathIsNotSet(interface3) + def test_401_exclude_midspan_devices(self): """ [IF1] --C1-- [FP1][Test Device][RP1] --C2-- [RP2][Test Device][FP2] --C3-- [IF2]