From 8bc3a8b17af57590afa574f337b1a33b9588ba78 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Fri, 24 Oct 2025 17:17:13 +0200 Subject: [PATCH] fix(dcim): Prevent cables from connecting to marked objects Restricts cable connections to terminations flagged as "mark connected" to avoid logical conflicts. Adds validation in the `Cable.clean()` method and includes corresponding test cases to verify behavior. Fixes #20646 --- netbox/dcim/models/cables.py | 17 ++++++++++++++--- netbox/dcim/tests/test_models.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 89c9a99b419..73ea08ff4a0 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -393,6 +393,17 @@ def __str__(self): def clean(self): super().clean() + # Disallow connecting a cable to any termination object that is + # explicitly flagged as "mark connected". + termination = getattr(self, 'termination', None) + if termination is not None and getattr(termination, "mark_connected", False): + raise ValidationError( + _("Cannot connect a cable to {obj_parent} > {obj} because it is marked as connected.").format( + obj_parent=termination.parent_object, + obj=termination, + ) + ) + # Check for existing termination qs = CableTermination.objects.filter( termination_type=self.termination_type, @@ -404,14 +415,14 @@ def clean(self): existing_termination = qs.first() if existing_termination is not None: raise ValidationError( - _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format( + _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}").format( app_label=self.termination_type.app_label, model=self.termination_type.model, termination_id=self.termination_id, cable_pk=existing_termination.cable.pk - )) + ) ) - # Validate interface type (if applicable) + # Validate the interface type (if applicable) if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES: raise ValidationError( _("Cables cannot be terminated to {type_display} interfaces").format( diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index be9f067d432..676030a047b 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -967,6 +967,18 @@ def test_cable_cannot_terminate_to_a_cellular_interface(self): with self.assertRaises(ValidationError): cable.clean() + def test_cannot_cable_to_mark_connected(self): + """ + Test that a cable cannot be connected to an interface marked as connected. + """ + device1 = Device.objects.get(name='TestDevice1') + interface1 = Interface.objects.get(device__name='TestDevice2', name='eth1') + + mark_connected_interface = Interface(device=device1, name='mark_connected1', mark_connected=True) + cable = Cable(a_terminations=[mark_connected_interface], b_terminations=[interface1]) + with self.assertRaises(ValidationError): + cable.clean() + class VirtualDeviceContextTestCase(TestCase):