diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 1e065f458..d2cb8e5ab 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -36,6 +36,15 @@ class ProviderTestCase(StandardTestCases.Views): "Provider 6,provider-6", ) + cls.bulk_edit_data = { + 'asn': 65009, + 'account': '5678', + 'portal_url': 'http://example.com/portal2', + 'noc_contact': 'noc2@example.com', + 'admin_contact': 'admin2@example.com', + 'comments': 'New comments', + } + class CircuitTypeTestCase(StandardTestCases.Views): model = CircuitType @@ -43,6 +52,7 @@ class CircuitTypeTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -73,23 +83,29 @@ class CircuitTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - provider = Provider(name='Provider 1', slug='provider-1', asn=65001) - provider.save() + providers = ( + Provider(name='Provider 1', slug='provider-1', asn=65001), + Provider(name='Provider 2', slug='provider-2', asn=65002), + ) + Provider.objects.bulk_create(providers) - circuittype = CircuitType(name='Circuit Type 1', slug='circuit-type-1') - circuittype.save() + circuittypes = ( + CircuitType(name='Circuit Type 1', slug='circuit-type-1'), + CircuitType(name='Circuit Type 2', slug='circuit-type-2'), + ) + CircuitType.objects.bulk_create(circuittypes) Circuit.objects.bulk_create([ - Circuit(cid='Circuit 1', provider=provider, type=circuittype), - Circuit(cid='Circuit 2', provider=provider, type=circuittype), - Circuit(cid='Circuit 3', provider=provider, type=circuittype), + Circuit(cid='Circuit 1', provider=providers[0], type=circuittypes[0]), + Circuit(cid='Circuit 2', provider=providers[0], type=circuittypes[0]), + Circuit(cid='Circuit 3', provider=providers[0], type=circuittypes[0]), ]) cls.form_data = { 'cid': 'Circuit X', - 'provider': provider.pk, - 'type': circuittype.pk, - 'status': CircuitStatusChoices.STATUS_ACTIVE, + 'provider': providers[1].pk, + 'type': circuittypes[1].pk, + 'status': CircuitStatusChoices.STATUS_DECOMMISSIONED, 'tenant': None, 'install_date': datetime.date(2020, 1, 1), 'commit_rate': 1000, @@ -104,3 +120,14 @@ class CircuitTestCase(StandardTestCases.Views): "Circuit 5,Provider 1,Circuit Type 1", "Circuit 6,Provider 1,Circuit Type 1", ) + + cls.bulk_edit_data = { + 'provider': providers[1].pk, + 'type': circuittypes[1].pk, + 'status': CircuitStatusChoices.STATUS_DECOMMISSIONED, + 'tenant': None, + 'commit_rate': 2000, + 'description': 'New description', + 'comments': 'New comments', + + } diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 34c0f38b4..ab5400f56 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -19,6 +19,7 @@ class RegionTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -52,20 +53,24 @@ class SiteTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - region = Region(name='Region 1', slug='region-1') - region.save() + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + ) + for region in regions: + region.save() Site.objects.bulk_create([ - Site(name='Site 1', slug='site-1', region=region), - Site(name='Site 2', slug='site-2', region=region), - Site(name='Site 3', slug='site-3', region=region), + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[0]), + Site(name='Site 3', slug='site-3', region=regions[0]), ]) cls.form_data = { 'name': 'Site X', 'slug': 'site-x', 'status': SiteStatusChoices.STATUS_PLANNED, - 'region': region.pk, + 'region': regions[1].pk, 'tenant': None, 'facility': 'Facility X', 'asn': 65001, @@ -89,6 +94,15 @@ class SiteTestCase(StandardTestCases.Views): "Site 6,site-6", ) + cls.bulk_edit_data = { + 'status': SiteStatusChoices.STATUS_PLANNED, + 'region': regions[1].pk, + 'tenant': None, + 'asn': 65009, + 'time_zone': pytz.timezone('US/Eastern'), + 'description': 'New description', + } + class RackGroupTestCase(StandardTestCases.Views): model = RackGroup @@ -96,6 +110,7 @@ class RackGroupTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -129,6 +144,7 @@ class RackRoleTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -159,32 +175,40 @@ class RackReservationTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None - test_create_object = None # TODO: Fix URL name for view + test_create_object = None + + # TODO: Fix URL name for view test_import_objects = None @classmethod def setUpTestData(cls): - user = User.objects.create_user(username='testuser2') + user2 = User.objects.create_user(username='testuser2') + user3 = User.objects.create_user(username='testuser3') - site = Site(name='Site 1', slug='site-1') - site.save() + site = Site.objects.create(name='Site 1', slug='site-1') rack = Rack(name='Rack 1', site=site) rack.save() RackReservation.objects.bulk_create([ - RackReservation(rack=rack, user=user, units=[1, 2, 3], description='Reservation 1'), - RackReservation(rack=rack, user=user, units=[4, 5, 6], description='Reservation 2'), - RackReservation(rack=rack, user=user, units=[7, 8, 9], description='Reservation 3'), + RackReservation(rack=rack, user=user2, units=[1, 2, 3], description='Reservation 1'), + RackReservation(rack=rack, user=user2, units=[4, 5, 6], description='Reservation 2'), + RackReservation(rack=rack, user=user2, units=[7, 8, 9], description='Reservation 3'), ]) cls.form_data = { 'rack': rack.pk, 'units': [10, 11, 12], - 'user': user.pk, + 'user': user3.pk, 'tenant': None, - 'description': 'New reservation', + 'description': 'Rack reservation', + } + + cls.bulk_edit_data = { + 'user': user3.pk, + 'tenant': None, + 'description': 'New description', } @@ -194,24 +218,38 @@ class RackTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - site = Site.objects.create(name='Site 1', slug='site-1') - rackgroup = RackGroup.objects.create(name='Rack Group 1', slug='rack-group-1', site=site) - rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1') + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) - Rack.objects.bulk_create([ - Rack(name='Rack 1', site=site), - Rack(name='Rack 2', site=site), - Rack(name='Rack 3', site=site), - ]) + rackgroups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]) + ) + RackGroup.objects.bulk_create(rackgroups) + + rackroles = ( + RackRole(name='Rack Role 1', slug='rack-role-1'), + RackRole(name='Rack Role 2', slug='rack-role-2'), + ) + RackRole.objects.bulk_create(rackroles) + + Rack.objects.bulk_create(( + Rack(name='Rack 1', site=sites[0]), + Rack(name='Rack 2', site=sites[0]), + Rack(name='Rack 3', site=sites[0]), + )) cls.form_data = { 'name': 'Rack X', 'facility_id': 'Facility X', - 'site': site.pk, - 'group': rackgroup.pk, + 'site': sites[1].pk, + 'group': rackgroups[1].pk, 'tenant': None, 'status': RackStatusChoices.STATUS_PLANNED, - 'role': rackrole.pk, + 'role': rackroles[1].pk, 'serial': '123456', 'asset_tag': 'ABCDEF', 'type': RackTypeChoices.TYPE_CABINET, @@ -232,6 +270,23 @@ class RackTestCase(StandardTestCases.Views): "Site 1,Rack 6,19,42", ) + cls.bulk_edit_data = { + 'site': sites[1].pk, + 'group': rackgroups[1].pk, + 'tenant': None, + 'status': RackStatusChoices.STATUS_DEPRECATED, + 'role': rackroles[1].pk, + 'serial': '654321', + 'type': RackTypeChoices.TYPE_4POST, + 'width': RackWidthChoices.WIDTH_23IN, + 'u_height': 49, + 'desc_units': True, + 'outer_width': 30, + 'outer_depth': 30, + 'outer_unit': RackDimensionUnitChoices.UNIT_INCH, + 'comments': 'New comments', + } + class ManufacturerTestCase(StandardTestCases.Views): model = Manufacturer @@ -239,6 +294,7 @@ class ManufacturerTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -268,17 +324,20 @@ class DeviceTypeTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') - manufacturer.save() + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2') + ) + Manufacturer.objects.bulk_create(manufacturers) DeviceType.objects.bulk_create([ - DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturer), - DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturer), - DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturer), + DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturers[0]), + DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturers[0]), + DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturers[0]), ]) cls.form_data = { - 'manufacturer': manufacturer.pk, + 'manufacturer': manufacturers[1].pk, 'model': 'Device Type X', 'slug': 'device-type-x', 'part_number': '123ABC', @@ -289,6 +348,12 @@ class DeviceTypeTestCase(StandardTestCases.Views): 'tags': 'Alpha,Bravo,Charlie', } + cls.bulk_edit_data = { + 'manufacturer': manufacturers[1].pk, + 'u_height': 3, + 'is_full_depth': False, + } + def test_import_objects(self): """ Custom import test for YAML-based imports (versus CSV) @@ -451,6 +516,7 @@ class DeviceRoleTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -483,6 +549,7 @@ class PlatformTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -517,29 +584,54 @@ class DeviceTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - site = Site.objects.create(name='Site 1', slug='site-1') - rack = Rack.objects.create(name='Rack 1', site=site) + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + racks = ( + Rack(name='Rack 1', site=sites[0]), + Rack(name='Rack 2', site=sites[1]), + ) + Rack.objects.bulk_create(racks) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) - devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') - platform = Platform.objects.create(name='Platform 1', slug='platform-1') + + devicetypes = ( + DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturer), + DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturer), + ) + DeviceType.objects.bulk_create(devicetypes) + + deviceroles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + ) + DeviceRole.objects.bulk_create(deviceroles) + + platforms = ( + Platform(name='Platform 1', slug='platform-1'), + Platform(name='Platform 2', slug='platform-2'), + ) + Platform.objects.bulk_create(platforms) Device.objects.bulk_create([ - Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole), - Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole), - Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 1', site=sites[0], rack=racks[0], device_type=devicetypes[0], device_role=deviceroles[0], platform=platforms[0]), + Device(name='Device 2', site=sites[0], rack=racks[0], device_type=devicetypes[0], device_role=deviceroles[0], platform=platforms[0]), + Device(name='Device 3', site=sites[0], rack=racks[0], device_type=devicetypes[0], device_role=deviceroles[0], platform=platforms[0]), ]) cls.form_data = { - 'device_type': devicetype.pk, - 'device_role': devicerole.pk, + 'device_type': devicetypes[1].pk, + 'device_role': deviceroles[1].pk, 'tenant': None, - 'platform': platform.pk, + 'platform': platforms[1].pk, 'name': 'Device X', 'serial': '123456', 'asset_tag': 'ABCDEF', - 'site': site.pk, - 'rack': rack.pk, + 'site': sites[1].pk, + 'rack': racks[1].pk, 'position': 1, 'face': DeviceFaceChoices.FACE_FRONT, 'status': DeviceStatusChoices.STATUS_PLANNED, @@ -561,6 +653,15 @@ class DeviceTestCase(StandardTestCases.Views): "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 6", ) + cls.bulk_edit_data = { + 'device_type': devicetypes[1].pk, + 'device_role': deviceroles[1].pk, + 'tenant': None, + 'platform': platforms[1].pk, + 'serial': '123456', + 'status': DeviceStatusChoices.STATUS_DECOMMISSIONING, + } + # TODO: Convert to StandardTestCases.Views class ConsolePortTestCase(TestCase): @@ -1071,28 +1172,28 @@ class CableTestCase(StandardTestCases.Views): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') - device1 = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) - device1.save() - device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole) - device2.save() - device3 = Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole) - device3.save() - device4 = Device(name='Device 4', site=site, device_type=devicetype, device_role=devicerole) - device4.save() + + devices = ( + Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 4', site=site, device_type=devicetype, device_role=devicerole), + ) + Device.objects.bulk_create(devices) interfaces = ( - Interface(device=device1, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device1, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device1, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device2, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device2, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device2, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device3, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device3, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device3, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device4, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device4, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=device4, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[2], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[2], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), ) Interface.objects.bulk_create(interfaces) @@ -1109,7 +1210,7 @@ class CableTestCase(StandardTestCases.Views): 'termination_b_id': interfaces[3].pk, 'type': CableTypeChoices.TYPE_CAT6, 'status': CableStatusChoices.STATUS_PLANNED, - 'label': 'New cable', + 'label': 'Label', 'color': 'c0c0c0', 'length': 100, 'length_unit': CableLengthUnitChoices.UNIT_FOOT, @@ -1122,6 +1223,15 @@ class CableTestCase(StandardTestCases.Views): "Device 3,interface,Interface 3,Device 4,interface,Interface 3", ) + cls.bulk_edit_data = { + 'type': CableTypeChoices.TYPE_CAT5E, + 'status': CableStatusChoices.STATUS_CONNECTED, + 'label': 'New label', + 'color': '00ff00', + 'length': 50, + 'length_unit': CableLengthUnitChoices.UNIT_METER, + } + class VirtualChassisTestCase(StandardTestCases.Views): model = VirtualChassis @@ -1129,6 +1239,7 @@ class VirtualChassisTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_import_objects = None + test_bulk_edit_objects = None test_bulk_delete_objects = None # TODO: Requires special form handling diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 0fc60dcd9..ecb25a78c 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -33,6 +33,10 @@ class TagTestCase(StandardTestCases.Views): 'comments': 'Some comments', } + cls.bulk_edit_data = { + 'color': '00ff00', + } + class ConfigContextTestCase(StandardTestCases.Views): model = ConfigContext @@ -53,7 +57,7 @@ class ConfigContextTestCase(StandardTestCases.Views): for i in range(1, 4): configcontext = ConfigContext( name='Config Context {}'.format(i), - data='{{"foo": {}}}'.format(i) + data={'foo': i} ) configcontext.save() configcontext.sites.add(site) @@ -73,7 +77,14 @@ class ConfigContextTestCase(StandardTestCases.Views): 'data': '{"foo": 123}', } + cls.bulk_edit_data = { + 'weight': 300, + 'is_active': False, + 'description': 'New description', + } + +# TODO: Convert to StandardTestCases.Views class ObjectChangeTestCase(TestCase): user_permissions = ( 'extras.view_objectchange', diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index db8326fbd..cfa06788c 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -36,6 +36,12 @@ class VRFTestCase(StandardTestCases.Views): "VRF 6", ) + cls.bulk_edit_data = { + 'tenant': None, + 'enforce_unique': False, + 'description': 'New description', + } + class RIRTestCase(StandardTestCases.Views): model = RIR @@ -43,6 +49,7 @@ class RIRTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -73,18 +80,22 @@ class AggregateTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - rir = RIR.objects.create(name='RIR 1', slug='rir-1') + rirs = ( + RIR(name='RIR 1', slug='rir-1'), + RIR(name='RIR 2', slug='rir-2'), + ) + RIR.objects.bulk_create(rirs) Aggregate.objects.bulk_create([ - Aggregate(family=4, prefix=IPNetwork('10.1.0.0/16'), rir=rir), - Aggregate(family=4, prefix=IPNetwork('10.2.0.0/16'), rir=rir), - Aggregate(family=4, prefix=IPNetwork('10.3.0.0/16'), rir=rir), + Aggregate(family=4, prefix=IPNetwork('10.1.0.0/16'), rir=rirs[0]), + Aggregate(family=4, prefix=IPNetwork('10.2.0.0/16'), rir=rirs[0]), + Aggregate(family=4, prefix=IPNetwork('10.3.0.0/16'), rir=rirs[0]), ]) cls.form_data = { 'family': 4, 'prefix': IPNetwork('10.99.0.0/16'), - 'rir': rir.pk, + 'rir': rirs[1].pk, 'date_added': datetime.date(2020, 1, 1), 'description': 'A new aggregate', 'tags': 'Alpha,Bravo,Charlie', @@ -97,6 +108,12 @@ class AggregateTestCase(StandardTestCases.Views): "10.6.0.0/16,RIR 1", ) + cls.bulk_edit_data = { + 'rir': rirs[1].pk, + 'date_added': datetime.date(2020, 1, 1), + 'description': 'New description', + } + class RoleTestCase(StandardTestCases.Views): model = Role @@ -104,6 +121,7 @@ class RoleTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -135,25 +153,37 @@ class PrefixTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - site = Site.objects.create(name='Site 1', slug='site-1') - vrf = VRF.objects.create(name='VRF 1', rd='65000:1') - role = Role.objects.create(name='Role 1', slug='role-1') - # vlan = VLAN.objects.create(vid=123, name='VLAN 123') + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + vrfs = ( + VRF(name='VRF 1', rd='65000:1'), + VRF(name='VRF 2', rd='65000:2'), + ) + VRF.objects.bulk_create(vrfs) + + roles = ( + Role(name='Role 1', slug='role-1'), + Role(name='Role 2', slug='role-2'), + ) Prefix.objects.bulk_create([ - Prefix(family=4, prefix=IPNetwork('10.1.0.0/16'), site=site), - Prefix(family=4, prefix=IPNetwork('10.2.0.0/16'), site=site), - Prefix(family=4, prefix=IPNetwork('10.3.0.0/16'), site=site), + Prefix(family=4, prefix=IPNetwork('10.1.0.0/16'), vrf=vrfs[0], site=sites[0], role=roles[0]), + Prefix(family=4, prefix=IPNetwork('10.2.0.0/16'), vrf=vrfs[0], site=sites[0], role=roles[0]), + Prefix(family=4, prefix=IPNetwork('10.3.0.0/16'), vrf=vrfs[0], site=sites[0], role=roles[0]), ]) cls.form_data = { 'prefix': IPNetwork('192.0.2.0/24'), - 'site': site.pk, - 'vrf': vrf.pk, + 'site': sites[1].pk, + 'vrf': vrfs[1].pk, 'tenant': None, 'vlan': None, 'status': PrefixStatusChoices.STATUS_RESERVED, - 'role': role.pk, + 'role': roles[1].pk, 'is_pool': True, 'description': 'A new prefix', 'tags': 'Alpha,Bravo,Charlie', @@ -166,6 +196,16 @@ class PrefixTestCase(StandardTestCases.Views): "10.6.0.0/16,Active", ) + cls.bulk_edit_data = { + 'site': sites[1].pk, + 'vrf': vrfs[1].pk, + 'tenant': None, + 'status': PrefixStatusChoices.STATUS_RESERVED, + 'role': roles[1].pk, + 'is_pool': False, + 'description': 'New description', + } + class IPAddressTestCase(StandardTestCases.Views): model = IPAddress @@ -173,16 +213,19 @@ class IPAddressTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - vrf = VRF.objects.create(name='VRF 1', rd='65000:1') + vrfs = ( + VRF(name='VRF 1', rd='65000:1'), + VRF(name='VRF 2', rd='65000:2'), + ) IPAddress.objects.bulk_create([ - IPAddress(family=4, address=IPNetwork('192.0.2.1/24'), vrf=vrf), - IPAddress(family=4, address=IPNetwork('192.0.2.2/24'), vrf=vrf), - IPAddress(family=4, address=IPNetwork('192.0.2.3/24'), vrf=vrf), + IPAddress(family=4, address=IPNetwork('192.0.2.1/24'), vrf=vrfs[0]), + IPAddress(family=4, address=IPNetwork('192.0.2.2/24'), vrf=vrfs[0]), + IPAddress(family=4, address=IPNetwork('192.0.2.3/24'), vrf=vrfs[0]), ]) cls.form_data = { - 'vrf': vrf.pk, + 'vrf': vrfs[1].pk, 'address': IPNetwork('192.0.2.99/24'), 'tenant': None, 'status': IPAddressStatusChoices.STATUS_RESERVED, @@ -201,6 +244,15 @@ class IPAddressTestCase(StandardTestCases.Views): "192.0.2.6/24,Active", ) + cls.bulk_edit_data = { + 'vrf': vrfs[1].pk, + 'tenant': None, + 'status': IPAddressStatusChoices.STATUS_RESERVED, + 'role': IPAddressRoleChoices.ROLE_ANYCAST, + 'dns_name': 'example', + 'description': 'New description', + } + class VLANGroupTestCase(StandardTestCases.Views): model = VLANGroup @@ -208,6 +260,7 @@ class VLANGroupTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -240,24 +293,38 @@ class VLANTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - site = Site.objects.create(name='Site 1', slug='site-1') - vlangroup = VLANGroup.objects.create(name='VLAN Group 1', slug='vlan-group-1', site=site) - role = Role.objects.create(name='Role 1', slug='role-1') + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + vlangroups = ( + VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=sites[0]), + VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=sites[1]), + ) + VLANGroup.objects.bulk_create(vlangroups) + + roles = ( + Role(name='Role 1', slug='role-1'), + Role(name='Role 2', slug='role-2'), + ) + Role.objects.bulk_create(roles) VLAN.objects.bulk_create([ - VLAN(group=vlangroup, vid=101, name='VLAN101'), - VLAN(group=vlangroup, vid=102, name='VLAN102'), - VLAN(group=vlangroup, vid=103, name='VLAN103'), + VLAN(group=vlangroups[0], vid=101, name='VLAN101', site=sites[0], role=roles[0]), + VLAN(group=vlangroups[0], vid=102, name='VLAN102', site=sites[0], role=roles[0]), + VLAN(group=vlangroups[0], vid=103, name='VLAN103', site=sites[0], role=roles[0]), ]) cls.form_data = { - 'site': site.pk, - 'group': vlangroup.pk, + 'site': sites[1].pk, + 'group': vlangroups[1].pk, 'vid': 999, 'name': 'VLAN999', 'tenant': None, 'status': VLANStatusChoices.STATUS_RESERVED, - 'role': role.pk, + 'role': roles[1].pk, 'description': 'A new VLAN', 'tags': 'Alpha,Bravo,Charlie', } @@ -269,6 +336,15 @@ class VLANTestCase(StandardTestCases.Views): "106,VLAN106,Active", ) + cls.bulk_edit_data = { + 'site': sites[1].pk, + 'group': vlangroups[1].pk, + 'tenant': None, + 'status': VLANStatusChoices.STATUS_RESERVED, + 'role': roles[1].pk, + 'description': 'New description', + } + class ServiceTestCase(StandardTestCases.Views): model = Service @@ -304,3 +380,9 @@ class ServiceTestCase(StandardTestCases.Views): 'description': 'A new service', 'tags': 'Alpha,Bravo,Charlie', } + + cls.bulk_edit_data = { + 'protocol': ServiceProtocolChoices.PROTOCOL_UDP, + 'port': 888, + 'description': 'New description', + } diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 1da689e53..94f4cbd6a 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -14,6 +14,7 @@ class SecretRoleTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -56,21 +57,38 @@ class SecretTestCase(StandardTestCases.Views): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') - device = Device.objects.create(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) - secretrole = SecretRole.objects.create(name='Secret Role 1', slug='secret-role-1') - Secret.objects.bulk_create([ - Secret(device=device, role=secretrole, name='Secret 1', ciphertext=b'1234567890'), - Secret(device=device, role=secretrole, name='Secret 2', ciphertext=b'1234567890'), - Secret(device=device, role=secretrole, name='Secret 3', ciphertext=b'1234567890'), - ]) + devices = ( + Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole), + Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole), + ) + Device.objects.bulk_create(devices) + + secretroles = ( + SecretRole(name='Secret Role 1', slug='secret-role-1'), + SecretRole(name='Secret Role 2', slug='secret-role-2'), + ) + SecretRole.objects.bulk_create(secretroles) + + # Create one secret per device to allow bulk-editing of names (which must be unique per device/role) + Secret.objects.bulk_create(( + Secret(device=devices[0], role=secretroles[0], name='Secret 1', ciphertext=b'1234567890'), + Secret(device=devices[1], role=secretroles[0], name='Secret 2', ciphertext=b'1234567890'), + Secret(device=devices[2], role=secretroles[0], name='Secret 3', ciphertext=b'1234567890'), + )) cls.form_data = { - 'device': device.pk, - 'role': secretrole.pk, + 'device': devices[1].pk, + 'role': secretroles[1].pk, 'name': 'Secret X', } + cls.bulk_edit_data = { + 'role': secretroles[1].pk, + 'name': 'New name', + } + def setUp(self): super().setUp() diff --git a/netbox/tenancy/tests/test_views.py b/netbox/tenancy/tests/test_views.py index 1825a4ff9..a44ca2932 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -8,6 +8,7 @@ class TenantGroupTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -37,18 +38,22 @@ class TenantTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - tenantgroup = TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1') + tenantgroups = ( + TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), + TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), + ) + TenantGroup.objects.bulk_create(tenantgroups) Tenant.objects.bulk_create([ - Tenant(name='Tenant 1', slug='tenant-1', group=tenantgroup), - Tenant(name='Tenant 2', slug='tenant-2', group=tenantgroup), - Tenant(name='Tenant 3', slug='tenant-3', group=tenantgroup), + Tenant(name='Tenant 1', slug='tenant-1', group=tenantgroups[0]), + Tenant(name='Tenant 2', slug='tenant-2', group=tenantgroups[0]), + Tenant(name='Tenant 3', slug='tenant-3', group=tenantgroups[0]), ]) cls.form_data = { 'name': 'Tenant X', 'slug': 'tenant-x', - 'group': tenantgroup.pk, + 'group': tenantgroups[1].pk, 'description': 'A new tenant', 'comments': 'Some comments', 'tags': 'Alpha,Bravo,Charlie', @@ -60,3 +65,7 @@ class TenantTestCase(StandardTestCases.Views): "Tenant 5,tenant-5", "Tenant 6,tenant-6", ) + + cls.bulk_edit_data = { + 'group': tenantgroups[1].pk, + } diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index ff4353962..b55d914d9 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -85,8 +85,15 @@ class StandardTestCases: - Import multiple new objects """ model = None + + # Data to be sent when creating/editing individual objects form_data = {} - csv_data = {} + + # CSV lines used for bulk import of new objects + csv_data = () + + # Form data to be used when editing multiple objects at once + bulk_edit_data = {} maxDiff = None @@ -107,7 +114,7 @@ class StandardTestCases: self.model._meta.model_name ) - if action in ('list', 'add', 'import', 'bulk_delete'): + if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'): return reverse(url_format.format(action)) elif action in ('get', 'edit', 'delete'): @@ -254,6 +261,41 @@ class StandardTestCases: self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1) + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_bulk_edit_objects(self): + pk_list = self.model.objects.values_list('pk', flat=True) + + request = { + 'path': self._get_url('bulk_edit'), + 'data': { + 'pk': pk_list, + '_apply': True, # Form button + }, + 'follow': False, # Do not follow 302 redirects + } + + # Append the form data to the request + request['data'].update(post_data(self.bulk_edit_data)) + + # Attempt to make the request without required permissions + with disable_warnings('django.request'): + self.assertHttpStatus(self.client.post(**request), 403) + + # Assign the required permission and submit again + self.add_permissions( + '{}.change_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.post(**request) + self.assertHttpStatus(response, 302) + + bulk_edit_fields = self.bulk_edit_data.keys() + for i, instance in enumerate(self.model.objects.filter(pk__in=pk_list)): + self.assertDictEqual( + model_to_dict(instance, fields=bulk_edit_fields), + self.bulk_edit_data, + msg="Instance {} failed to validate after bulk edit: {}".format(i, instance) + ) + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_bulk_delete_objects(self): pk_list = self.model.objects.values_list('pk', flat=True) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index ed065678b..77f87c92a 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -10,6 +10,7 @@ class ClusterGroupTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -39,6 +40,7 @@ class ClusterTypeTestCase(StandardTestCases.Views): # Disable inapplicable tests test_get_object = None test_delete_object = None + test_bulk_edit_objects = None @classmethod def setUpTestData(cls): @@ -68,22 +70,36 @@ class ClusterTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - site = Site.objects.create(name='Site 1', slug='site-1') - clustergroup = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1') - clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + clustergroups = ( + ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), + ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'), + ) + ClusterGroup.objects.bulk_create(clustergroups) + + clustertypes = ( + ClusterType(name='Cluster Type 1', slug='cluster-type-1'), + ClusterType(name='Cluster Type 2', slug='cluster-type-2'), + ) + ClusterType.objects.bulk_create(clustertypes) Cluster.objects.bulk_create([ - Cluster(name='Cluster 1', group=clustergroup, type=clustertype), - Cluster(name='Cluster 2', group=clustergroup, type=clustertype), - Cluster(name='Cluster 3', group=clustergroup, type=clustertype), + Cluster(name='Cluster 1', group=clustergroups[0], type=clustertypes[0], site=sites[0]), + Cluster(name='Cluster 2', group=clustergroups[0], type=clustertypes[0], site=sites[0]), + Cluster(name='Cluster 3', group=clustergroups[0], type=clustertypes[0], site=sites[0]), ]) cls.form_data = { 'name': 'Cluster X', - 'group': clustergroup.pk, - 'type': clustertype.pk, + 'group': clustergroups[1].pk, + 'type': clustertypes[1].pk, 'tenant': None, - 'site': site.pk, + 'site': sites[1].pk, 'comments': 'Some comments', 'tags': 'Alpha,Bravo,Charlie', } @@ -95,6 +111,14 @@ class ClusterTestCase(StandardTestCases.Views): "Cluster 6,Cluster Type 1", ) + cls.bulk_edit_data = { + 'group': clustergroups[1].pk, + 'type': clustertypes[1].pk, + 'tenant': None, + 'site': sites[1].pk, + 'comments': 'New comments', + } + class VirtualMachineTestCase(StandardTestCases.Views): model = VirtualMachine @@ -102,24 +126,39 @@ class VirtualMachineTestCase(StandardTestCases.Views): @classmethod def setUpTestData(cls): - devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') - platform = Platform.objects.create(name='Platform 1', slug='platform-1') + deviceroles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + ) + DeviceRole.objects.bulk_create(deviceroles) + + platforms = ( + Platform(name='Platform 1', slug='platform-1'), + Platform(name='Platform 2', slug='platform-2'), + ) + Platform.objects.bulk_create(platforms) + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') - cluster = Cluster.objects.create(name='Cluster 1', type=clustertype) + + clusters = ( + Cluster(name='Cluster 1', type=clustertype), + Cluster(name='Cluster 2', type=clustertype), + ) + Cluster.objects.bulk_create(clusters) VirtualMachine.objects.bulk_create([ - VirtualMachine(name='Virtual Machine 1', cluster=cluster), - VirtualMachine(name='Virtual Machine 2', cluster=cluster), - VirtualMachine(name='Virtual Machine 3', cluster=cluster), + VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), + VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), + VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), ]) cls.form_data = { - 'cluster': cluster.pk, + 'cluster': clusters[1].pk, 'tenant': None, - 'platform': None, + 'platform': platforms[1].pk, 'name': 'Virtual Machine X', 'status': VirtualMachineStatusChoices.STATUS_STAGED, - 'role': devicerole.pk, + 'role': deviceroles[1].pk, 'primary_ip4': None, 'primary_ip6': None, 'vcpus': 4, @@ -136,3 +175,15 @@ class VirtualMachineTestCase(StandardTestCases.Views): "Virtual Machine 5,Cluster 1", "Virtual Machine 6,Cluster 1", ) + + cls.bulk_edit_data = { + 'cluster': clusters[1].pk, + 'tenant': None, + 'platform': platforms[1].pk, + 'status': VirtualMachineStatusChoices.STATUS_STAGED, + 'role': deviceroles[1].pk, + 'vcpus': 8, + 'memory': 65535, + 'disk': 8000, + 'comments': 'New comments', + }