views.py 16 KB

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