2
0

views.py 17 KB

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