mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-23 07:56:44 -06:00
Support bulk removal of parent interfaces via UI if all children are included
This commit is contained in:
parent
9397d2af0b
commit
9eb1ce7791
@ -2531,6 +2531,36 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
response = self.client.get(reverse('dcim:interface_trace', kwargs={'pk': interface1.pk}))
|
response = self.client.get(reverse('dcim:interface_trace', kwargs={'pk': interface1.pk}))
|
||||||
self.assertHttpStatus(response, 200)
|
self.assertHttpStatus(response, 200)
|
||||||
|
|
||||||
|
def test_bulk_delete_child_interfaces(self):
|
||||||
|
interface1 = Interface.objects.get(name='Interface 1')
|
||||||
|
device = interface1.device
|
||||||
|
self.add_permissions('dcim.delete_interface')
|
||||||
|
|
||||||
|
# Create a child interface
|
||||||
|
child = Interface.objects.create(
|
||||||
|
device=device,
|
||||||
|
name='Interface 1A',
|
||||||
|
type=InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||||
|
parent=interface1
|
||||||
|
)
|
||||||
|
self.assertEqual(device.interfaces.count(), 6)
|
||||||
|
|
||||||
|
# Attempt to delete only the parent interface
|
||||||
|
data = {
|
||||||
|
'confirm': True,
|
||||||
|
}
|
||||||
|
self.client.post(self._get_url('delete', interface1), data)
|
||||||
|
self.assertEqual(device.interfaces.count(), 6) # 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(device.interfaces.count(), 4) # Child & parent were both deleted
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import traceback
|
import traceback
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -26,6 +25,7 @@ from tenancy.views import ObjectContactsView
|
|||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
|
from utilities.query_functions import CollateAsChar
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
@ -2562,7 +2562,8 @@ class InterfaceBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceBulkDeleteView(generic.BulkDeleteView):
|
class InterfaceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Interface.objects.all()
|
# Ensure child interfaces are deleted prior to their parents
|
||||||
|
queryset = Interface.objects.order_by('device', 'parent', CollateAsChar('_name'))
|
||||||
filterset = filtersets.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
|
|
||||||
|
@ -798,11 +798,12 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
queryset = self.queryset.filter(pk__in=pk_list)
|
queryset = self.queryset.filter(pk__in=pk_list)
|
||||||
deleted_count = queryset.count()
|
deleted_count = queryset.count()
|
||||||
try:
|
try:
|
||||||
for obj in queryset:
|
with transaction.atomic():
|
||||||
# Take a snapshot of change-logged models
|
for obj in queryset:
|
||||||
if hasattr(obj, 'snapshot'):
|
# Take a snapshot of change-logged models
|
||||||
obj.snapshot()
|
if hasattr(obj, 'snapshot'):
|
||||||
obj.delete()
|
obj.snapshot()
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
except (ProtectedError, RestrictedError) as e:
|
except (ProtectedError, RestrictedError) as e:
|
||||||
logger.info(f"Caught {type(e)} while attempting to delete objects")
|
logger.info(f"Caught {type(e)} while attempting to delete objects")
|
||||||
|
@ -374,3 +374,32 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
'untagged_vlan': vlans[0].pk,
|
'untagged_vlan': vlans[0].pk,
|
||||||
'tagged_vlans': [v.pk for v in vlans[1:4]],
|
'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
|
import traceback
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@ -19,6 +18,7 @@ from ipam.tables import InterfaceVLANTable
|
|||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from tenancy.views import ObjectContactsView
|
from tenancy.views import ObjectContactsView
|
||||||
|
from utilities.query_functions import CollateAsChar
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from utilities.views import ViewTab, register_model_view
|
from utilities.views import ViewTab, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
@ -550,7 +550,8 @@ class VMInterfaceBulkRenameView(generic.BulkRenameView):
|
|||||||
|
|
||||||
|
|
||||||
class VMInterfaceBulkDeleteView(generic.BulkDeleteView):
|
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
|
filterset = filtersets.VMInterfaceFilterSet
|
||||||
table = tables.VMInterfaceTable
|
table = tables.VMInterfaceTable
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user