views.py 16 KB

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