views.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. from django.contrib import messages
  2. from django.db import transaction
  3. from django.db.models import Prefetch, Sum
  4. from django.shortcuts import get_object_or_404, redirect, render
  5. from django.urls import reverse
  6. from django.utils.translation import gettext as _
  7. from dcim.filtersets import DeviceFilterSet
  8. from dcim.models import Device
  9. from dcim.tables import DeviceTable
  10. from extras.views import ObjectConfigContextView
  11. from ipam.models import IPAddress, Service
  12. from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
  13. from netbox.views import generic
  14. from utilities.utils import count_related
  15. from utilities.views import ViewTab, register_model_view
  16. from . import filtersets, forms, tables
  17. from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
  18. #
  19. # Cluster types
  20. #
  21. class ClusterTypeListView(generic.ObjectListView):
  22. queryset = ClusterType.objects.annotate(
  23. cluster_count=count_related(Cluster, 'type')
  24. )
  25. filterset = filtersets.ClusterTypeFilterSet
  26. filterset_form = forms.ClusterTypeFilterForm
  27. table = tables.ClusterTypeTable
  28. @register_model_view(ClusterType)
  29. class ClusterTypeView(generic.ObjectView):
  30. queryset = ClusterType.objects.all()
  31. def get_extra_context(self, request, instance):
  32. clusters = Cluster.objects.restrict(request.user, 'view').filter(
  33. type=instance
  34. ).annotate(
  35. device_count=count_related(Device, 'cluster'),
  36. vm_count=count_related(VirtualMachine, 'cluster')
  37. )
  38. clusters_table = tables.ClusterTable(clusters, user=request.user, exclude=('type',))
  39. clusters_table.configure(request)
  40. return {
  41. 'clusters_table': clusters_table,
  42. }
  43. @register_model_view(ClusterType, 'edit')
  44. class ClusterTypeEditView(generic.ObjectEditView):
  45. queryset = ClusterType.objects.all()
  46. form = forms.ClusterTypeForm
  47. @register_model_view(ClusterType, 'delete')
  48. class ClusterTypeDeleteView(generic.ObjectDeleteView):
  49. queryset = ClusterType.objects.all()
  50. class ClusterTypeBulkImportView(generic.BulkImportView):
  51. queryset = ClusterType.objects.all()
  52. model_form = forms.ClusterTypeImportForm
  53. table = tables.ClusterTypeTable
  54. class ClusterTypeBulkEditView(generic.BulkEditView):
  55. queryset = ClusterType.objects.annotate(
  56. cluster_count=count_related(Cluster, 'type')
  57. )
  58. filterset = filtersets.ClusterTypeFilterSet
  59. table = tables.ClusterTypeTable
  60. form = forms.ClusterTypeBulkEditForm
  61. class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
  62. queryset = ClusterType.objects.annotate(
  63. cluster_count=count_related(Cluster, 'type')
  64. )
  65. filterset = filtersets.ClusterTypeFilterSet
  66. table = tables.ClusterTypeTable
  67. #
  68. # Cluster groups
  69. #
  70. class ClusterGroupListView(generic.ObjectListView):
  71. queryset = ClusterGroup.objects.annotate(
  72. cluster_count=count_related(Cluster, 'group')
  73. )
  74. filterset = filtersets.ClusterGroupFilterSet
  75. filterset_form = forms.ClusterGroupFilterForm
  76. table = tables.ClusterGroupTable
  77. @register_model_view(ClusterGroup)
  78. class ClusterGroupView(generic.ObjectView):
  79. queryset = ClusterGroup.objects.all()
  80. def get_extra_context(self, request, instance):
  81. clusters = Cluster.objects.restrict(request.user, 'view').filter(
  82. group=instance
  83. ).annotate(
  84. device_count=count_related(Device, 'cluster'),
  85. vm_count=count_related(VirtualMachine, 'cluster')
  86. )
  87. clusters_table = tables.ClusterTable(clusters, user=request.user, exclude=('group',))
  88. clusters_table.configure(request)
  89. return {
  90. 'clusters_table': clusters_table,
  91. }
  92. @register_model_view(ClusterGroup, 'edit')
  93. class ClusterGroupEditView(generic.ObjectEditView):
  94. queryset = ClusterGroup.objects.all()
  95. form = forms.ClusterGroupForm
  96. @register_model_view(ClusterGroup, 'delete')
  97. class ClusterGroupDeleteView(generic.ObjectDeleteView):
  98. queryset = ClusterGroup.objects.all()
  99. class ClusterGroupBulkImportView(generic.BulkImportView):
  100. queryset = ClusterGroup.objects.annotate(
  101. cluster_count=count_related(Cluster, 'group')
  102. )
  103. model_form = forms.ClusterGroupImportForm
  104. table = tables.ClusterGroupTable
  105. class ClusterGroupBulkEditView(generic.BulkEditView):
  106. queryset = ClusterGroup.objects.annotate(
  107. cluster_count=count_related(Cluster, 'group')
  108. )
  109. filterset = filtersets.ClusterGroupFilterSet
  110. table = tables.ClusterGroupTable
  111. form = forms.ClusterGroupBulkEditForm
  112. class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
  113. queryset = ClusterGroup.objects.annotate(
  114. cluster_count=count_related(Cluster, 'group')
  115. )
  116. filterset = filtersets.ClusterGroupFilterSet
  117. table = tables.ClusterGroupTable
  118. #
  119. # Clusters
  120. #
  121. class ClusterListView(generic.ObjectListView):
  122. permission_required = 'virtualization.view_cluster'
  123. queryset = Cluster.objects.annotate(
  124. device_count=count_related(Device, 'cluster'),
  125. vm_count=count_related(VirtualMachine, 'cluster')
  126. )
  127. table = tables.ClusterTable
  128. filterset = filtersets.ClusterFilterSet
  129. filterset_form = forms.ClusterFilterForm
  130. @register_model_view(Cluster)
  131. class ClusterView(generic.ObjectView):
  132. queryset = Cluster.objects.all()
  133. def get_extra_context(self, request, instance):
  134. return instance.virtual_machines.aggregate(vcpus_sum=Sum('vcpus'), memory_sum=Sum('memory'), disk_sum=Sum('disk'))
  135. @register_model_view(Cluster, 'virtualmachines', path='virtual-machines')
  136. class ClusterVirtualMachinesView(generic.ObjectChildrenView):
  137. queryset = Cluster.objects.all()
  138. child_model = VirtualMachine
  139. table = tables.VirtualMachineTable
  140. filterset = filtersets.VirtualMachineFilterSet
  141. template_name = 'virtualization/cluster/virtual_machines.html'
  142. tab = ViewTab(
  143. label=_('Virtual Machines'),
  144. badge=lambda obj: obj.virtual_machines.count(),
  145. permission='virtualization.view_virtualmachine',
  146. weight=500
  147. )
  148. def get_children(self, request, parent):
  149. return VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=parent)
  150. @register_model_view(Cluster, 'devices')
  151. class ClusterDevicesView(generic.ObjectChildrenView):
  152. queryset = Cluster.objects.all()
  153. child_model = Device
  154. table = DeviceTable
  155. filterset = DeviceFilterSet
  156. template_name = 'virtualization/cluster/devices.html'
  157. tab = ViewTab(
  158. label=_('Devices'),
  159. badge=lambda obj: obj.devices.count(),
  160. permission='virtualization.view_virtualmachine',
  161. weight=600
  162. )
  163. def get_children(self, request, parent):
  164. return Device.objects.restrict(request.user, 'view').filter(cluster=parent)
  165. @register_model_view(Cluster, 'edit')
  166. class ClusterEditView(generic.ObjectEditView):
  167. queryset = Cluster.objects.all()
  168. form = forms.ClusterForm
  169. @register_model_view(Cluster, 'delete')
  170. class ClusterDeleteView(generic.ObjectDeleteView):
  171. queryset = Cluster.objects.all()
  172. class ClusterBulkImportView(generic.BulkImportView):
  173. queryset = Cluster.objects.all()
  174. model_form = forms.ClusterImportForm
  175. table = tables.ClusterTable
  176. class ClusterBulkEditView(generic.BulkEditView):
  177. queryset = Cluster.objects.all()
  178. filterset = filtersets.ClusterFilterSet
  179. table = tables.ClusterTable
  180. form = forms.ClusterBulkEditForm
  181. class ClusterBulkDeleteView(generic.BulkDeleteView):
  182. queryset = Cluster.objects.all()
  183. filterset = filtersets.ClusterFilterSet
  184. table = tables.ClusterTable
  185. @register_model_view(Cluster, 'add_devices', path='devices/add')
  186. class ClusterAddDevicesView(generic.ObjectEditView):
  187. queryset = Cluster.objects.all()
  188. form = forms.ClusterAddDevicesForm
  189. template_name = 'virtualization/cluster_add_devices.html'
  190. def get(self, request, pk):
  191. cluster = get_object_or_404(self.queryset, pk=pk)
  192. form = self.form(cluster, initial=request.GET)
  193. return render(request, self.template_name, {
  194. 'cluster': cluster,
  195. 'form': form,
  196. 'return_url': reverse('virtualization:cluster', kwargs={'pk': pk}),
  197. })
  198. def post(self, request, pk):
  199. cluster = get_object_or_404(self.queryset, pk=pk)
  200. form = self.form(cluster, request.POST)
  201. if form.is_valid():
  202. device_pks = form.cleaned_data['devices']
  203. with transaction.atomic():
  204. # Assign the selected Devices to the Cluster
  205. for device in Device.objects.filter(pk__in=device_pks):
  206. device.cluster = cluster
  207. device.save()
  208. messages.success(request, "Added {} devices to cluster {}".format(
  209. len(device_pks), cluster
  210. ))
  211. return redirect(cluster.get_absolute_url())
  212. return render(request, self.template_name, {
  213. 'cluster': cluster,
  214. 'form': form,
  215. 'return_url': cluster.get_absolute_url(),
  216. })
  217. @register_model_view(Cluster, 'remove_devices', path='devices/remove')
  218. class ClusterRemoveDevicesView(generic.ObjectEditView):
  219. queryset = Cluster.objects.all()
  220. form = forms.ClusterRemoveDevicesForm
  221. template_name = 'generic/bulk_remove.html'
  222. def post(self, request, pk):
  223. cluster = get_object_or_404(self.queryset, pk=pk)
  224. if '_confirm' in request.POST:
  225. form = self.form(request.POST)
  226. if form.is_valid():
  227. device_pks = form.cleaned_data['pk']
  228. with transaction.atomic():
  229. # Remove the selected Devices from the Cluster
  230. for device in Device.objects.filter(pk__in=device_pks):
  231. device.cluster = None
  232. device.save()
  233. messages.success(request, "Removed {} devices from cluster {}".format(
  234. len(device_pks), cluster
  235. ))
  236. return redirect(cluster.get_absolute_url())
  237. else:
  238. form = self.form(initial={'pk': request.POST.getlist('pk')})
  239. selected_objects = Device.objects.filter(pk__in=form.initial['pk'])
  240. device_table = DeviceTable(list(selected_objects), orderable=False)
  241. return render(request, self.template_name, {
  242. 'form': form,
  243. 'parent_obj': cluster,
  244. 'table': device_table,
  245. 'obj_type_plural': 'devices',
  246. 'return_url': cluster.get_absolute_url(),
  247. })
  248. #
  249. # Virtual machines
  250. #
  251. class VirtualMachineListView(generic.ObjectListView):
  252. queryset = VirtualMachine.objects.prefetch_related('primary_ip4', 'primary_ip6')
  253. filterset = filtersets.VirtualMachineFilterSet
  254. filterset_form = forms.VirtualMachineFilterForm
  255. table = tables.VirtualMachineTable
  256. template_name = 'virtualization/virtualmachine_list.html'
  257. @register_model_view(VirtualMachine)
  258. class VirtualMachineView(generic.ObjectView):
  259. queryset = VirtualMachine.objects.prefetch_related('tenant__group')
  260. def get_extra_context(self, request, instance):
  261. # Interfaces
  262. vminterfaces = VMInterface.objects.restrict(request.user, 'view').filter(
  263. virtual_machine=instance
  264. ).prefetch_related(
  265. Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user))
  266. )
  267. vminterface_table = tables.VirtualMachineVMInterfaceTable(vminterfaces, user=request.user, orderable=False)
  268. if request.user.has_perm('virtualization.change_vminterface') or \
  269. request.user.has_perm('virtualization.delete_vminterface'):
  270. vminterface_table.columns.show('pk')
  271. # Services
  272. services = Service.objects.restrict(request.user, 'view').filter(
  273. virtual_machine=instance
  274. ).prefetch_related(
  275. Prefetch('ipaddresses', queryset=IPAddress.objects.restrict(request.user)),
  276. 'virtual_machine'
  277. )
  278. return {
  279. 'vminterface_table': vminterface_table,
  280. 'services': services,
  281. }
  282. @register_model_view(VirtualMachine, 'interfaces')
  283. class VirtualMachineInterfacesView(generic.ObjectChildrenView):
  284. queryset = VirtualMachine.objects.all()
  285. child_model = VMInterface
  286. table = tables.VirtualMachineVMInterfaceTable
  287. filterset = filtersets.VMInterfaceFilterSet
  288. template_name = 'virtualization/virtualmachine/interfaces.html'
  289. tab = ViewTab(
  290. label=_('Interfaces'),
  291. badge=lambda obj: obj.interfaces.count(),
  292. permission='virtualization.view_vminterface',
  293. weight=500
  294. )
  295. def get_children(self, request, parent):
  296. return parent.interfaces.restrict(request.user, 'view').prefetch_related(
  297. Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
  298. 'tags',
  299. )
  300. @register_model_view(VirtualMachine, 'configcontext', path='config-context')
  301. class VirtualMachineConfigContextView(ObjectConfigContextView):
  302. queryset = VirtualMachine.objects.annotate_config_context_data()
  303. base_template = 'virtualization/virtualmachine.html'
  304. tab = ViewTab(
  305. label=_('Config Context'),
  306. permission='extras.view_configcontext',
  307. weight=2000
  308. )
  309. @register_model_view(VirtualMachine, 'edit')
  310. class VirtualMachineEditView(generic.ObjectEditView):
  311. queryset = VirtualMachine.objects.all()
  312. form = forms.VirtualMachineForm
  313. @register_model_view(VirtualMachine, 'delete')
  314. class VirtualMachineDeleteView(generic.ObjectDeleteView):
  315. queryset = VirtualMachine.objects.all()
  316. class VirtualMachineBulkImportView(generic.BulkImportView):
  317. queryset = VirtualMachine.objects.all()
  318. model_form = forms.VirtualMachineImportForm
  319. table = tables.VirtualMachineTable
  320. class VirtualMachineBulkEditView(generic.BulkEditView):
  321. queryset = VirtualMachine.objects.prefetch_related('primary_ip4', 'primary_ip6')
  322. filterset = filtersets.VirtualMachineFilterSet
  323. table = tables.VirtualMachineTable
  324. form = forms.VirtualMachineBulkEditForm
  325. class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
  326. queryset = VirtualMachine.objects.prefetch_related('primary_ip4', 'primary_ip6')
  327. filterset = filtersets.VirtualMachineFilterSet
  328. table = tables.VirtualMachineTable
  329. #
  330. # VM interfaces
  331. #
  332. class VMInterfaceListView(generic.ObjectListView):
  333. queryset = VMInterface.objects.all()
  334. filterset = filtersets.VMInterfaceFilterSet
  335. filterset_form = forms.VMInterfaceFilterForm
  336. table = tables.VMInterfaceTable
  337. actions = ('import', 'export', 'bulk_edit', 'bulk_delete')
  338. @register_model_view(VMInterface)
  339. class VMInterfaceView(generic.ObjectView):
  340. queryset = VMInterface.objects.all()
  341. def get_extra_context(self, request, instance):
  342. # Get assigned IP addresses
  343. ipaddress_table = AssignedIPAddressesTable(
  344. data=instance.ip_addresses.restrict(request.user, 'view'),
  345. orderable=False
  346. )
  347. # Get child interfaces
  348. child_interfaces = VMInterface.objects.restrict(request.user, 'view').filter(parent=instance)
  349. child_interfaces_tables = tables.VMInterfaceTable(
  350. child_interfaces,
  351. exclude=('virtual_machine',),
  352. orderable=False
  353. )
  354. # Get assigned VLANs and annotate whether each is tagged or untagged
  355. vlans = []
  356. if instance.untagged_vlan is not None:
  357. vlans.append(instance.untagged_vlan)
  358. vlans[0].tagged = False
  359. for vlan in instance.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'):
  360. vlan.tagged = True
  361. vlans.append(vlan)
  362. vlan_table = InterfaceVLANTable(
  363. interface=instance,
  364. data=vlans,
  365. orderable=False
  366. )
  367. return {
  368. 'ipaddress_table': ipaddress_table,
  369. 'child_interfaces_table': child_interfaces_tables,
  370. 'vlan_table': vlan_table,
  371. }
  372. class VMInterfaceCreateView(generic.ComponentCreateView):
  373. queryset = VMInterface.objects.all()
  374. form = forms.VMInterfaceCreateForm
  375. model_form = forms.VMInterfaceForm
  376. @register_model_view(VMInterface, 'edit')
  377. class VMInterfaceEditView(generic.ObjectEditView):
  378. queryset = VMInterface.objects.all()
  379. form = forms.VMInterfaceForm
  380. @register_model_view(VMInterface, 'delete')
  381. class VMInterfaceDeleteView(generic.ObjectDeleteView):
  382. queryset = VMInterface.objects.all()
  383. class VMInterfaceBulkImportView(generic.BulkImportView):
  384. queryset = VMInterface.objects.all()
  385. model_form = forms.VMInterfaceImportForm
  386. table = tables.VMInterfaceTable
  387. class VMInterfaceBulkEditView(generic.BulkEditView):
  388. queryset = VMInterface.objects.all()
  389. filterset = filtersets.VMInterfaceFilterSet
  390. table = tables.VMInterfaceTable
  391. form = forms.VMInterfaceBulkEditForm
  392. class VMInterfaceBulkRenameView(generic.BulkRenameView):
  393. queryset = VMInterface.objects.all()
  394. form = forms.VMInterfaceBulkRenameForm
  395. class VMInterfaceBulkDeleteView(generic.BulkDeleteView):
  396. queryset = VMInterface.objects.all()
  397. filterset = filtersets.VMInterfaceFilterSet
  398. table = tables.VMInterfaceTable
  399. #
  400. # Bulk Device component creation
  401. #
  402. class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView):
  403. parent_model = VirtualMachine
  404. parent_field = 'virtual_machine'
  405. form = forms.VMInterfaceBulkCreateForm
  406. queryset = VMInterface.objects.all()
  407. model_form = forms.VMInterfaceForm
  408. filterset = filtersets.VirtualMachineFilterSet
  409. table = tables.VirtualMachineTable
  410. default_return_url = 'virtualization:virtualmachine_list'
  411. def get_required_permission(self):
  412. return f'virtualization.add_vminterface'