Skip to content

Commit 5b3ff3c

Browse files
authored
Merge pull request #20739 from netbox-community/20738-vc-delete
20738 update vc_position in delete not signal handler
2 parents 730d730 + 8522c03 commit 5b3ff3c

File tree

3 files changed

+97
-14
lines changed

3 files changed

+97
-14
lines changed

netbox/dcim/models/devices.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,6 @@ def clean(self):
11541154
})
11551155

11561156
def delete(self, *args, **kwargs):
1157-
11581157
# Check for LAG interfaces split across member chassis
11591158
interfaces = Interface.objects.filter(
11601159
device__in=self.members.all(),
@@ -1168,6 +1167,13 @@ def delete(self, *args, **kwargs):
11681167
"interfaces."
11691168
).format(self=self, interfaces=InterfaceSpeedChoices))
11701169

1170+
# Clear vc_position and vc_priority on member devices BEFORE calling super().delete()
1171+
# This must be done here because on_delete=SET_NULL executes before pre_delete signal
1172+
for device in self.members.all():
1173+
device.vc_position = None
1174+
device.vc_priority = None
1175+
device.save()
1176+
11711177
return super().delete(*args, **kwargs)
11721178

11731179

netbox/dcim/signals.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from django.db.models.signals import post_save, post_delete, pre_delete
3+
from django.db.models.signals import post_save, post_delete
44
from django.dispatch import receiver
55

66
from dcim.choices import CableEndChoices, LinkStatusChoices
@@ -85,18 +85,6 @@ def assign_virtualchassis_master(instance, created, **kwargs):
8585
master.save()
8686

8787

88-
@receiver(pre_delete, sender=VirtualChassis)
89-
def clear_virtualchassis_members(instance, **kwargs):
90-
"""
91-
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
92-
"""
93-
devices = Device.objects.filter(virtual_chassis=instance.pk)
94-
for device in devices:
95-
device.vc_position = None
96-
device.vc_priority = None
97-
device.save()
98-
99-
10088
#
10189
# Cables
10290
#

netbox/dcim/tests/test_models.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,3 +1031,92 @@ def test_vdc_duplicate_identifier(self):
10311031
vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
10321032
with self.assertRaises(ValidationError):
10331033
vdc2.full_clean()
1034+
1035+
1036+
class VirtualChassisTestCase(TestCase):
1037+
1038+
@classmethod
1039+
def setUpTestData(cls):
1040+
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
1041+
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
1042+
devicetype = DeviceType.objects.create(
1043+
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
1044+
)
1045+
role = DeviceRole.objects.create(
1046+
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
1047+
)
1048+
Device.objects.create(
1049+
device_type=devicetype, role=role, name='TestDevice1', site=site
1050+
)
1051+
Device.objects.create(
1052+
device_type=devicetype, role=role, name='TestDevice2', site=site
1053+
)
1054+
1055+
def test_virtualchassis_deletion_clears_vc_position(self):
1056+
"""
1057+
Test that when a VirtualChassis is deleted, member devices have their
1058+
vc_position and vc_priority fields set to None.
1059+
"""
1060+
devices = Device.objects.all()
1061+
device1 = devices[0]
1062+
device2 = devices[1]
1063+
1064+
# Create a VirtualChassis with two member devices
1065+
vc = VirtualChassis.objects.create(name='Test VC', master=device1)
1066+
1067+
device1.virtual_chassis = vc
1068+
device1.vc_position = 1
1069+
device1.vc_priority = 10
1070+
device1.save()
1071+
1072+
device2.virtual_chassis = vc
1073+
device2.vc_position = 2
1074+
device2.vc_priority = 20
1075+
device2.save()
1076+
1077+
# Verify devices are members of the VC with positions set
1078+
device1.refresh_from_db()
1079+
device2.refresh_from_db()
1080+
self.assertEqual(device1.virtual_chassis, vc)
1081+
self.assertEqual(device1.vc_position, 1)
1082+
self.assertEqual(device1.vc_priority, 10)
1083+
self.assertEqual(device2.virtual_chassis, vc)
1084+
self.assertEqual(device2.vc_position, 2)
1085+
self.assertEqual(device2.vc_priority, 20)
1086+
1087+
# Delete the VirtualChassis
1088+
vc.delete()
1089+
1090+
# Verify devices have vc_position and vc_priority set to None
1091+
device1.refresh_from_db()
1092+
device2.refresh_from_db()
1093+
self.assertIsNone(device1.virtual_chassis)
1094+
self.assertIsNone(device1.vc_position)
1095+
self.assertIsNone(device1.vc_priority)
1096+
self.assertIsNone(device2.virtual_chassis)
1097+
self.assertIsNone(device2.vc_position)
1098+
self.assertIsNone(device2.vc_priority)
1099+
1100+
def test_virtualchassis_duplicate_vc_position(self):
1101+
"""
1102+
Test that two devices cannot be assigned to the same vc_position
1103+
within the same VirtualChassis.
1104+
"""
1105+
devices = Device.objects.all()
1106+
device1 = devices[0]
1107+
device2 = devices[1]
1108+
1109+
# Create a VirtualChassis
1110+
vc = VirtualChassis.objects.create(name='Test VC')
1111+
1112+
# Assign first device to vc_position 1
1113+
device1.virtual_chassis = vc
1114+
device1.vc_position = 1
1115+
device1.full_clean()
1116+
device1.save()
1117+
1118+
# Try to assign second device to the same vc_position
1119+
device2.virtual_chassis = vc
1120+
device2.vc_position = 1
1121+
with self.assertRaises(ValidationError):
1122+
device2.full_clean()

0 commit comments

Comments
 (0)