views.py 16 KB

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