mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* Closes #12135: Prevent the deletion of interfaces with children * Change PROTECT to RESTRICT * Extend handle_protectederror() to also handle RestrictedError * Fix string translation * Update migrations * Support bulk removal of parent interfaces via UI if all children are included * Add support for the bulk deletion of restricted objects via REST API
This commit is contained in:
@@ -3,6 +3,7 @@ from rest_framework.routers import APIRootView
|
||||
from dcim.models import Device
|
||||
from extras.api.mixins import ConfigContextQuerySetMixin
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.utils import count_related
|
||||
from virtualization import filtersets
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||
@@ -87,3 +88,7 @@ class VMInterfaceViewSet(NetBoxModelViewSet):
|
||||
serializer_class = serializers.VMInterfaceSerializer
|
||||
filterset_class = filtersets.VMInterfaceFilterSet
|
||||
brief_prefetch_fields = ['virtual_machine']
|
||||
|
||||
def get_bulk_destroy_queryset(self):
|
||||
# Ensure child interfaces are deleted prior to their parents
|
||||
return self.get_queryset().order_by('virtual_machine', 'parent', CollateAsChar('_name'))
|
||||
|
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.6 on 2023-10-20 11:48
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('virtualization', '0036_virtualmachine_config_template'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vminterface',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='child_interfaces', to='virtualization.vminterface'),
|
||||
),
|
||||
]
|
@@ -293,3 +293,29 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
|
||||
'vrf': vrfs[2].pk,
|
||||
},
|
||||
]
|
||||
|
||||
def test_bulk_delete_child_interfaces(self):
|
||||
interface1 = VMInterface.objects.get(name='Interface 1')
|
||||
virtual_machine = interface1.virtual_machine
|
||||
self.add_permissions('virtualization.delete_vminterface')
|
||||
|
||||
# Create a child interface
|
||||
child = VMInterface.objects.create(
|
||||
virtual_machine=virtual_machine,
|
||||
name='Interface 1A',
|
||||
parent=interface1
|
||||
)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 4)
|
||||
|
||||
# Attempt to delete only the parent interface
|
||||
url = self._get_detail_url(interface1)
|
||||
self.client.delete(url, **self.header)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 4) # Parent was not deleted
|
||||
|
||||
# Attempt to bulk delete parent & child together
|
||||
data = [
|
||||
{"id": interface1.pk},
|
||||
{"id": child.pk},
|
||||
]
|
||||
self.client.delete(self._get_list_url(), data, format='json', **self.header)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 2) # Child & parent were both deleted
|
||||
|
@@ -374,3 +374,32 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
'untagged_vlan': vlans[0].pk,
|
||||
'tagged_vlans': [v.pk for v in vlans[1:4]],
|
||||
}
|
||||
|
||||
def test_bulk_delete_child_interfaces(self):
|
||||
interface1 = VMInterface.objects.get(name='Interface 1')
|
||||
virtual_machine = interface1.virtual_machine
|
||||
self.add_permissions('virtualization.delete_vminterface')
|
||||
|
||||
# Create a child interface
|
||||
child = VMInterface.objects.create(
|
||||
virtual_machine=virtual_machine,
|
||||
name='Interface 1A',
|
||||
parent=interface1
|
||||
)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 4)
|
||||
|
||||
# Attempt to delete only the parent interface
|
||||
data = {
|
||||
'confirm': True,
|
||||
}
|
||||
self.client.post(self._get_url('delete', interface1), data)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 4) # Parent was not deleted
|
||||
|
||||
# Attempt to bulk delete parent & child together
|
||||
data = {
|
||||
'pk': [interface1.pk, child.pk],
|
||||
'confirm': True,
|
||||
'_confirm': True, # Form button
|
||||
}
|
||||
self.client.post(self._get_url('bulk_delete'), data)
|
||||
self.assertEqual(virtual_machine.interfaces.count(), 2) # Child & parent were both deleted
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
@@ -19,6 +18,7 @@ from ipam.tables import InterfaceVLANTable
|
||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||
from netbox.views import generic
|
||||
from tenancy.views import ObjectContactsView
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import ViewTab, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
@@ -550,7 +550,8 @@ class VMInterfaceBulkRenameView(generic.BulkRenameView):
|
||||
|
||||
|
||||
class VMInterfaceBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = VMInterface.objects.all()
|
||||
# Ensure child interfaces are deleted prior to their parents
|
||||
queryset = VMInterface.objects.order_by('virtual_machine', 'parent', CollateAsChar('_name'))
|
||||
filterset = filtersets.VMInterfaceFilterSet
|
||||
table = tables.VMInterfaceTable
|
||||
|
||||
|
Reference in New Issue
Block a user