From fca347e49e50c4bcb4493fdae8eb846a141d81b6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 4 Feb 2020 15:41:15 -0500 Subject: [PATCH 01/11] Reorder URLs --- netbox/dcim/urls.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 38e24308b..ddc322da2 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -172,11 +172,11 @@ urlpatterns = [ path(r'devices//console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), path(r'devices//console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), path(r'console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'), + path(r'console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), path(r'console-ports//connect//', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path(r'console-ports//edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), path(r'console-ports//delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), path(r'console-ports//trace/', views.CableTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}), - path(r'console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), # Console server ports path(r'devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), @@ -184,24 +184,24 @@ urlpatterns = [ path(r'devices//console-server-ports/edit/', views.ConsoleServerPortBulkEditView.as_view(), name='consoleserverport_bulk_edit'), path(r'devices//console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), path(r'console-server-ports/', views.ConsoleServerPortListView.as_view(), name='consoleserverport_list'), + path(r'console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), + path(r'console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), + path(r'console-server-ports/import/', views.ConsoleServerPortBulkImportView.as_view(), name='consoleserverport_import'), path(r'console-server-ports//connect//', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path(r'console-server-ports//edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), path(r'console-server-ports//delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), path(r'console-server-ports//trace/', views.CableTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}), - path(r'console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), - path(r'console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), - path(r'console-server-ports/import/', views.ConsoleServerPortBulkImportView.as_view(), name='consoleserverport_import'), # Power ports path(r'devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), path(r'devices//power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), path(r'devices//power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), path(r'power-ports/', views.PowerPortListView.as_view(), name='powerport_list'), + path(r'power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), path(r'power-ports//connect//', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path(r'power-ports//edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), path(r'power-ports//delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'), path(r'power-ports//trace/', views.CableTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}), - path(r'power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), # Power outlets path(r'devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), @@ -209,13 +209,13 @@ urlpatterns = [ path(r'devices//power-outlets/edit/', views.PowerOutletBulkEditView.as_view(), name='poweroutlet_bulk_edit'), path(r'devices//power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), path(r'power-outlets/', views.PowerOutletListView.as_view(), name='poweroutlet_list'), + path(r'power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), + path(r'power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), + path(r'power-outlets/import/', views.PowerOutletBulkImportView.as_view(), name='poweroutlet_import'), path(r'power-outlets//connect//', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path(r'power-outlets//edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), path(r'power-outlets//delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), path(r'power-outlets//trace/', views.CableTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}), - path(r'power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), - path(r'power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), - path(r'power-outlets/import/', views.PowerOutletBulkImportView.as_view(), name='poweroutlet_import'), # Interfaces path(r'devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), @@ -223,15 +223,15 @@ urlpatterns = [ path(r'devices//interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), path(r'devices//interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), path(r'interfaces/', views.InterfaceListView.as_view(), name='interface_list'), + path(r'interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), + path(r'interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), + path(r'interfaces/import/', views.InterfaceBulkImportView.as_view(), name='interface_import'), path(r'interfaces//connect//', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path(r'interfaces//', views.InterfaceView.as_view(), name='interface'), path(r'interfaces//edit/', views.InterfaceEditView.as_view(), name='interface_edit'), path(r'interfaces//delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'), path(r'interfaces//changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), path(r'interfaces//trace/', views.CableTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}), - path(r'interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), - path(r'interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), - path(r'interfaces/import/', views.InterfaceBulkImportView.as_view(), name='interface_import'), # Front ports # path(r'devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), @@ -239,13 +239,13 @@ urlpatterns = [ path(r'devices//front-ports/edit/', views.FrontPortBulkEditView.as_view(), name='frontport_bulk_edit'), path(r'devices//front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), path(r'front-ports/', views.FrontPortListView.as_view(), name='frontport_list'), + path(r'front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), + path(r'front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), + path(r'front-ports/import/', views.FrontPortBulkImportView.as_view(), name='frontport_import'), path(r'front-ports//connect//', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), path(r'front-ports//edit/', views.FrontPortEditView.as_view(), name='frontport_edit'), path(r'front-ports//delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'), path(r'front-ports//trace/', views.CableTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}), - path(r'front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), - path(r'front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), - path(r'front-ports/import/', views.FrontPortBulkImportView.as_view(), name='frontport_import'), # Rear ports # path(r'devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), @@ -253,25 +253,25 @@ urlpatterns = [ path(r'devices//rear-ports/edit/', views.RearPortBulkEditView.as_view(), name='rearport_bulk_edit'), path(r'devices//rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), path(r'rear-ports/', views.RearPortListView.as_view(), name='rearport_list'), + path(r'rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), + path(r'rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), + path(r'rear-ports/import/', views.RearPortBulkImportView.as_view(), name='rearport_import'), path(r'rear-ports//connect//', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path(r'rear-ports//edit/', views.RearPortEditView.as_view(), name='rearport_edit'), path(r'rear-ports//delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'), path(r'rear-ports//trace/', views.CableTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}), - path(r'rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), - path(r'rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), - path(r'rear-ports/import/', views.RearPortBulkImportView.as_view(), name='rearport_import'), # Device bays path(r'devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'), path(r'devices//bays/add/', views.DeviceBayCreateView.as_view(), name='devicebay_add'), path(r'devices//bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), path(r'device-bays/', views.DeviceBayListView.as_view(), name='devicebay_list'), + path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), + path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), path(r'device-bays//edit/', views.DeviceBayEditView.as_view(), name='devicebay_edit'), path(r'device-bays//delete/', views.DeviceBayDeleteView.as_view(), name='devicebay_delete'), path(r'device-bays//populate/', views.DeviceBayPopulateView.as_view(), name='devicebay_populate'), path(r'device-bays//depopulate/', views.DeviceBayDepopulateView.as_view(), name='devicebay_depopulate'), - path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), - path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), # Inventory items path(r'inventory-items/', views.InventoryItemListView.as_view(), name='inventoryitem_list'), From c1639b778130f684b525acde484e124a99ee8260 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 4 Feb 2020 16:06:55 -0500 Subject: [PATCH 02/11] Move component bulk delete views to new URLs --- netbox/dcim/tests/test_views.py | 8 -------- netbox/dcim/urls.py | 16 ++++++++-------- netbox/dcim/views.py | 16 ++++++++-------- netbox/templates/dcim/device.html | 12 ++++++------ .../templates/virtualization/virtualmachine.html | 2 +- netbox/utilities/views.py | 9 --------- netbox/virtualization/urls.py | 3 ++- 7 files changed, 25 insertions(+), 41 deletions(-) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index aa880b67d..baf80b522 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -686,7 +686,6 @@ class ConsolePortTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -728,7 +727,6 @@ class ConsoleServerPortTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -769,7 +767,6 @@ class PowerPortTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -812,7 +809,6 @@ class PowerOutletTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -858,7 +854,6 @@ class InterfaceTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -916,7 +911,6 @@ class FrontPortTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -968,7 +962,6 @@ class RearPortTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): @@ -1009,7 +1002,6 @@ class DeviceBayTestCase(StandardTestCases.Views): # TODO test_create_object = None test_bulk_edit_objects = None - test_bulk_delete_objects = None @classmethod def setUpTestData(cls): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index ddc322da2..fd2f165d4 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -170,9 +170,9 @@ urlpatterns = [ # Console ports path(r'devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), path(r'devices//console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), - path(r'devices//console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), path(r'console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'), path(r'console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), + path(r'console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), path(r'console-ports//connect//', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path(r'console-ports//edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), path(r'console-ports//delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), @@ -182,11 +182,11 @@ urlpatterns = [ path(r'devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), path(r'devices//console-server-ports/add/', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), path(r'devices//console-server-ports/edit/', views.ConsoleServerPortBulkEditView.as_view(), name='consoleserverport_bulk_edit'), - path(r'devices//console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), path(r'console-server-ports/', views.ConsoleServerPortListView.as_view(), name='consoleserverport_list'), path(r'console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), path(r'console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), path(r'console-server-ports/import/', views.ConsoleServerPortBulkImportView.as_view(), name='consoleserverport_import'), + path(r'console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), path(r'console-server-ports//connect//', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path(r'console-server-ports//edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), path(r'console-server-ports//delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), @@ -195,9 +195,9 @@ urlpatterns = [ # Power ports path(r'devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), path(r'devices//power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), - path(r'devices//power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), path(r'power-ports/', views.PowerPortListView.as_view(), name='powerport_list'), path(r'power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), + path(r'power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), path(r'power-ports//connect//', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path(r'power-ports//edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), path(r'power-ports//delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'), @@ -207,11 +207,11 @@ urlpatterns = [ path(r'devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), path(r'devices//power-outlets/add/', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), path(r'devices//power-outlets/edit/', views.PowerOutletBulkEditView.as_view(), name='poweroutlet_bulk_edit'), - path(r'devices//power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), path(r'power-outlets/', views.PowerOutletListView.as_view(), name='poweroutlet_list'), path(r'power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), path(r'power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), path(r'power-outlets/import/', views.PowerOutletBulkImportView.as_view(), name='poweroutlet_import'), + path(r'power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), path(r'power-outlets//connect//', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path(r'power-outlets//edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), path(r'power-outlets//delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), @@ -221,11 +221,11 @@ urlpatterns = [ path(r'devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), path(r'devices//interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), path(r'devices//interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), - path(r'devices//interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), path(r'interfaces/', views.InterfaceListView.as_view(), name='interface_list'), path(r'interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), path(r'interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), path(r'interfaces/import/', views.InterfaceBulkImportView.as_view(), name='interface_import'), + path(r'interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), path(r'interfaces//connect//', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path(r'interfaces//', views.InterfaceView.as_view(), name='interface'), path(r'interfaces//edit/', views.InterfaceEditView.as_view(), name='interface_edit'), @@ -237,11 +237,11 @@ urlpatterns = [ # path(r'devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), path(r'devices//front-ports/add/', views.FrontPortCreateView.as_view(), name='frontport_add'), path(r'devices//front-ports/edit/', views.FrontPortBulkEditView.as_view(), name='frontport_bulk_edit'), - path(r'devices//front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), path(r'front-ports/', views.FrontPortListView.as_view(), name='frontport_list'), path(r'front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), path(r'front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), path(r'front-ports/import/', views.FrontPortBulkImportView.as_view(), name='frontport_import'), + path(r'front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), path(r'front-ports//connect//', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), path(r'front-ports//edit/', views.FrontPortEditView.as_view(), name='frontport_edit'), path(r'front-ports//delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'), @@ -251,11 +251,11 @@ urlpatterns = [ # path(r'devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), path(r'devices//rear-ports/add/', views.RearPortCreateView.as_view(), name='rearport_add'), path(r'devices//rear-ports/edit/', views.RearPortBulkEditView.as_view(), name='rearport_bulk_edit'), - path(r'devices//rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), path(r'rear-ports/', views.RearPortListView.as_view(), name='rearport_list'), path(r'rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), path(r'rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), path(r'rear-ports/import/', views.RearPortBulkImportView.as_view(), name='rearport_import'), + path(r'rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), path(r'rear-ports//connect//', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path(r'rear-ports//edit/', views.RearPortEditView.as_view(), name='rearport_edit'), path(r'rear-ports//delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'), @@ -264,10 +264,10 @@ urlpatterns = [ # Device bays path(r'devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'), path(r'devices//bays/add/', views.DeviceBayCreateView.as_view(), name='devicebay_add'), - path(r'devices//bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), path(r'device-bays/', views.DeviceBayListView.as_view(), name='devicebay_list'), path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), + path(r'device-bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), path(r'device-bays//edit/', views.DeviceBayEditView.as_view(), name='devicebay_edit'), path(r'device-bays//delete/', views.DeviceBayDeleteView.as_view(), name='devicebay_delete'), path(r'device-bays//populate/', views.DeviceBayPopulateView.as_view(), name='devicebay_populate'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index fd3d09ab7..2fdf7f457 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1234,8 +1234,8 @@ class ConsolePortBulkImportView(PermissionRequiredMixin, BulkImportView): class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleport' queryset = ConsolePort.objects.all() - parent_model = Device table = tables.ConsolePortTable + default_return_url = 'dcim:consoleport_list' # @@ -1302,8 +1302,8 @@ class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnec class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleserverport' queryset = ConsoleServerPort.objects.all() - parent_model = Device table = tables.ConsoleServerPortTable + default_return_url = 'dcim:consoleserverport_list' # @@ -1350,8 +1350,8 @@ class PowerPortBulkImportView(PermissionRequiredMixin, BulkImportView): class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_powerport' queryset = PowerPort.objects.all() - parent_model = Device table = tables.PowerPortTable + default_return_url = 'dcim:powerport_list' # @@ -1418,8 +1418,8 @@ class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView) class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_poweroutlet' queryset = PowerOutlet.objects.all() - parent_model = Device table = tables.PowerOutletTable + default_return_url = 'dcim:poweroutlet_list' # @@ -1523,8 +1523,8 @@ class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_interface' queryset = Interface.objects.all() - parent_model = Device table = tables.InterfaceTable + default_return_url = 'dcim:interface_list' # @@ -1591,8 +1591,8 @@ class FrontPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_frontport' queryset = FrontPort.objects.all() - parent_model = Device table = tables.FrontPortTable + default_return_url = 'dcim:frontport_list' # @@ -1659,8 +1659,8 @@ class RearPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): class RearPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rearport' queryset = RearPort.objects.all() - parent_model = Device table = tables.RearPortTable + default_return_url = 'dcim:rearport_list' # @@ -1784,8 +1784,8 @@ class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView): class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicebay' queryset = DeviceBay.objects.all() - parent_model = Device table = tables.DeviceBayTable + default_return_url = 'dcim:devicebay_list' # diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index fa37f1ac5..57c068727 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -524,7 +524,7 @@ {% endif %} {% if device_bays and perms.dcim.delete_devicebay %} - {% endif %} @@ -597,7 +597,7 @@ {% endif %} {% if interfaces and perms.dcim.delete_interface %} - {% endif %} @@ -657,7 +657,7 @@ {% endif %} {% if consoleserverports and perms.dcim.delete_consoleserverport %} - {% endif %} @@ -718,7 +718,7 @@ {% endif %} {% if poweroutlets and perms.dcim.delete_poweroutlet %} - {% endif %} @@ -778,7 +778,7 @@ {% endif %} {% if front_ports and perms.dcim.delete_frontport %} - {% endif %} @@ -835,7 +835,7 @@ {% endif %} {% if rear_ports and perms.dcim.delete_rearport %} - {% endif %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 982ef0e5e..a1887be6d 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -293,7 +293,7 @@ {% endif %} {% if interfaces and perms.dcim.delete_interface %} - {% endif %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index df73d480b..f32c57c3f 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -742,14 +742,12 @@ class BulkDeleteView(GetReturnURLMixin, View): Delete objects in bulk. queryset: Custom queryset to use when retrieving objects (e.g. to select related objects) - parent_model: The model of the parent object (if any) filter: FilterSet to apply when deleting by QuerySet table: The table used to display devices being deleted form: The form class used to delete objects in bulk template_name: The name of the template """ queryset = None - parent_model = None filterset = None table = None form = None @@ -762,12 +760,6 @@ class BulkDeleteView(GetReturnURLMixin, View): model = self.queryset.model - # Attempt to derive parent object if a parent class has been given - if self.parent_model: - parent_obj = get_object_or_404(self.parent_model, **kwargs) - else: - parent_obj = None - # Are we deleting *all* objects in the queryset or just a selected subset? if request.POST.get('_all'): if self.filterset is not None: @@ -809,7 +801,6 @@ class BulkDeleteView(GetReturnURLMixin, View): return render(request, self.template_name, { 'form': form, - 'parent_obj': parent_obj, 'obj_type_plural': model._meta.verbose_name_plural, 'table': table, 'return_url': self.get_return_url(request), diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 7cc28be51..88a8dc6d9 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -54,7 +54,8 @@ urlpatterns = [ path(r'virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_interface'), path(r'virtual-machines//interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), path(r'virtual-machines//interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), - path(r'virtual-machines//interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), + path(r'interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), + # TODO: Rename vm-interfaces to interfaces path(r'vm-interfaces//edit/', views.InterfaceEditView.as_view(), name='interface_edit'), path(r'vm-interfaces//delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'), From 3e79b9d26aab22f874507ac7ca623bdc1a9cbb32 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 4 Feb 2020 16:40:18 -0500 Subject: [PATCH 03/11] Add InterfaceTestCase for virtual machines --- netbox/utilities/testing/testcases.py | 15 +++-- netbox/virtualization/tests/test_views.py | 77 ++++++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index b55d914d9..094629676 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -104,15 +104,22 @@ class StandardTestCases: if self.model is None: raise Exception("Test case requires model to be defined") + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return '{}:{}_{{}}'.format( + self.model._meta.app_label, + self.model._meta.model_name + ) + def _get_url(self, action, instance=None): """ Return the URL name for a specific action. An instance must be specified for get/edit/delete views. """ - url_format = '{}:{}_{{}}'.format( - self.model._meta.app_label, - self.model._meta.model_name - ) + url_format = self._get_base_url() if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'): return reverse(url_format.format(action)) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 77f87c92a..40a3b6c53 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -1,4 +1,8 @@ -from dcim.models import DeviceRole, Platform, Site +from netaddr import EUI + +from dcim.choices import InterfaceModeChoices +from dcim.models import DeviceRole, Interface, Platform, Site +from ipam.models import VLAN from utilities.testing import StandardTestCases from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -187,3 +191,74 @@ class VirtualMachineTestCase(StandardTestCases.Views): 'disk': 8000, 'comments': 'New comments', } + + +class InterfaceTestCase(StandardTestCases.Views): + model = Interface + + # Disable inapplicable tests + test_list_objects = None + test_import_objects = None + + # TODO + test_create_object = None + test_bulk_edit_objects = None + + def _get_base_url(self): + # Interface belongs to the DCIM app, so we have to override the base URL + return 'virtualization:interface_{}' + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site-1') + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + cluster = Cluster.objects.create(name='Cluster 1', type=clustertype, site=site) + virtualmachines = ( + VirtualMachine(name='Virtual Machine 1', cluster=cluster, role=devicerole), + VirtualMachine(name='Virtual Machine 2', cluster=cluster, role=devicerole), + ) + VirtualMachine.objects.bulk_create(virtualmachines) + + Interface.objects.bulk_create([ + Interface(virtual_machine=virtualmachines[0], name='Interface 1'), + Interface(virtual_machine=virtualmachines[0], name='Interface 2'), + Interface(virtual_machine=virtualmachines[0], name='Interface 3'), + ]) + + vlans = ( + VLAN(vid=1, name='VLAN1', site=site), + VLAN(vid=101, name='VLAN101', site=site), + VLAN(vid=102, name='VLAN102', site=site), + VLAN(vid=103, name='VLAN103', site=site), + ) + VLAN.objects.bulk_create(vlans) + + cls.form_data = { + 'virtual_machine': virtualmachines[1].pk, + 'name': 'Interface X', + 'type': InterfaceTypeChoices.TYPE_VIRTUAL, + 'enabled': False, + 'mgmt_only': False, + 'mac_address': EUI('01-02-03-04-05-06'), + 'mtu': 2000, + 'description': 'New description', + 'mode': InterfaceModeChoices.MODE_TAGGED, + 'untagged_vlan': vlans[0].pk, + 'tagged_vlans': [v.pk for v in vlans[1:4]], + 'tags': 'Alpha,Bravo,Charlie', + + # Extraneous model fields + 'device': None, + 'lag': None, + 'cable': None, + 'connection_status': None, + } + + cls.csv_data = ( + "device,name,type", + "Device 1,Interface 4,1000BASE-T (1GE)", + "Device 1,Interface 5,1000BASE-T (1GE)", + "Device 1,Interface 6,1000BASE-T (1GE)", + ) From f805b577788ca3ca3f3f4d4905802812615e416e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 4 Feb 2020 18:08:40 -0500 Subject: [PATCH 04/11] Adapt BulkEditView to not require a parent object for device components --- netbox/dcim/forms.py | 12 ++++++++---- netbox/dcim/urls.py | 13 ++++++++----- netbox/dcim/views.py | 5 ----- netbox/templates/dcim/device.html | 10 +++++----- .../templates/virtualization/virtualmachine.html | 2 +- netbox/utilities/forms.py | 3 +-- netbox/utilities/views.py | 14 +++----------- netbox/virtualization/forms.py | 6 ++++-- netbox/virtualization/urls.py | 2 +- 9 files changed, 31 insertions(+), 36 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 723491610..14e13ad44 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2525,7 +2525,11 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): super().__init__(*args, **kwargs) # Limit power_port queryset to PowerPorts which belong to the parent Device - self.fields['power_port'].queryset = PowerPort.objects.filter(device=self.parent_obj) + if 'device' in self.initial: + device = Device.objects.filter(pk=self.initial['device']).first() + self.fields['power_port'].queryset = PowerPort.objects.filter(device=device) + else: + self.fields['power_port'].queryset = PowerPort.objects.none() class PowerOutletBulkRenameForm(BulkRenameForm): @@ -2836,14 +2840,14 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): super().__init__(*args, **kwargs) # Limit LAG choices to interfaces which belong to the parent device (or VC master) - device = self.parent_obj - if device is not None: + if 'device' in self.initial: + device = Device.objects.filter(pk=self.initial['device']).first() self.fields['lag'].queryset = Interface.objects.filter( device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG ) else: - self.fields['lag'].choices = [] + self.fields['lag'].queryset = Interface.objects.none() def clean(self): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index fd2f165d4..c6057b4a3 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -172,6 +172,7 @@ urlpatterns = [ path(r'devices//console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), path(r'console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'), path(r'console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), + # TODO: Bulk edit view for ConsolePorts path(r'console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), path(r'console-ports//connect//', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path(r'console-ports//edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), @@ -181,11 +182,11 @@ urlpatterns = [ # Console server ports path(r'devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), path(r'devices//console-server-ports/add/', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), - path(r'devices//console-server-ports/edit/', views.ConsoleServerPortBulkEditView.as_view(), name='consoleserverport_bulk_edit'), path(r'console-server-ports/', views.ConsoleServerPortListView.as_view(), name='consoleserverport_list'), path(r'console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), path(r'console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), path(r'console-server-ports/import/', views.ConsoleServerPortBulkImportView.as_view(), name='consoleserverport_import'), + path(r'console-server-ports/edit/', views.ConsoleServerPortBulkEditView.as_view(), name='consoleserverport_bulk_edit'), path(r'console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), path(r'console-server-ports//connect//', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path(r'console-server-ports//edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), @@ -197,6 +198,7 @@ urlpatterns = [ path(r'devices//power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), path(r'power-ports/', views.PowerPortListView.as_view(), name='powerport_list'), path(r'power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), + # TODO: Bulk edit view for PowerPorts path(r'power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), path(r'power-ports//connect//', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path(r'power-ports//edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), @@ -206,11 +208,11 @@ urlpatterns = [ # Power outlets path(r'devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), path(r'devices//power-outlets/add/', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), - path(r'devices//power-outlets/edit/', views.PowerOutletBulkEditView.as_view(), name='poweroutlet_bulk_edit'), path(r'power-outlets/', views.PowerOutletListView.as_view(), name='poweroutlet_list'), path(r'power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), path(r'power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), path(r'power-outlets/import/', views.PowerOutletBulkImportView.as_view(), name='poweroutlet_import'), + path(r'power-outlets/edit/', views.PowerOutletBulkEditView.as_view(), name='poweroutlet_bulk_edit'), path(r'power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), path(r'power-outlets//connect//', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path(r'power-outlets//edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), @@ -220,11 +222,11 @@ urlpatterns = [ # Interfaces path(r'devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), path(r'devices//interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), - path(r'devices//interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), path(r'interfaces/', views.InterfaceListView.as_view(), name='interface_list'), path(r'interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), path(r'interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), path(r'interfaces/import/', views.InterfaceBulkImportView.as_view(), name='interface_import'), + path(r'interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), path(r'interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), path(r'interfaces//connect//', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path(r'interfaces//', views.InterfaceView.as_view(), name='interface'), @@ -236,11 +238,11 @@ urlpatterns = [ # Front ports # path(r'devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), path(r'devices//front-ports/add/', views.FrontPortCreateView.as_view(), name='frontport_add'), - path(r'devices//front-ports/edit/', views.FrontPortBulkEditView.as_view(), name='frontport_bulk_edit'), path(r'front-ports/', views.FrontPortListView.as_view(), name='frontport_list'), path(r'front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), path(r'front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), path(r'front-ports/import/', views.FrontPortBulkImportView.as_view(), name='frontport_import'), + path(r'front-ports/edit/', views.FrontPortBulkEditView.as_view(), name='frontport_bulk_edit'), path(r'front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), path(r'front-ports//connect//', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), path(r'front-ports//edit/', views.FrontPortEditView.as_view(), name='frontport_edit'), @@ -250,11 +252,11 @@ urlpatterns = [ # Rear ports # path(r'devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), path(r'devices//rear-ports/add/', views.RearPortCreateView.as_view(), name='rearport_add'), - path(r'devices//rear-ports/edit/', views.RearPortBulkEditView.as_view(), name='rearport_bulk_edit'), path(r'rear-ports/', views.RearPortListView.as_view(), name='rearport_list'), path(r'rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), path(r'rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), path(r'rear-ports/import/', views.RearPortBulkImportView.as_view(), name='rearport_import'), + path(r'rear-ports/edit/', views.RearPortBulkEditView.as_view(), name='rearport_bulk_edit'), path(r'rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), path(r'rear-ports//connect//', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path(r'rear-ports//edit/', views.RearPortEditView.as_view(), name='rearport_edit'), @@ -267,6 +269,7 @@ urlpatterns = [ path(r'device-bays/', views.DeviceBayListView.as_view(), name='devicebay_list'), path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), + # TODO: Bulk edit view for DeviceBays path(r'device-bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), path(r'device-bays//edit/', views.DeviceBayEditView.as_view(), name='devicebay_edit'), path(r'device-bays//delete/', views.DeviceBayDeleteView.as_view(), name='devicebay_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2fdf7f457..9d9223e29 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1282,7 +1282,6 @@ class ConsoleServerPortBulkImportView(PermissionRequiredMixin, BulkImportView): class ConsoleServerPortBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_consoleserverport' queryset = ConsoleServerPort.objects.all() - parent_model = Device table = tables.ConsoleServerPortTable form = forms.ConsoleServerPortBulkEditForm @@ -1398,7 +1397,6 @@ class PowerOutletBulkImportView(PermissionRequiredMixin, BulkImportView): class PowerOutletBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_poweroutlet' queryset = PowerOutlet.objects.all() - parent_model = Device table = tables.PowerOutletTable form = forms.PowerOutletBulkEditForm @@ -1503,7 +1501,6 @@ class InterfaceBulkImportView(PermissionRequiredMixin, BulkImportView): class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_interface' queryset = Interface.objects.all() - parent_model = Device table = tables.InterfaceTable form = forms.InterfaceBulkEditForm @@ -1571,7 +1568,6 @@ class FrontPortBulkImportView(PermissionRequiredMixin, BulkImportView): class FrontPortBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_frontport' queryset = FrontPort.objects.all() - parent_model = Device table = tables.FrontPortTable form = forms.FrontPortBulkEditForm @@ -1639,7 +1635,6 @@ class RearPortBulkImportView(PermissionRequiredMixin, BulkImportView): class RearPortBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rearport' queryset = RearPort.objects.all() - parent_model = Device table = tables.RearPortTable form = forms.RearPortBulkEditForm diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 57c068727..089483354 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -587,7 +587,7 @@ - {% endif %} @@ -649,7 +649,7 @@ - - - - - {% endif %} diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index a6eca7382..77c3dd18c 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -743,10 +743,9 @@ class BulkEditForm(forms.Form): """ Base form for editing multiple objects in bulk """ - def __init__(self, model, parent_obj=None, *args, **kwargs): + def __init__(self, model, *args, **kwargs): super().__init__(*args, **kwargs) self.model = model - self.parent_obj = parent_obj self.nullable_fields = [] # Copy any nullable fields defined in Meta diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index f32c57c3f..bea9450bd 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -604,14 +604,12 @@ class BulkEditView(GetReturnURLMixin, View): Edit objects in bulk. queryset: Custom queryset to use when retrieving objects (e.g. to select related objects) - parent_model: The model of the parent object (if any) filter: FilterSet to apply when deleting by QuerySet table: The table used to display devices being edited form: The form class used to edit objects in bulk template_name: The name of the template """ queryset = None - parent_model = None filterset = None table = None form = None @@ -624,12 +622,6 @@ class BulkEditView(GetReturnURLMixin, View): model = self.queryset.model - # Attempt to derive parent object if a parent class has been given - if self.parent_model: - parent_obj = get_object_or_404(self.parent_model, **kwargs) - else: - parent_obj = None - # Are we editing *all* objects in the queryset or just a selected subset? if request.POST.get('_all') and self.filterset is not None: pk_list = [obj.pk for obj in self.filterset(request.GET, model.objects.only('pk')).qs] @@ -637,7 +629,7 @@ class BulkEditView(GetReturnURLMixin, View): pk_list = [int(pk) for pk in request.POST.getlist('pk')] if '_apply' in request.POST: - form = self.form(model, parent_obj, request.POST) + form = self.form(model, request.POST, initial=request.GET) if form.is_valid(): custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else [] @@ -719,9 +711,9 @@ class BulkEditView(GetReturnURLMixin, View): messages.error(self.request, "{} failed validation: {}".format(obj, e)) else: - initial_data = request.POST.copy() + initial_data = request.GET.copy() initial_data['pk'] = pk_list - form = self.form(model, parent_obj, initial=initial_data) + form = self.form(model, initial=initial_data) # Retrieve objects being edited table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False) diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index b470bace2..f5ecad119 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -881,6 +881,8 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + parent_obj = VirtualMachine.objects.filter(pk=self.initial.get('virtual_machine')).first() + # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site vlan_choices = [] global_vlans = VLAN.objects.filter(site=None, group=None) @@ -892,8 +894,8 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): vlan_choices.append( (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) ) - if self.parent_obj.cluster is not None: - site = getattr(self.parent_obj.cluster, 'site', None) + if parent_obj.cluster is not None: + site = getattr(parent_obj.cluster, 'site', None) if site is not None: # Add non-grouped site VLANs diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 88a8dc6d9..587ec2c34 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -53,7 +53,7 @@ urlpatterns = [ # VM interfaces path(r'virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_interface'), path(r'virtual-machines//interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), - path(r'virtual-machines//interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), + path(r'interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), path(r'interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), # TODO: Rename vm-interfaces to interfaces path(r'vm-interfaces//edit/', views.InterfaceEditView.as_view(), name='interface_edit'), From f8ce67c69f01406b884fcf7dc3c1d84a392c8b87 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 5 Feb 2020 09:15:48 -0500 Subject: [PATCH 05/11] Tweak BulkEditView to improve handling of initial PK values --- netbox/templates/dcim/device.html | 10 +++++----- netbox/utilities/utils.py | 13 +++++++++++++ netbox/utilities/views.py | 25 +++++++++++++++---------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 089483354..1c287cdc8 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -587,7 +587,7 @@ - {% endif %} @@ -649,7 +649,7 @@ - - - - - {% endif %} diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index f5ecad119..f9261e8e2 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -835,6 +835,10 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput() ) + virtual_machine = forms.ModelChoiceField( + queryset=VirtualMachine.objects.all(), + widget=forms.HiddenInput() + ) enabled = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect() @@ -881,37 +885,39 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - parent_obj = VirtualMachine.objects.filter(pk=self.initial.get('virtual_machine')).first() + # Limit available VLANs based on the parent VirtualMachine + if 'virtual_machine' in self.initial: + parent_obj = VirtualMachine.objects.filter(pk=self.initial['virtual_machine']).first() - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) + # Limit VLAN choices to global VLANs, VLANs in global groups, the current site's group, the current site + vlan_choices = [] + global_vlans = VLAN.objects.filter(site=None, group=None) vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) + ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) ) - if parent_obj.cluster is not None: - site = getattr(parent_obj.cluster, 'site', None) - if site is not None: + for group in VLANGroup.objects.filter(site=None): + global_group_vlans = VLAN.objects.filter(group=group) + vlan_choices.append( + (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) + ) + if parent_obj.cluster is not None: + site = getattr(parent_obj.cluster, 'site', None) + if site is not None: - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) + # Add non-grouped site VLANs + site_vlans = VLAN.objects.filter(site=site, group=None) + vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) + # Add grouped site VLANs + for group in VLANGroup.objects.filter(site=site): + site_group_vlans = VLAN.objects.filter(group=group) + vlan_choices.append(( + '{} / {}'.format(group.site.name, group.name), + [(vlan.pk, vlan) for vlan in site_group_vlans] + )) - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices + self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices + self.fields['tagged_vlans'].choices = vlan_choices # From 6b9fa5e76f22343f54bc3aa19a282dd5475bda61 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 5 Feb 2020 11:14:07 -0500 Subject: [PATCH 07/11] Enable tests for component bulk edit views --- netbox/dcim/tests/test_views.py | 55 ++++++++++++++++++----- netbox/utilities/testing/testcases.py | 3 +- netbox/virtualization/tests/test_views.py | 17 +++++-- netbox/virtualization/views.py | 2 - 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index baf80b522..328e0e603 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -726,7 +726,6 @@ class ConsoleServerPortTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -757,6 +756,12 @@ class ConsoleServerPortTestCase(StandardTestCases.Views): "Device 1,Console Server Port 6", ) + cls.bulk_edit_data = { + 'device': device.pk, + 'type': ConsolePortTypeChoices.TYPE_RJ45, + 'description': 'New description', + } + class PowerPortTestCase(StandardTestCases.Views): model = PowerPort @@ -808,7 +813,6 @@ class PowerOutletTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -847,23 +851,32 @@ class PowerOutletTestCase(StandardTestCases.Views): "Device 1,Power Outlet 6", ) + cls.bulk_edit_data = { + 'device': device.pk, + 'type': PowerOutletTypeChoices.TYPE_IEC_C13, + 'power_port': powerports[1].pk, + 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B, + 'description': 'New description', + } + class InterfaceTestCase(StandardTestCases.Views): model = Interface # TODO test_create_object = None - test_bulk_edit_objects = None @classmethod def setUpTestData(cls): device = create_test_device('Device 1') - Interface.objects.bulk_create([ + interfaces = ( Interface(device=device, name='Interface 1'), Interface(device=device, name='Interface 2'), Interface(device=device, name='Interface 3'), - ]) + Interface(device=device, name='LAG', type=InterfaceTypeChoices.TYPE_LAG), + ) + Interface.objects.bulk_create(interfaces) vlans = ( VLAN(vid=1, name='VLAN1', site=device.site), @@ -879,11 +892,11 @@ class InterfaceTestCase(StandardTestCases.Views): 'name': 'Interface X', 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, 'enabled': False, - 'lag': None, + 'lag': interfaces[3].pk, 'mac_address': EUI('01:02:03:04:05:06'), 'mtu': 2000, 'mgmt_only': True, - 'description': 'New description', + 'description': 'A front port', 'mode': InterfaceModeChoices.MODE_TAGGED, 'untagged_vlan': vlans[0].pk, 'tagged_vlans': [v.pk for v in vlans[1:4]], @@ -901,6 +914,20 @@ class InterfaceTestCase(StandardTestCases.Views): "Device 1,Interface 6,1000BASE-T (1GE)", ) + cls.bulk_edit_data = { + 'device': device.pk, + 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, + 'enabled': False, + 'lag': interfaces[3].pk, + 'mac_address': EUI('01:02:03:04:05:06'), + 'mtu': 2000, + 'mgmt_only': True, + 'description': 'New description', + 'mode': InterfaceModeChoices.MODE_TAGGED, + 'untagged_vlan': vlans[0].pk, + 'tagged_vlans': [v.pk for v in vlans[1:4]], + } + class FrontPortTestCase(StandardTestCases.Views): model = FrontPort @@ -910,7 +937,6 @@ class FrontPortTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -952,6 +978,11 @@ class FrontPortTestCase(StandardTestCases.Views): "Device 1,Front Port 6,8P8C,Rear Port 6,1", ) + cls.bulk_edit_data = { + 'type': PortTypeChoices.TYPE_8P8C, + 'description': 'New description', + } + class RearPortTestCase(StandardTestCases.Views): model = RearPort @@ -961,7 +992,6 @@ class RearPortTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -978,7 +1008,7 @@ class RearPortTestCase(StandardTestCases.Views): 'name': 'Rear Port X', 'type': PortTypeChoices.TYPE_8P8C, 'positions': 3, - 'description': 'New description', + 'description': 'A rear port', 'tags': 'Alpha,Bravo,Charlie', # Extraneous model fields @@ -992,6 +1022,11 @@ class RearPortTestCase(StandardTestCases.Views): "Device 1,Rear Port 6,8P8C,1", ) + cls.bulk_edit_data = { + 'type': PortTypeChoices.TYPE_8P8C, + 'description': 'New description', + } + class DeviceBayTestCase(StandardTestCases.Views): model = DeviceBay diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index 094629676..7f9d6259e 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -270,7 +270,8 @@ class StandardTestCases: @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_bulk_edit_objects(self): - pk_list = self.model.objects.values_list('pk', flat=True) + # Bulk edit the first three objects only + pk_list = self.model.objects.values_list('pk', flat=True)[:3] request = { 'path': self._get_url('bulk_edit'), diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 40a3b6c53..35ab03269 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -202,7 +202,6 @@ class InterfaceTestCase(StandardTestCases.Views): # TODO test_create_object = None - test_bulk_edit_objects = None def _get_base_url(self): # Interface belongs to the DCIM app, so we have to override the base URL @@ -222,9 +221,9 @@ class InterfaceTestCase(StandardTestCases.Views): VirtualMachine.objects.bulk_create(virtualmachines) Interface.objects.bulk_create([ - Interface(virtual_machine=virtualmachines[0], name='Interface 1'), - Interface(virtual_machine=virtualmachines[0], name='Interface 2'), - Interface(virtual_machine=virtualmachines[0], name='Interface 3'), + Interface(virtual_machine=virtualmachines[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL), + Interface(virtual_machine=virtualmachines[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL), + Interface(virtual_machine=virtualmachines[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL), ]) vlans = ( @@ -262,3 +261,13 @@ class InterfaceTestCase(StandardTestCases.Views): "Device 1,Interface 5,1000BASE-T (1GE)", "Device 1,Interface 6,1000BASE-T (1GE)", ) + + cls.bulk_edit_data = { + 'virtual_machine': virtualmachines[1].pk, + 'enabled': False, + 'mtu': 2000, + 'description': 'New description', + 'mode': InterfaceModeChoices.MODE_TAGGED, + # 'untagged_vlan': vlans[0].pk, + # 'tagged_vlans': [v.pk for v in vlans[1:4]], + } diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 646fb000d..4198fbe40 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -353,7 +353,6 @@ class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView): class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_interface' queryset = Interface.objects.all() - parent_model = VirtualMachine table = tables.InterfaceTable form = forms.InterfaceBulkEditForm @@ -361,7 +360,6 @@ class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_interface' queryset = Interface.objects.all() - parent_model = VirtualMachine table = tables.InterfaceTable From 75906f7591de19c15c4d12714189dbe3d5231bc5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 5 Feb 2020 12:36:38 -0500 Subject: [PATCH 08/11] Move component bulk creation views to new URLs --- netbox/dcim/forms.py | 58 ++++++++++++++----- netbox/dcim/urls.py | 17 +++--- netbox/dcim/views.py | 16 ----- netbox/templates/dcim/device.html | 48 ++++++++++----- .../templates/dcim/device_component_add.html | 9 +-- .../virtualization/virtualmachine.html | 2 +- .../virtualmachine_component_add.html | 2 +- netbox/utilities/forms.py | 5 +- netbox/utilities/views.py | 31 ++++------ netbox/virtualization/forms.py | 16 ++--- netbox/virtualization/urls.py | 2 +- netbox/virtualization/views.py | 2 - 12 files changed, 111 insertions(+), 97 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4d332e904..8222d3d26 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2180,6 +2180,10 @@ class ConsolePortForm(BootstrapMixin, forms.ModelForm): class ConsolePortCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -2238,6 +2242,10 @@ class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm): class ConsoleServerPortCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -2331,6 +2339,10 @@ class PowerPortForm(BootstrapMixin, forms.ModelForm): class PowerPortCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -2412,6 +2424,10 @@ class PowerOutletForm(BootstrapMixin, forms.ModelForm): class PowerOutletCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -2437,11 +2453,12 @@ class PowerOutletCreateForm(ComponentForm): ) def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Limit power_port choices to those on the parent device - self.fields['power_port'].queryset = PowerPort.objects.filter(device=self.parent) + # Limit power_port queryset to PowerPorts which belong to the parent Device + if 'device' in self.initial: + device = Device.objects.filter(pk=self.initial['device']).first() + self.fields['power_port'].queryset = PowerPort.objects.filter(device=device) class PowerOutletCSVForm(forms.ModelForm): @@ -2631,7 +2648,8 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=StaticSelect2(), ) enabled = forms.BooleanField( - required=False + required=False, + initial=True ) lag = forms.ModelChoiceField( queryset=Interface.objects.all(), @@ -2686,21 +2704,15 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): ) def __init__(self, *args, **kwargs): - - # Set interfaces enabled by default - kwargs['initial'] = kwargs.get('initial', {}).copy() - kwargs['initial'].update({'enabled': True}) - super().__init__(*args, **kwargs) - # Limit LAG choices to interfaces belonging to this device (or its VC master) - if self.parent is not None: + # Limit LAG choices to interfaces which belong to the parent device (or VC master) + if 'device' in self.initial: + device = Device.objects.filter(pk=self.initial['device']).first() self.fields['lag'].queryset = Interface.objects.filter( - device__in=[self.parent, self.parent.get_vc_master()], + device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG ) - else: - self.fields['lag'].queryset = Interface.objects.none() class InterfaceCSVForm(forms.ModelForm): @@ -2917,6 +2929,10 @@ class FrontPortForm(BootstrapMixin, forms.ModelForm): # TODO: Merge with FrontPortTemplateCreateForm to remove duplicate logic class FrontPortCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -2936,15 +2952,17 @@ class FrontPortCreateForm(ComponentForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + parent = Device.objects.get(pk=self.initial['device']) + # Determine which rear port positions are occupied. These will be excluded from the list of available mappings. occupied_port_positions = [ (front_port.rear_port_id, front_port.rear_port_position) - for front_port in self.parent.frontports.all() + for front_port in parent.frontports.all() ] # Populate rear port choices choices = [] - rear_ports = RearPort.objects.filter(device=self.parent) + rear_ports = RearPort.objects.filter(device=parent) for rear_port in rear_ports: for i in range(1, rear_port.positions + 1): if (rear_port.pk, i) not in occupied_port_positions: @@ -3084,6 +3102,10 @@ class RearPortForm(BootstrapMixin, forms.ModelForm): class RearPortCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) @@ -3688,6 +3710,10 @@ class DeviceBayForm(BootstrapMixin, forms.ModelForm): class DeviceBayCreateForm(ComponentForm): + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + widget=forms.HiddenInput() + ) name_pattern = ExpandableNameField( label='Name' ) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c6057b4a3..121beff97 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -169,8 +169,8 @@ urlpatterns = [ # Console ports path(r'devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), - path(r'devices//console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), path(r'console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'), + path(r'console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), path(r'console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), # TODO: Bulk edit view for ConsolePorts path(r'console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), @@ -181,10 +181,10 @@ urlpatterns = [ # Console server ports path(r'devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), - path(r'devices//console-server-ports/add/', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), path(r'console-server-ports/', views.ConsoleServerPortListView.as_view(), name='consoleserverport_list'), path(r'console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), path(r'console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), + path(r'console-server-ports/add/', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), path(r'console-server-ports/import/', views.ConsoleServerPortBulkImportView.as_view(), name='consoleserverport_import'), path(r'console-server-ports/edit/', views.ConsoleServerPortBulkEditView.as_view(), name='consoleserverport_bulk_edit'), path(r'console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), @@ -195,8 +195,8 @@ urlpatterns = [ # Power ports path(r'devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), - path(r'devices//power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), path(r'power-ports/', views.PowerPortListView.as_view(), name='powerport_list'), + path(r'power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), path(r'power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), # TODO: Bulk edit view for PowerPorts path(r'power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), @@ -207,10 +207,10 @@ urlpatterns = [ # Power outlets path(r'devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), - path(r'devices//power-outlets/add/', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), path(r'power-outlets/', views.PowerOutletListView.as_view(), name='poweroutlet_list'), path(r'power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), path(r'power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), + path(r'power-outlets/add/', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), path(r'power-outlets/import/', views.PowerOutletBulkImportView.as_view(), name='poweroutlet_import'), path(r'power-outlets/edit/', views.PowerOutletBulkEditView.as_view(), name='poweroutlet_bulk_edit'), path(r'power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), @@ -221,10 +221,10 @@ urlpatterns = [ # Interfaces path(r'devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), - path(r'devices//interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), path(r'interfaces/', views.InterfaceListView.as_view(), name='interface_list'), path(r'interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), path(r'interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), + path(r'interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'), path(r'interfaces/import/', views.InterfaceBulkImportView.as_view(), name='interface_import'), path(r'interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), path(r'interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), @@ -237,10 +237,10 @@ urlpatterns = [ # Front ports # path(r'devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), - path(r'devices//front-ports/add/', views.FrontPortCreateView.as_view(), name='frontport_add'), path(r'front-ports/', views.FrontPortListView.as_view(), name='frontport_list'), path(r'front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), path(r'front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), + path(r'front-ports/add/', views.FrontPortCreateView.as_view(), name='frontport_add'), path(r'front-ports/import/', views.FrontPortBulkImportView.as_view(), name='frontport_import'), path(r'front-ports/edit/', views.FrontPortBulkEditView.as_view(), name='frontport_bulk_edit'), path(r'front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), @@ -251,10 +251,10 @@ urlpatterns = [ # Rear ports # path(r'devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), - path(r'devices//rear-ports/add/', views.RearPortCreateView.as_view(), name='rearport_add'), path(r'rear-ports/', views.RearPortListView.as_view(), name='rearport_list'), path(r'rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), path(r'rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), + path(r'rear-ports/add/', views.RearPortCreateView.as_view(), name='rearport_add'), path(r'rear-ports/import/', views.RearPortBulkImportView.as_view(), name='rearport_import'), path(r'rear-ports/edit/', views.RearPortBulkEditView.as_view(), name='rearport_bulk_edit'), path(r'rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), @@ -265,9 +265,9 @@ urlpatterns = [ # Device bays path(r'devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'), - path(r'devices//bays/add/', views.DeviceBayCreateView.as_view(), name='devicebay_add'), path(r'device-bays/', views.DeviceBayListView.as_view(), name='devicebay_list'), path(r'device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), + path(r'device-bays/add/', views.DeviceBayCreateView.as_view(), name='devicebay_add'), path(r'device-bays/import/', views.DeviceBayBulkImportView.as_view(), name='devicebay_import'), # TODO: Bulk edit view for DeviceBays path(r'device-bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), @@ -283,6 +283,7 @@ urlpatterns = [ path(r'inventory-items/delete/', views.InventoryItemBulkDeleteView.as_view(), name='inventoryitem_bulk_delete'), path(r'inventory-items//edit/', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'), path(r'inventory-items//delete/', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'), + # TODO: Replace below with InventoryItemCreateView path(r'devices//inventory-items/add/', views.InventoryItemEditView.as_view(), name='inventoryitem_add'), # Cables diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 9d9223e29..6e7018fb5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1205,8 +1205,6 @@ class ConsolePortListView(PermissionRequiredMixin, ObjectListView): class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleport' - parent_model = Device - parent_field = 'device' model = ConsolePort form = forms.ConsolePortCreateForm model_form = forms.ConsolePortForm @@ -1253,8 +1251,6 @@ class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView): class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleserverport' - parent_model = Device - parent_field = 'device' model = ConsoleServerPort form = forms.ConsoleServerPortCreateForm model_form = forms.ConsoleServerPortForm @@ -1320,8 +1316,6 @@ class PowerPortListView(PermissionRequiredMixin, ObjectListView): class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_powerport' - parent_model = Device - parent_field = 'device' model = PowerPort form = forms.PowerPortCreateForm model_form = forms.PowerPortForm @@ -1368,8 +1362,6 @@ class PowerOutletListView(PermissionRequiredMixin, ObjectListView): class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_poweroutlet' - parent_model = Device - parent_field = 'device' model = PowerOutlet form = forms.PowerOutletCreateForm model_form = forms.PowerOutletForm @@ -1471,8 +1463,6 @@ class InterfaceView(PermissionRequiredMixin, View): class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_interface' - parent_model = Device - parent_field = 'device' model = Interface form = forms.InterfaceCreateForm model_form = forms.InterfaceForm @@ -1539,8 +1529,6 @@ class FrontPortListView(PermissionRequiredMixin, ObjectListView): class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_frontport' - parent_model = Device - parent_field = 'device' model = FrontPort form = forms.FrontPortCreateForm model_form = forms.FrontPortForm @@ -1606,8 +1594,6 @@ class RearPortListView(PermissionRequiredMixin, ObjectListView): class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_rearport' - parent_model = Device - parent_field = 'device' model = RearPort form = forms.RearPortCreateForm model_form = forms.RearPortForm @@ -1675,8 +1661,6 @@ class DeviceBayListView(PermissionRequiredMixin, ObjectListView): class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_devicebay' - parent_model = Device - parent_field = 'device' model = DeviceBay form = forms.DeviceBayCreateForm model_form = forms.DeviceBayForm diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index c36671349..5ede19d78 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -48,14 +48,30 @@ Add Components {% endif %} @@ -333,12 +349,12 @@ {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}