From 413be72f73c224e8c39e7cb8613e9aa9b5a5c429 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Fri, 12 Dec 2025 10:52:07 -0600 Subject: [PATCH] Fixes #20912: Clear ModuleBay parent when module assignment removed When a ModuleBay's module field is cleared (returning it to device-level), the parent field was not being updated, leaving stale MPTT tree data. The save() method only set parent when module was present, but failed to clear it when module was removed. Now explicitly sets parent to None when module is cleared. --- netbox/dcim/models/device_components.py | 2 ++ netbox/dcim/tests/test_models.py | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 9c44e0494..dc8c1e455 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1222,6 +1222,8 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel): def save(self, *args, **kwargs): if self.module: self.parent = self.module.module_bay + else: + self.parent = None super().save(*args, **kwargs) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index e67f32e60..f41509808 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -841,6 +841,32 @@ class ModuleBayTestCase(TestCase): nested_bay = module.modulebays.get(name='SFP A-21') self.assertEqual(nested_bay.label, 'A-21') + @tag('regression') # #20912 + def test_module_bay_parent_cleared_when_module_removed(self): + """Test that the parent field is properly cleared when a module bay's module assignment is removed""" + device = Device.objects.first() + manufacturer = Manufacturer.objects.first() + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type') + bay1 = ModuleBay.objects.create(device=device, name='Test Bay 1') + bay2 = ModuleBay.objects.create(device=device, name='Test Bay 2') + + # Install a module in bay1 + module1 = Module.objects.create(device=device, module_bay=bay1, module_type=module_type) + + # Assign bay2 to module1 and verify parent is now set to bay1 (module1's bay) + bay2.module = module1 + bay2.save() + bay2.refresh_from_db() + self.assertEqual(bay2.parent, bay1) + self.assertEqual(bay2.module, module1) + + # Clear the module assignment (return bay2 to device level) Verify parent is cleared + bay2.module = None + bay2.save() + bay2.refresh_from_db() + self.assertIsNone(bay2.parent) + self.assertIsNone(bay2.module) + class CableTestCase(TestCase):