test_views.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.test import override_settings
  3. from django.urls import reverse
  4. from dcim.choices import InterfaceModeChoices
  5. from dcim.models import DeviceRole, Platform, Site
  6. from extras.models import ConfigTemplate
  7. from ipam.models import VLAN, VRF
  8. from utilities.testing import ViewTestCases, create_tags, create_test_device, create_test_virtualmachine
  9. from virtualization.choices import *
  10. from virtualization.models import *
  11. class ClusterGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  12. model = ClusterGroup
  13. @classmethod
  14. def setUpTestData(cls):
  15. cluster_groups = (
  16. ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
  17. ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
  18. ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'),
  19. )
  20. ClusterGroup.objects.bulk_create(cluster_groups)
  21. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  22. cls.form_data = {
  23. 'name': 'Cluster Group X',
  24. 'slug': 'cluster-group-x',
  25. 'description': 'A new cluster group',
  26. 'tags': [t.pk for t in tags],
  27. }
  28. cls.csv_data = (
  29. "name,slug,description",
  30. "Cluster Group 4,cluster-group-4,Fourth cluster group",
  31. "Cluster Group 5,cluster-group-5,Fifth cluster group",
  32. "Cluster Group 6,cluster-group-6,Sixth cluster group",
  33. )
  34. cls.csv_update_data = (
  35. "id,name,description",
  36. f"{cluster_groups[0].pk},Cluster Group 7,Fourth cluster group7",
  37. f"{cluster_groups[1].pk},Cluster Group 8,Fifth cluster group8",
  38. f"{cluster_groups[2].pk},Cluster Group 9,Sixth cluster group9",
  39. )
  40. cls.bulk_edit_data = {
  41. 'description': 'New description',
  42. }
  43. class ClusterTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  44. model = ClusterType
  45. @classmethod
  46. def setUpTestData(cls):
  47. cluster_types = (
  48. ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
  49. ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
  50. ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
  51. )
  52. ClusterType.objects.bulk_create(cluster_types)
  53. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  54. cls.form_data = {
  55. 'name': 'Cluster Type X',
  56. 'slug': 'cluster-type-x',
  57. 'description': 'A new cluster type',
  58. 'tags': [t.pk for t in tags],
  59. }
  60. cls.csv_data = (
  61. "name,slug,description",
  62. "Cluster Type 4,cluster-type-4,Fourth cluster type",
  63. "Cluster Type 5,cluster-type-5,Fifth cluster type",
  64. "Cluster Type 6,cluster-type-6,Sixth cluster type",
  65. )
  66. cls.csv_update_data = (
  67. "id,name,description",
  68. f"{cluster_types[0].pk},Cluster Type 7,Fourth cluster type7",
  69. f"{cluster_types[1].pk},Cluster Type 8,Fifth cluster type8",
  70. f"{cluster_types[2].pk},Cluster Type 9,Sixth cluster type9",
  71. )
  72. cls.bulk_edit_data = {
  73. 'description': 'New description',
  74. }
  75. class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  76. model = Cluster
  77. @classmethod
  78. def setUpTestData(cls):
  79. sites = (
  80. Site(name='Site 1', slug='site-1'),
  81. Site(name='Site 2', slug='site-2'),
  82. )
  83. Site.objects.bulk_create(sites)
  84. clustergroups = (
  85. ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
  86. ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
  87. )
  88. ClusterGroup.objects.bulk_create(clustergroups)
  89. clustertypes = (
  90. ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
  91. ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
  92. )
  93. ClusterType.objects.bulk_create(clustertypes)
  94. clusters = (
  95. Cluster(
  96. name='Cluster 1',
  97. group=clustergroups[0],
  98. type=clustertypes[0],
  99. status=ClusterStatusChoices.STATUS_ACTIVE,
  100. scope=sites[0],
  101. ),
  102. Cluster(
  103. name='Cluster 2',
  104. group=clustergroups[0],
  105. type=clustertypes[0],
  106. status=ClusterStatusChoices.STATUS_ACTIVE,
  107. scope=sites[0],
  108. ),
  109. Cluster(
  110. name='Cluster 3',
  111. group=clustergroups[0],
  112. type=clustertypes[0],
  113. status=ClusterStatusChoices.STATUS_ACTIVE,
  114. scope=sites[0],
  115. ),
  116. )
  117. for cluster in clusters:
  118. cluster.save()
  119. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  120. cls.form_data = {
  121. 'name': 'Cluster X',
  122. 'group': clustergroups[1].pk,
  123. 'type': clustertypes[1].pk,
  124. 'status': ClusterStatusChoices.STATUS_OFFLINE,
  125. 'tenant': None,
  126. 'scope_type': ContentType.objects.get_for_model(Site).pk,
  127. 'scope': sites[1].pk,
  128. 'comments': 'Some comments',
  129. 'tags': [t.pk for t in tags],
  130. }
  131. cls.csv_data = {
  132. 'default': (
  133. "name,type,status,scope_type,scope_id",
  134. f"Cluster 4,Cluster Type 1,active,dcim.site,{sites[0].pk}",
  135. f"Cluster 5,Cluster Type 1,active,dcim.site,{sites[0].pk}",
  136. f"Cluster 6,Cluster Type 1,active,dcim.site,{sites[0].pk}",
  137. ),
  138. 'scope_name': (
  139. "name,type,status,scope_type,scope_name",
  140. f"Cluster 4,Cluster Type 1,active,dcim.site,{sites[0].name}",
  141. f"Cluster 5,Cluster Type 1,active,dcim.site,{sites[0].name}",
  142. f"Cluster 6,Cluster Type 1,active,dcim.site,{sites[0].name}",
  143. ),
  144. }
  145. cls.csv_update_data = (
  146. "id,name,comments",
  147. f"{clusters[0].pk},Cluster 7,New comments 7",
  148. f"{clusters[1].pk},Cluster 8,New comments 8",
  149. f"{clusters[2].pk},Cluster 9,New comments 9",
  150. )
  151. cls.bulk_edit_data = {
  152. 'group': clustergroups[1].pk,
  153. 'type': clustertypes[1].pk,
  154. 'status': ClusterStatusChoices.STATUS_OFFLINE,
  155. 'tenant': None,
  156. 'comments': 'New comments',
  157. }
  158. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  159. def test_cluster_virtualmachines(self):
  160. cluster = Cluster.objects.first()
  161. url = reverse('virtualization:cluster_virtualmachines', kwargs={'pk': cluster.pk})
  162. self.assertHttpStatus(self.client.get(url), 200)
  163. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  164. def test_cluster_devices(self):
  165. cluster = Cluster.objects.first()
  166. url = reverse('virtualization:cluster_devices', kwargs={'pk': cluster.pk})
  167. self.assertHttpStatus(self.client.get(url), 200)
  168. class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  169. model = VirtualMachine
  170. @classmethod
  171. def setUpTestData(cls):
  172. roles = (
  173. DeviceRole(name='Device Role 1', slug='device-role-1'),
  174. DeviceRole(name='Device Role 2', slug='device-role-2'),
  175. )
  176. for role in roles:
  177. role.save()
  178. platforms = (
  179. Platform(name='Platform 1', slug='platform-1'),
  180. Platform(name='Platform 2', slug='platform-2'),
  181. )
  182. for platform in platforms:
  183. platform.save()
  184. sites = (
  185. Site(name='Site 1', slug='site-1'),
  186. Site(name='Site 2', slug='site-2'),
  187. )
  188. Site.objects.bulk_create(sites)
  189. clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  190. clusters = (
  191. Cluster(name='Cluster 1', type=clustertype, scope=sites[0]),
  192. Cluster(name='Cluster 2', type=clustertype, scope=sites[1]),
  193. )
  194. for cluster in clusters:
  195. cluster.save()
  196. devices = (
  197. create_test_device('device1', site=sites[0], cluster=clusters[0]),
  198. create_test_device('device2', site=sites[1], cluster=clusters[1]),
  199. )
  200. virtual_machines = (
  201. VirtualMachine(
  202. name='Virtual Machine 1',
  203. site=sites[0],
  204. cluster=clusters[0],
  205. device=devices[0],
  206. role=roles[0],
  207. platform=platforms[0],
  208. ),
  209. VirtualMachine(
  210. name='Virtual Machine 2',
  211. site=sites[0],
  212. cluster=clusters[0],
  213. device=devices[0],
  214. role=roles[0],
  215. platform=platforms[0],
  216. ),
  217. VirtualMachine(
  218. name='Virtual Machine 3',
  219. site=sites[0],
  220. cluster=clusters[0],
  221. device=devices[0],
  222. role=roles[0],
  223. platform=platforms[0],
  224. ),
  225. )
  226. VirtualMachine.objects.bulk_create(virtual_machines)
  227. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  228. cls.form_data = {
  229. 'cluster': clusters[1].pk,
  230. 'device': devices[1].pk,
  231. 'site': sites[1].pk,
  232. 'tenant': None,
  233. 'platform': platforms[1].pk,
  234. 'name': 'Virtual Machine X',
  235. 'status': VirtualMachineStatusChoices.STATUS_STAGED,
  236. 'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
  237. 'role': roles[1].pk,
  238. 'primary_ip4': None,
  239. 'primary_ip6': None,
  240. 'vcpus': 4,
  241. 'memory': 32768,
  242. 'disk': 4000,
  243. 'serial': 'aaa-111',
  244. 'comments': 'Some comments',
  245. 'tags': [t.pk for t in tags],
  246. 'local_context_data': None,
  247. }
  248. cls.csv_data = (
  249. "name,status,site,cluster,device",
  250. "Virtual Machine 4,active,Site 1,Cluster 1,device1",
  251. "Virtual Machine 5,active,Site 1,Cluster 1,device1",
  252. "Virtual Machine 6,active,Site 1,Cluster 1,",
  253. )
  254. cls.csv_update_data = (
  255. "id,name,comments",
  256. f"{virtual_machines[0].pk},Virtual Machine 7,New comments 7",
  257. f"{virtual_machines[1].pk},Virtual Machine 8,New comments 8",
  258. f"{virtual_machines[2].pk},Virtual Machine 9,New comments 9",
  259. )
  260. cls.bulk_edit_data = {
  261. 'site': sites[1].pk,
  262. 'cluster': clusters[1].pk,
  263. 'device': devices[1].pk,
  264. 'tenant': None,
  265. 'platform': platforms[1].pk,
  266. 'status': VirtualMachineStatusChoices.STATUS_STAGED,
  267. 'role': roles[1].pk,
  268. 'vcpus': 8,
  269. 'memory': 65535,
  270. 'disk': 8000,
  271. 'comments': 'New comments',
  272. 'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_OFF,
  273. }
  274. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  275. def test_virtualmachine_interfaces(self):
  276. virtualmachine = VirtualMachine.objects.first()
  277. vminterfaces = (
  278. VMInterface(virtual_machine=virtualmachine, name='Interface 1'),
  279. VMInterface(virtual_machine=virtualmachine, name='Interface 2'),
  280. VMInterface(virtual_machine=virtualmachine, name='Interface 3'),
  281. )
  282. VMInterface.objects.bulk_create(vminterfaces)
  283. url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
  284. self.assertHttpStatus(self.client.get(url), 200)
  285. def test_bulk_edit_device_context_preserves_device(self):
  286. """
  287. Regression test for #21990: Bulk editing VMs from the Device's VMs tab (URL contains
  288. ?device=<id>) must not clear the device field on those VMs.
  289. """
  290. self.add_permissions('virtualization.view_virtualmachine', 'virtualization.change_virtualmachine')
  291. device = VirtualMachine.objects.filter(device__isnull=False).first().device
  292. vms = list(VirtualMachine.objects.filter(device=device)[:3])
  293. pk_list = [vm.pk for vm in vms]
  294. data = {
  295. 'pk': pk_list,
  296. '_apply': True,
  297. # Only change status — device is intentionally omitted
  298. 'status': VirtualMachineStatusChoices.STATUS_STAGED,
  299. }
  300. # Simulate navigation from Device -> Virtual Machines tab by passing ?device=<id> as GET param
  301. url = reverse('virtualization:virtualmachine_bulk_edit') + f'?device={device.pk}'
  302. response = self.client.post(url, data)
  303. self.assertHttpStatus(response, 302)
  304. for vm in VirtualMachine.objects.filter(pk__in=pk_list):
  305. self.assertEqual(vm.device, device, msg=f"Device was unexpectedly cleared on VM '{vm.name}'")
  306. self.assertEqual(vm.status, VirtualMachineStatusChoices.STATUS_STAGED)
  307. def test_virtualmachine_renderconfig(self):
  308. configtemplate = ConfigTemplate.objects.create(
  309. name='Test Config Template',
  310. template_code='Config for VM {{ virtualmachine.name }}'
  311. )
  312. vm = VirtualMachine.objects.first()
  313. vm.config_template = configtemplate
  314. vm.save()
  315. url = reverse('virtualization:virtualmachine_render-config', kwargs={'pk': vm.pk})
  316. # User with only view permission should NOT be able to render config
  317. self.add_permissions('virtualization.view_virtualmachine')
  318. self.assertHttpStatus(self.client.get(url), 403)
  319. # With render_config permission added should be able to render config
  320. self.add_permissions('virtualization.render_config_virtualmachine')
  321. self.assertHttpStatus(self.client.get(url), 200)
  322. # With view permission removed should NOT be able to render config
  323. self.remove_permissions('virtualization.view_virtualmachine')
  324. self.assertHttpStatus(self.client.get(url), 403)
  325. def test_virtualmachine_renderconfig_with_config_template_id(self):
  326. default_template = ConfigTemplate.objects.create(
  327. name='Default Template',
  328. template_code='Default config for {{ virtualmachine.name }}'
  329. )
  330. override_template = ConfigTemplate.objects.create(
  331. name='Override Template',
  332. template_code='Override config for {{ virtualmachine.name }}'
  333. )
  334. vm = VirtualMachine.objects.first()
  335. vm.config_template = default_template
  336. vm.save()
  337. self.add_permissions(
  338. 'virtualization.view_virtualmachine', 'virtualization.render_config_virtualmachine',
  339. 'extras.view_configtemplate'
  340. )
  341. url = reverse('virtualization:virtualmachine_render-config', kwargs={'pk': vm.pk})
  342. # Render with override config_template_id
  343. response = self.client.get(url, {'config_template_id': override_template.pk})
  344. self.assertHttpStatus(response, 200)
  345. self.assertIn(b'Override config for', response.content)
  346. # Render with nonexistent config_template_id still returns 200 with error message
  347. response = self.client.get(url, {'config_template_id': 999999})
  348. self.assertHttpStatus(response, 200)
  349. self.assertIn(b'Error rendering template', response.content)
  350. # Render with non-integer config_template_id still returns 200 with error message
  351. response = self.client.get(url, {'config_template_id': 'abc'})
  352. self.assertHttpStatus(response, 200)
  353. self.assertIn(b'Error rendering template', response.content)
  354. # Without view_configtemplate permission, override template should not be accessible
  355. self.remove_permissions('extras.view_configtemplate')
  356. response = self.client.get(url, {'config_template_id': override_template.pk})
  357. self.assertHttpStatus(response, 200)
  358. self.assertIn(b'Error rendering template', response.content)
  359. class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
  360. model = VMInterface
  361. validation_excluded_fields = ('name',)
  362. @classmethod
  363. def setUpTestData(cls):
  364. site = Site.objects.create(name='Site 1', slug='site-1')
  365. role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  366. clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  367. cluster = Cluster.objects.create(name='Cluster 1', type=clustertype, scope=site)
  368. virtualmachines = (
  369. VirtualMachine(name='Virtual Machine 1', site=site, cluster=cluster, role=role),
  370. VirtualMachine(name='Virtual Machine 2', site=site, cluster=cluster, role=role),
  371. )
  372. VirtualMachine.objects.bulk_create(virtualmachines)
  373. interfaces = VMInterface.objects.bulk_create([
  374. VMInterface(virtual_machine=virtualmachines[0], name='Interface 1'),
  375. VMInterface(virtual_machine=virtualmachines[0], name='Interface 2'),
  376. VMInterface(virtual_machine=virtualmachines[0], name='Interface 3'),
  377. VMInterface(virtual_machine=virtualmachines[1], name='BRIDGE'),
  378. ])
  379. vlans = (
  380. VLAN(vid=1, name='VLAN1', site=site),
  381. VLAN(vid=101, name='VLAN101', site=site),
  382. VLAN(vid=102, name='VLAN102', site=site),
  383. VLAN(vid=103, name='VLAN103', site=site),
  384. )
  385. VLAN.objects.bulk_create(vlans)
  386. vrfs = (
  387. VRF(name='VRF 1'),
  388. VRF(name='VRF 2'),
  389. VRF(name='VRF 3'),
  390. )
  391. VRF.objects.bulk_create(vrfs)
  392. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  393. cls.form_data = {
  394. 'virtual_machine': virtualmachines[0].pk,
  395. 'name': 'Interface X',
  396. 'enabled': False,
  397. 'bridge': interfaces[1].pk,
  398. 'mtu': 65000,
  399. 'description': 'New description',
  400. 'mode': InterfaceModeChoices.MODE_TAGGED,
  401. 'untagged_vlan': vlans[0].pk,
  402. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  403. 'vrf': vrfs[0].pk,
  404. 'tags': [t.pk for t in tags],
  405. }
  406. cls.bulk_create_data = {
  407. 'virtual_machine': virtualmachines[1].pk,
  408. 'name': 'Interface [4-6]',
  409. 'enabled': False,
  410. 'bridge': interfaces[3].pk,
  411. 'mtu': 2000,
  412. 'description': 'New description',
  413. 'mode': InterfaceModeChoices.MODE_TAGGED,
  414. 'untagged_vlan': vlans[0].pk,
  415. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  416. 'vrf': vrfs[0].pk,
  417. 'tags': [t.pk for t in tags],
  418. }
  419. cls.csv_data = (
  420. "virtual_machine,name,vrf.pk,mode,untagged_vlan,tagged_vlans",
  421. (
  422. f"Virtual Machine 2,Interface 4,{vrfs[0].pk},"
  423. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  424. ),
  425. (
  426. f"Virtual Machine 2,Interface 5,{vrfs[0].pk},"
  427. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  428. ),
  429. (
  430. f"Virtual Machine 2,Interface 6,{vrfs[0].pk},"
  431. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  432. ),
  433. )
  434. cls.csv_update_data = (
  435. "id,name,description",
  436. f"{interfaces[0].pk},Interface 7,New description 7",
  437. f"{interfaces[1].pk},Interface 8,New description 8",
  438. f"{interfaces[2].pk},Interface 9,New description 9",
  439. )
  440. cls.bulk_edit_data = {
  441. 'enabled': False,
  442. 'mtu': 2000,
  443. 'description': 'New description',
  444. 'mode': InterfaceModeChoices.MODE_TAGGED,
  445. 'untagged_vlan': vlans[0].pk,
  446. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  447. }
  448. def test_bulk_delete_child_interfaces(self):
  449. interface1 = VMInterface.objects.get(name='Interface 1')
  450. virtual_machine = interface1.virtual_machine
  451. self.add_permissions('virtualization.delete_vminterface')
  452. # Create a child interface
  453. child = VMInterface.objects.create(
  454. virtual_machine=virtual_machine,
  455. name='Interface 1A',
  456. parent=interface1
  457. )
  458. self.assertEqual(virtual_machine.interfaces.count(), 4)
  459. # Attempt to delete only the parent interface
  460. data = {
  461. 'confirm': True,
  462. }
  463. self.client.post(self._get_url('delete', interface1), data)
  464. self.assertEqual(virtual_machine.interfaces.count(), 4) # Parent was not deleted
  465. # Attempt to bulk delete parent & child together
  466. data = {
  467. 'pk': [interface1.pk, child.pk],
  468. 'confirm': True,
  469. '_confirm': True, # Form button
  470. }
  471. self.client.post(self._get_url('bulk_delete'), data)
  472. self.assertEqual(virtual_machine.interfaces.count(), 2) # Child & parent were both deleted
  473. class VirtualDiskTestCase(ViewTestCases.DeviceComponentViewTestCase):
  474. model = VirtualDisk
  475. validation_excluded_fields = ('name',)
  476. @classmethod
  477. def setUpTestData(cls):
  478. virtualmachine = create_test_virtualmachine('Virtual Machine 1')
  479. disks = VirtualDisk.objects.bulk_create([
  480. VirtualDisk(virtual_machine=virtualmachine, name='Virtual Disk 1', size=10),
  481. VirtualDisk(virtual_machine=virtualmachine, name='Virtual Disk 2', size=10),
  482. VirtualDisk(virtual_machine=virtualmachine, name='Virtual Disk 3', size=10),
  483. ])
  484. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  485. cls.form_data = {
  486. 'virtual_machine': virtualmachine.pk,
  487. 'name': 'Virtual Disk X',
  488. 'size': 20,
  489. 'description': 'New description',
  490. 'tags': [t.pk for t in tags],
  491. }
  492. cls.bulk_create_data = {
  493. 'virtual_machine': virtualmachine.pk,
  494. 'name': 'Virtual Disk [4-6]',
  495. 'size': 10,
  496. 'tags': [t.pk for t in tags],
  497. }
  498. cls.csv_data = (
  499. "virtual_machine,name,size,description",
  500. "Virtual Machine 1,Disk 4,20,Fourth",
  501. "Virtual Machine 1,Disk 5,20,Fifth",
  502. "Virtual Machine 1,Disk 6,20,Sixth",
  503. )
  504. cls.csv_update_data = (
  505. "id,name,size",
  506. f"{disks[0].pk},disk1,20",
  507. f"{disks[1].pk},disk2,20",
  508. f"{disks[2].pk},disk3,20",
  509. )
  510. cls.bulk_edit_data = {
  511. 'size': 30,
  512. 'description': 'New description',
  513. }