views.py 16 KB

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