views.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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 utilities.tables import paginate_table
  13. from utilities.utils import count_related
  14. from . import filtersets, forms, tables
  15. from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
  16. #
  17. # Cluster types
  18. #
  19. class ClusterTypeListView(generic.ObjectListView):
  20. queryset = ClusterType.objects.annotate(
  21. cluster_count=count_related(Cluster, 'type')
  22. )
  23. filterset = filtersets.ClusterTypeFilterSet
  24. filterset_form = forms.ClusterTypeFilterForm
  25. table = tables.ClusterTypeTable
  26. class ClusterTypeView(generic.ObjectView):
  27. queryset = ClusterType.objects.all()
  28. def get_extra_context(self, request, instance):
  29. clusters = Cluster.objects.restrict(request.user, 'view').filter(
  30. type=instance
  31. ).annotate(
  32. device_count=count_related(Device, 'cluster'),
  33. vm_count=count_related(VirtualMachine, 'cluster')
  34. )
  35. clusters_table = tables.ClusterTable(clusters)
  36. clusters_table.columns.hide('type')
  37. paginate_table(clusters_table, request)
  38. return {
  39. 'clusters_table': clusters_table,
  40. }
  41. class ClusterTypeEditView(generic.ObjectEditView):
  42. queryset = ClusterType.objects.all()
  43. model_form = forms.ClusterTypeForm
  44. class ClusterTypeDeleteView(generic.ObjectDeleteView):
  45. queryset = ClusterType.objects.all()
  46. class ClusterTypeBulkImportView(generic.BulkImportView):
  47. queryset = ClusterType.objects.all()
  48. model_form = forms.ClusterTypeCSVForm
  49. table = tables.ClusterTypeTable
  50. class ClusterTypeBulkEditView(generic.BulkEditView):
  51. queryset = ClusterType.objects.annotate(
  52. cluster_count=count_related(Cluster, 'type')
  53. )
  54. filterset = filtersets.ClusterTypeFilterSet
  55. table = tables.ClusterTypeTable
  56. form = forms.ClusterTypeBulkEditForm
  57. class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
  58. queryset = ClusterType.objects.annotate(
  59. cluster_count=count_related(Cluster, 'type')
  60. )
  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. class ClusterGroupView(generic.ObjectView):
  73. queryset = ClusterGroup.objects.all()
  74. def get_extra_context(self, request, instance):
  75. clusters = Cluster.objects.restrict(request.user, 'view').filter(
  76. group=instance
  77. ).annotate(
  78. device_count=count_related(Device, 'cluster'),
  79. vm_count=count_related(VirtualMachine, 'cluster')
  80. )
  81. clusters_table = tables.ClusterTable(clusters)
  82. clusters_table.columns.hide('group')
  83. paginate_table(clusters_table, request)
  84. return {
  85. 'clusters_table': clusters_table,
  86. }
  87. class ClusterGroupEditView(generic.ObjectEditView):
  88. queryset = ClusterGroup.objects.all()
  89. model_form = forms.ClusterGroupForm
  90. class ClusterGroupDeleteView(generic.ObjectDeleteView):
  91. queryset = ClusterGroup.objects.all()
  92. class ClusterGroupBulkImportView(generic.BulkImportView):
  93. queryset = ClusterGroup.objects.annotate(
  94. cluster_count=count_related(Cluster, 'group')
  95. )
  96. model_form = forms.ClusterGroupCSVForm
  97. table = tables.ClusterGroupTable
  98. class ClusterGroupBulkEditView(generic.BulkEditView):
  99. queryset = ClusterGroup.objects.annotate(
  100. cluster_count=count_related(Cluster, 'group')
  101. )
  102. filterset = filtersets.ClusterGroupFilterSet
  103. table = tables.ClusterGroupTable
  104. form = forms.ClusterGroupBulkEditForm
  105. class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
  106. queryset = ClusterGroup.objects.annotate(
  107. cluster_count=count_related(Cluster, 'group')
  108. )
  109. table = tables.ClusterGroupTable
  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. class ClusterView(generic.ObjectView):
  123. queryset = Cluster.objects.all()
  124. class ClusterVirtualMachinesView(generic.ObjectView):
  125. queryset = Cluster.objects.all()
  126. template_name = 'virtualization/cluster/virtual_machines.html'
  127. def get_extra_context(self, request, instance):
  128. virtualmachines = VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=instance)
  129. virtualmachines_table = tables.VirtualMachineTable(virtualmachines, orderable=False)
  130. return {
  131. 'virtualmachines_table': virtualmachines_table,
  132. 'active_tab': 'virtual-machines',
  133. }
  134. class ClusterDevicesView(generic.ObjectView):
  135. queryset = Cluster.objects.all()
  136. template_name = 'virtualization/cluster/devices.html'
  137. def get_extra_context(self, request, instance):
  138. devices = Device.objects.restrict(request.user, 'view').filter(cluster=instance).prefetch_related(
  139. 'site', 'rack', 'tenant', 'device_type__manufacturer'
  140. )
  141. devices_table = DeviceTable(list(devices), orderable=False)
  142. if request.user.has_perm('virtualization.change_cluster'):
  143. devices_table.columns.show('pk')
  144. return {
  145. 'devices_table': devices_table,
  146. 'active_tab': 'devices',
  147. }
  148. class ClusterEditView(generic.ObjectEditView):
  149. queryset = Cluster.objects.all()
  150. model_form = forms.ClusterForm
  151. class ClusterDeleteView(generic.ObjectDeleteView):
  152. queryset = Cluster.objects.all()
  153. class ClusterBulkImportView(generic.BulkImportView):
  154. queryset = Cluster.objects.all()
  155. model_form = forms.ClusterCSVForm
  156. table = tables.ClusterTable
  157. class ClusterBulkEditView(generic.BulkEditView):
  158. queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
  159. filterset = filtersets.ClusterFilterSet
  160. table = tables.ClusterTable
  161. form = forms.ClusterBulkEditForm
  162. class ClusterBulkDeleteView(generic.BulkDeleteView):
  163. queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
  164. filterset = filtersets.ClusterFilterSet
  165. table = tables.ClusterTable
  166. class ClusterAddDevicesView(generic.ObjectEditView):
  167. queryset = Cluster.objects.all()
  168. form = forms.ClusterAddDevicesForm
  169. template_name = 'virtualization/cluster_add_devices.html'
  170. def get(self, request, pk):
  171. cluster = get_object_or_404(self.queryset, pk=pk)
  172. form = self.form(cluster, initial=request.GET)
  173. return render(request, self.template_name, {
  174. 'cluster': cluster,
  175. 'form': form,
  176. 'return_url': reverse('virtualization:cluster', kwargs={'pk': pk}),
  177. })
  178. def post(self, request, pk):
  179. cluster = get_object_or_404(self.queryset, pk=pk)
  180. form = self.form(cluster, request.POST)
  181. if form.is_valid():
  182. device_pks = form.cleaned_data['devices']
  183. with transaction.atomic():
  184. # Assign the selected Devices to the Cluster
  185. for device in Device.objects.filter(pk__in=device_pks):
  186. device.cluster = cluster
  187. device.save()
  188. messages.success(request, "Added {} devices to cluster {}".format(
  189. len(device_pks), cluster
  190. ))
  191. return redirect(cluster.get_absolute_url())
  192. return render(request, self.template_name, {
  193. 'cluster': cluster,
  194. 'form': form,
  195. 'return_url': cluster.get_absolute_url(),
  196. })
  197. class ClusterRemoveDevicesView(generic.ObjectEditView):
  198. queryset = Cluster.objects.all()
  199. form = forms.ClusterRemoveDevicesForm
  200. template_name = 'generic/object_bulk_remove.html'
  201. def post(self, request, pk):
  202. cluster = get_object_or_404(self.queryset, pk=pk)
  203. if '_confirm' in request.POST:
  204. form = self.form(request.POST)
  205. if form.is_valid():
  206. device_pks = form.cleaned_data['pk']
  207. with transaction.atomic():
  208. # Remove the selected Devices from the Cluster
  209. for device in Device.objects.filter(pk__in=device_pks):
  210. device.cluster = None
  211. device.save()
  212. messages.success(request, "Removed {} devices from cluster {}".format(
  213. len(device_pks), cluster
  214. ))
  215. return redirect(cluster.get_absolute_url())
  216. else:
  217. form = self.form(initial={'pk': request.POST.getlist('pk')})
  218. selected_objects = Device.objects.filter(pk__in=form.initial['pk'])
  219. device_table = DeviceTable(list(selected_objects), orderable=False)
  220. return render(request, self.template_name, {
  221. 'form': form,
  222. 'parent_obj': cluster,
  223. 'table': device_table,
  224. 'obj_type_plural': 'devices',
  225. 'return_url': cluster.get_absolute_url(),
  226. })
  227. #
  228. # Virtual machines
  229. #
  230. class VirtualMachineListView(generic.ObjectListView):
  231. queryset = VirtualMachine.objects.all()
  232. filterset = filtersets.VirtualMachineFilterSet
  233. filterset_form = forms.VirtualMachineFilterForm
  234. table = tables.VirtualMachineDetailTable
  235. template_name = 'virtualization/virtualmachine_list.html'
  236. class VirtualMachineView(generic.ObjectView):
  237. queryset = VirtualMachine.objects.prefetch_related('tenant__group')
  238. def get_extra_context(self, request, instance):
  239. # Interfaces
  240. vminterfaces = VMInterface.objects.restrict(request.user, 'view').filter(
  241. virtual_machine=instance
  242. ).prefetch_related(
  243. Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user))
  244. )
  245. vminterface_table = tables.VirtualMachineVMInterfaceTable(vminterfaces, user=request.user, orderable=False)
  246. if request.user.has_perm('virtualization.change_vminterface') or \
  247. request.user.has_perm('virtualization.delete_vminterface'):
  248. vminterface_table.columns.show('pk')
  249. # Services
  250. services = Service.objects.restrict(request.user, 'view').filter(
  251. virtual_machine=instance
  252. ).prefetch_related(
  253. Prefetch('ipaddresses', queryset=IPAddress.objects.restrict(request.user))
  254. )
  255. return {
  256. 'vminterface_table': vminterface_table,
  257. 'services': services,
  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 = ('import', '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. class VMInterfaceEditView(generic.ObjectEditView):
  348. queryset = VMInterface.objects.all()
  349. model_form = forms.VMInterfaceForm
  350. template_name = 'virtualization/vminterface_edit.html'
  351. class VMInterfaceDeleteView(generic.ObjectDeleteView):
  352. queryset = VMInterface.objects.all()
  353. class VMInterfaceBulkImportView(generic.BulkImportView):
  354. queryset = VMInterface.objects.all()
  355. model_form = forms.VMInterfaceCSVForm
  356. table = tables.VMInterfaceTable
  357. class VMInterfaceBulkEditView(generic.BulkEditView):
  358. queryset = VMInterface.objects.all()
  359. table = tables.VMInterfaceTable
  360. form = forms.VMInterfaceBulkEditForm
  361. class VMInterfaceBulkRenameView(generic.BulkRenameView):
  362. queryset = VMInterface.objects.all()
  363. form = forms.VMInterfaceBulkRenameForm
  364. class VMInterfaceBulkDeleteView(generic.BulkDeleteView):
  365. queryset = VMInterface.objects.all()
  366. table = tables.VMInterfaceTable
  367. #
  368. # Bulk Device component creation
  369. #
  370. class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView):
  371. parent_model = VirtualMachine
  372. parent_field = 'virtual_machine'
  373. form = forms.VMInterfaceBulkCreateForm
  374. queryset = VMInterface.objects.all()
  375. model_form = forms.VMInterfaceForm
  376. filterset = filtersets.VirtualMachineFilterSet
  377. table = tables.VirtualMachineTable
  378. def get_required_permission(self):
  379. return f'virtualization.add_vminterface'