Преглед изворни кода

#21990 fix deletion of device from Virtual Machines

Arthur пре 1 месец
родитељ
комит
e54e70c735

+ 7 - 0
netbox/dcim/forms/bulk_edit.py

@@ -1754,6 +1754,13 @@ class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
     )
     )
     nullable_fields = ('device', 'tenant', )
     nullable_fields = ('device', 'tenant', )
 
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Remove parent device passed as context to avoid conflicts with the actual device field
+        # on this form (see bug #21990)
+        self.initial.pop('device', None)
+
 
 
 #
 #
 # Addressing
 # Addressing

+ 4 - 0
netbox/virtualization/forms/bulk_edit.py

@@ -164,6 +164,10 @@ class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
 
 
+        # Remove parent device passed as context to avoid conflicts with the actual device field
+        # on this form (see bug #21990)
+        self.initial.pop('device', None)
+
         # Set unit labels based on configured RAM_BASE_UNIT / DISK_BASE_UNIT (MB vs MiB)
         # Set unit labels based on configured RAM_BASE_UNIT / DISK_BASE_UNIT (MB vs MiB)
         self.fields['memory'].label = _('Memory ({unit})').format(unit=get_capacity_unit_label(settings.RAM_BASE_UNIT))
         self.fields['memory'].label = _('Memory ({unit})').format(unit=get_capacity_unit_label(settings.RAM_BASE_UNIT))
         self.fields['disk'].label = _('Disk ({unit})').format(unit=get_capacity_unit_label(settings.DISK_BASE_UNIT))
         self.fields['disk'].label = _('Disk ({unit})').format(unit=get_capacity_unit_label(settings.DISK_BASE_UNIT))

+ 27 - 0
netbox/virtualization/tests/test_views.py

@@ -335,6 +335,33 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
         url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
         self.assertHttpStatus(self.client.get(url), 200)
         self.assertHttpStatus(self.client.get(url), 200)
 
 
+    def test_bulk_edit_device_context_preserves_device(self):
+        """
+        Regression test for #21990: Bulk editing VMs from the Device's VMs tab (URL contains
+        ?device=<id>) must not clear the device field on those VMs.
+        """
+        self.add_permissions('virtualization.view_virtualmachine', 'virtualization.change_virtualmachine')
+
+        device = VirtualMachine.objects.filter(device__isnull=False).first().device
+        vms = list(VirtualMachine.objects.filter(device=device)[:3])
+        pk_list = [vm.pk for vm in vms]
+
+        data = {
+            'pk': pk_list,
+            '_apply': True,
+            # Only change status — device is intentionally omitted
+            'status': VirtualMachineStatusChoices.STATUS_STAGED,
+        }
+
+        # Simulate navigation from Device -> Virtual Machines tab by passing ?device=<id> as GET param
+        url = reverse('virtualization:virtualmachine_bulk_edit') + f'?device={device.pk}'
+        response = self.client.post(url, data)
+        self.assertHttpStatus(response, 302)
+
+        for vm in VirtualMachine.objects.filter(pk__in=pk_list):
+            self.assertEqual(vm.device, device, msg=f"Device was unexpectedly cleared on VM '{vm.name}'")
+            self.assertEqual(vm.status, VirtualMachineStatusChoices.STATUS_STAGED)
+
     def test_virtualmachine_renderconfig(self):
     def test_virtualmachine_renderconfig(self):
         configtemplate = ConfigTemplate.objects.create(
         configtemplate = ConfigTemplate.objects.create(
             name='Test Config Template',
             name='Test Config Template',