views.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. from netaddr import IPNetwork, IPSet
  2. from django_tables2 import RequestConfig
  3. from django.contrib.auth.mixins import PermissionRequiredMixin
  4. from django.db.models import Count, Q
  5. from django.shortcuts import get_object_or_404, render
  6. from dcim.models import Device
  7. from utilities.paginator import EnhancedPaginator
  8. from utilities.views import (
  9. BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  10. )
  11. from . import filters, forms, tables
  12. from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
  13. def add_available_prefixes(parent, prefix_list):
  14. """
  15. Create fake Prefix objects for all unallocated space within a prefix.
  16. """
  17. # Find all unallocated space
  18. available_prefixes = IPSet(parent) ^ IPSet([p.prefix for p in prefix_list])
  19. available_prefixes = [Prefix(prefix=p) for p in available_prefixes.iter_cidrs()]
  20. # Concatenate and sort complete list of children
  21. prefix_list = list(prefix_list) + available_prefixes
  22. prefix_list.sort(key=lambda p: p.prefix)
  23. return prefix_list
  24. def add_available_ipaddresses(prefix, ipaddress_list):
  25. """
  26. Create fake IPAddress objects for all unallocated space within a prefix.
  27. """
  28. # Find all unallocated space
  29. available_ips = IPSet(prefix) - IPSet([str(ip.address.ip) for ip in ipaddress_list])
  30. available_ips = [IPAddress(address=IPNetwork('{}/{}'.format(ip, prefix.prefixlen))) for ip in available_ips]
  31. # Concatenate and sort complete list of children
  32. ipaddress_list = list(ipaddress_list) + available_ips
  33. ipaddress_list.sort(key=lambda ip: ip.address)
  34. if not ipaddress_list:
  35. return []
  36. # Summarize free IPs in the list
  37. computed_list = []
  38. count = 0
  39. prev_ip = ipaddress_list[0]
  40. for ip in ipaddress_list:
  41. if ip.pk:
  42. if count:
  43. computed_list.append((count, prev_ip))
  44. count = 0
  45. computed_list.append(ip)
  46. continue
  47. if not count:
  48. prev_ip = ip
  49. count += 1
  50. if count:
  51. computed_list.append((count, prev_ip))
  52. return computed_list
  53. #
  54. # VRFs
  55. #
  56. class VRFListView(ObjectListView):
  57. queryset = VRF.objects.select_related('tenant')
  58. filter = filters.VRFFilter
  59. filter_form = forms.VRFFilterForm
  60. table = tables.VRFTable
  61. edit_permissions = ['ipam.change_vrf', 'ipam.delete_vrf']
  62. template_name = 'ipam/vrf_list.html'
  63. def vrf(request, pk):
  64. vrf = get_object_or_404(VRF.objects.all(), pk=pk)
  65. prefixes = Prefix.objects.filter(vrf=vrf)
  66. prefix_table = tables.PrefixBriefTable(prefixes)
  67. return render(request, 'ipam/vrf.html', {
  68. 'vrf': vrf,
  69. 'prefix_table': prefix_table,
  70. })
  71. class VRFEditView(PermissionRequiredMixin, ObjectEditView):
  72. permission_required = 'ipam.change_vrf'
  73. model = VRF
  74. form_class = forms.VRFForm
  75. cancel_url = 'ipam:vrf_list'
  76. class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  77. permission_required = 'ipam.delete_vrf'
  78. model = VRF
  79. redirect_url = 'ipam:vrf_list'
  80. class VRFBulkImportView(PermissionRequiredMixin, BulkImportView):
  81. permission_required = 'ipam.add_vrf'
  82. form = forms.VRFImportForm
  83. table = tables.VRFTable
  84. template_name = 'ipam/vrf_import.html'
  85. obj_list_url = 'ipam:vrf_list'
  86. class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
  87. permission_required = 'ipam.change_vrf'
  88. cls = VRF
  89. form = forms.VRFBulkEditForm
  90. template_name = 'ipam/vrf_bulk_edit.html'
  91. default_redirect_url = 'ipam:vrf_list'
  92. def update_objects(self, pk_list, form):
  93. fields_to_update = {}
  94. if form.cleaned_data['tenant'] == 0:
  95. fields_to_update['tenant'] = None
  96. elif form.cleaned_data['tenant']:
  97. fields_to_update['tenant'] = form.cleaned_data['tenant']
  98. for field in ['description']:
  99. if form.cleaned_data[field]:
  100. fields_to_update[field] = form.cleaned_data[field]
  101. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  102. class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  103. permission_required = 'ipam.delete_vrf'
  104. cls = VRF
  105. default_redirect_url = 'ipam:vrf_list'
  106. #
  107. # RIRs
  108. #
  109. class RIRListView(ObjectListView):
  110. queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
  111. table = tables.RIRTable
  112. edit_permissions = ['ipam.change_rir', 'ipam.delete_rir']
  113. template_name = 'ipam/rir_list.html'
  114. class RIREditView(PermissionRequiredMixin, ObjectEditView):
  115. permission_required = 'ipam.change_rir'
  116. model = RIR
  117. form_class = forms.RIRForm
  118. success_url = 'ipam:rir_list'
  119. cancel_url = 'ipam:rir_list'
  120. class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  121. permission_required = 'ipam.delete_rir'
  122. cls = RIR
  123. default_redirect_url = 'ipam:rir_list'
  124. #
  125. # Aggregates
  126. #
  127. class AggregateListView(ObjectListView):
  128. queryset = Aggregate.objects.select_related('rir').extra(select={
  129. 'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
  130. })
  131. filter = filters.AggregateFilter
  132. filter_form = forms.AggregateFilterForm
  133. table = tables.AggregateTable
  134. edit_permissions = ['ipam.change_aggregate', 'ipam.delete_aggregate']
  135. template_name = 'ipam/aggregate_list.html'
  136. def extra_context(self):
  137. ipv4_total = 0
  138. ipv6_total = 0
  139. for a in self.queryset:
  140. if a.prefix.version == 4:
  141. ipv4_total += a.prefix.size
  142. elif a.prefix.version == 6:
  143. ipv6_total += a.prefix.size / 2 ** 64
  144. return {
  145. 'ipv4_total': ipv4_total,
  146. 'ipv6_total': ipv6_total,
  147. }
  148. def aggregate(request, pk):
  149. aggregate = get_object_or_404(Aggregate, pk=pk)
  150. # Find all child prefixes contained by this aggregate
  151. child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))\
  152. .select_related('site', 'role').annotate_depth(limit=0)
  153. child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
  154. prefix_table = tables.PrefixTable(child_prefixes)
  155. prefix_table.model = Prefix
  156. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  157. prefix_table.base_columns['pk'].visible = True
  158. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(prefix_table)
  159. return render(request, 'ipam/aggregate.html', {
  160. 'aggregate': aggregate,
  161. 'prefix_table': prefix_table,
  162. })
  163. class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
  164. permission_required = 'ipam.change_aggregate'
  165. model = Aggregate
  166. form_class = forms.AggregateForm
  167. cancel_url = 'ipam:aggregate_list'
  168. class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  169. permission_required = 'ipam.delete_aggregate'
  170. model = Aggregate
  171. redirect_url = 'ipam:aggregate_list'
  172. class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
  173. permission_required = 'ipam.add_aggregate'
  174. form = forms.AggregateImportForm
  175. table = tables.AggregateTable
  176. template_name = 'ipam/aggregate_import.html'
  177. obj_list_url = 'ipam:aggregate_list'
  178. class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
  179. permission_required = 'ipam.change_aggregate'
  180. cls = Aggregate
  181. form = forms.AggregateBulkEditForm
  182. template_name = 'ipam/aggregate_bulk_edit.html'
  183. default_redirect_url = 'ipam:aggregate_list'
  184. def update_objects(self, pk_list, form):
  185. fields_to_update = {}
  186. for field in ['rir', 'date_added', 'description']:
  187. if form.cleaned_data[field]:
  188. fields_to_update[field] = form.cleaned_data[field]
  189. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  190. class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  191. permission_required = 'ipam.delete_aggregate'
  192. cls = Aggregate
  193. default_redirect_url = 'ipam:aggregate_list'
  194. #
  195. # Prefix/VLAN roles
  196. #
  197. class RoleListView(ObjectListView):
  198. queryset = Role.objects.all()
  199. table = tables.RoleTable
  200. edit_permissions = ['ipam.change_role', 'ipam.delete_role']
  201. template_name = 'ipam/role_list.html'
  202. class RoleEditView(PermissionRequiredMixin, ObjectEditView):
  203. permission_required = 'ipam.change_role'
  204. model = Role
  205. form_class = forms.RoleForm
  206. success_url = 'ipam:role_list'
  207. cancel_url = 'ipam:role_list'
  208. class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  209. permission_required = 'ipam.delete_role'
  210. cls = Role
  211. default_redirect_url = 'ipam:role_list'
  212. #
  213. # Prefixes
  214. #
  215. class PrefixListView(ObjectListView):
  216. queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'role')
  217. filter = filters.PrefixFilter
  218. filter_form = forms.PrefixFilterForm
  219. table = tables.PrefixTable
  220. edit_permissions = ['ipam.change_prefix', 'ipam.delete_prefix']
  221. template_name = 'ipam/prefix_list.html'
  222. def alter_queryset(self, request):
  223. # Show only top-level prefixes by default (unless searching)
  224. limit = None if request.GET.get('expand') or request.GET.get('q') else 0
  225. return self.queryset.annotate_depth(limit=limit)
  226. def prefix(request, pk):
  227. prefix = get_object_or_404(Prefix.objects.select_related('site', 'vlan', 'role'), pk=pk)
  228. try:
  229. aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
  230. except Aggregate.DoesNotExist:
  231. aggregate = None
  232. # Count child IP addresses
  233. ipaddress_count = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\
  234. .count()
  235. # Parent prefixes table
  236. parent_prefixes = Prefix.objects.filter(Q(vrf=prefix.vrf) | Q(vrf__isnull=True))\
  237. .filter(prefix__net_contains=str(prefix.prefix))\
  238. .select_related('site', 'role').annotate_depth()
  239. parent_prefix_table = tables.PrefixBriefTable(parent_prefixes)
  240. # Duplicate prefixes table
  241. duplicate_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix=str(prefix.prefix)).exclude(pk=prefix.pk)\
  242. .select_related('site', 'role')
  243. duplicate_prefix_table = tables.PrefixBriefTable(duplicate_prefixes)
  244. # Child prefixes table
  245. if prefix.vrf:
  246. # If the prefix is in a VRF, show child prefixes only within that VRF.
  247. child_prefixes = Prefix.objects.filter(vrf=prefix.vrf)
  248. else:
  249. # If the prefix is in the global table, show child prefixes from all VRFs.
  250. child_prefixes = Prefix.objects.all()
  251. child_prefixes = child_prefixes.filter(prefix__net_contained=str(prefix.prefix))\
  252. .select_related('site', 'role').annotate_depth(limit=0)
  253. if child_prefixes:
  254. child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
  255. child_prefix_table = tables.PrefixTable(child_prefixes)
  256. child_prefix_table.model = Prefix
  257. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  258. child_prefix_table.base_columns['pk'].visible = True
  259. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(child_prefix_table)
  260. return render(request, 'ipam/prefix.html', {
  261. 'prefix': prefix,
  262. 'aggregate': aggregate,
  263. 'ipaddress_count': ipaddress_count,
  264. 'parent_prefix_table': parent_prefix_table,
  265. 'child_prefix_table': child_prefix_table,
  266. 'duplicate_prefix_table': duplicate_prefix_table,
  267. })
  268. class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
  269. permission_required = 'ipam.change_prefix'
  270. model = Prefix
  271. form_class = forms.PrefixForm
  272. fields_initial = ['site', 'vrf', 'prefix']
  273. cancel_url = 'ipam:prefix_list'
  274. class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  275. permission_required = 'ipam.delete_prefix'
  276. model = Prefix
  277. redirect_url = 'ipam:prefix_list'
  278. class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
  279. permission_required = 'ipam.add_prefix'
  280. form = forms.PrefixImportForm
  281. table = tables.PrefixTable
  282. template_name = 'ipam/prefix_import.html'
  283. obj_list_url = 'ipam:prefix_list'
  284. class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
  285. permission_required = 'ipam.change_prefix'
  286. cls = Prefix
  287. form = forms.PrefixBulkEditForm
  288. template_name = 'ipam/prefix_bulk_edit.html'
  289. default_redirect_url = 'ipam:prefix_list'
  290. def update_objects(self, pk_list, form):
  291. fields_to_update = {}
  292. for field in ['vrf', 'tenant']:
  293. if form.cleaned_data[field] == 0:
  294. fields_to_update[field] = None
  295. elif form.cleaned_data[field]:
  296. fields_to_update[field] = form.cleaned_data[field]
  297. for field in ['site', 'status', 'role', 'description']:
  298. if form.cleaned_data[field]:
  299. fields_to_update[field] = form.cleaned_data[field]
  300. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  301. class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  302. permission_required = 'ipam.delete_prefix'
  303. cls = Prefix
  304. default_redirect_url = 'ipam:prefix_list'
  305. def prefix_ipaddresses(request, pk):
  306. prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
  307. # Find all IPAddresses belonging to this Prefix
  308. ipaddresses = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\
  309. .select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
  310. ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses)
  311. ip_table = tables.IPAddressTable(ipaddresses)
  312. ip_table.model = IPAddress
  313. if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
  314. ip_table.base_columns['pk'].visible = True
  315. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(ip_table)
  316. return render(request, 'ipam/prefix_ipaddresses.html', {
  317. 'prefix': prefix,
  318. 'ip_table': ip_table,
  319. })
  320. #
  321. # IP addresses
  322. #
  323. class IPAddressListView(ObjectListView):
  324. queryset = IPAddress.objects.select_related('vrf__tenant', 'interface__device')
  325. filter = filters.IPAddressFilter
  326. filter_form = forms.IPAddressFilterForm
  327. table = tables.IPAddressTable
  328. edit_permissions = ['ipam.change_ipaddress', 'ipam.delete_ipaddress']
  329. template_name = 'ipam/ipaddress_list.html'
  330. def ipaddress(request, pk):
  331. ipaddress = get_object_or_404(IPAddress.objects.select_related('interface__device'), pk=pk)
  332. # Parent prefixes table
  333. parent_prefixes = Prefix.objects.filter(vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip))
  334. parent_prefixes_table = tables.PrefixBriefTable(parent_prefixes)
  335. # Duplicate IPs table
  336. duplicate_ips = IPAddress.objects.filter(vrf=ipaddress.vrf, address=str(ipaddress.address))\
  337. .exclude(pk=ipaddress.pk).select_related('interface__device', 'nat_inside')
  338. duplicate_ips_table = tables.IPAddressBriefTable(duplicate_ips)
  339. # Related IP table
  340. related_ips = IPAddress.objects.select_related('interface__device').exclude(address=str(ipaddress.address))\
  341. .filter(vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address))
  342. related_ips_table = tables.IPAddressBriefTable(related_ips)
  343. return render(request, 'ipam/ipaddress.html', {
  344. 'ipaddress': ipaddress,
  345. 'parent_prefixes_table': parent_prefixes_table,
  346. 'duplicate_ips_table': duplicate_ips_table,
  347. 'related_ips_table': related_ips_table,
  348. })
  349. class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
  350. permission_required = 'ipam.change_ipaddress'
  351. model = IPAddress
  352. form_class = forms.IPAddressForm
  353. fields_initial = ['address', 'vrf']
  354. template_name = 'ipam/ipaddress_edit.html'
  355. cancel_url = 'ipam:ipaddress_list'
  356. class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  357. permission_required = 'ipam.delete_ipaddress'
  358. model = IPAddress
  359. redirect_url = 'ipam:ipaddress_list'
  360. class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
  361. permission_required = 'ipam.add_ipaddress'
  362. form = forms.IPAddressImportForm
  363. table = tables.IPAddressTable
  364. template_name = 'ipam/ipaddress_import.html'
  365. obj_list_url = 'ipam:ipaddress_list'
  366. def save_obj(self, obj):
  367. obj.save()
  368. # Update primary IP for device if needed
  369. try:
  370. if obj.family == 4 and obj.primary_ip4_for:
  371. device = obj.primary_ip4_for
  372. device.primary_ip4 = obj
  373. device.save()
  374. elif obj.family == 6 and obj.primary_ip6_for:
  375. device = obj.primary_ip6_for
  376. device.primary_ip6 = obj
  377. device.save()
  378. except Device.DoesNotExist:
  379. pass
  380. class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
  381. permission_required = 'ipam.change_ipaddress'
  382. cls = IPAddress
  383. form = forms.IPAddressBulkEditForm
  384. template_name = 'ipam/ipaddress_bulk_edit.html'
  385. default_redirect_url = 'ipam:ipaddress_list'
  386. def update_objects(self, pk_list, form):
  387. fields_to_update = {}
  388. for field in ['vrf', 'tenant']:
  389. if form.cleaned_data[field] == 0:
  390. fields_to_update[field] = None
  391. elif form.cleaned_data[field]:
  392. fields_to_update[field] = form.cleaned_data[field]
  393. for field in ['description']:
  394. if form.cleaned_data[field]:
  395. fields_to_update[field] = form.cleaned_data[field]
  396. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  397. class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  398. permission_required = 'ipam.delete_ipaddress'
  399. cls = IPAddress
  400. default_redirect_url = 'ipam:ipaddress_list'
  401. #
  402. # VLAN groups
  403. #
  404. class VLANGroupListView(ObjectListView):
  405. queryset = VLANGroup.objects.annotate(vlan_count=Count('vlans'))
  406. filter = filters.VLANGroupFilter
  407. filter_form = forms.VLANGroupFilterForm
  408. table = tables.VLANGroupTable
  409. edit_permissions = ['ipam.change_vlangroup', 'ipam.delete_vlangroup']
  410. template_name = 'ipam/vlangroup_list.html'
  411. class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
  412. permission_required = 'ipam.change_vlangroup'
  413. model = VLANGroup
  414. form_class = forms.VLANGroupForm
  415. cancel_url = 'ipam:vlangroup_list'
  416. class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  417. permission_required = 'ipam.delete_vlangroup'
  418. cls = VLANGroup
  419. default_redirect_url = 'ipam:vlangroup_list'
  420. #
  421. # VLANs
  422. #
  423. class VLANListView(ObjectListView):
  424. queryset = VLAN.objects.select_related('site', 'role')
  425. filter = filters.VLANFilter
  426. filter_form = forms.VLANFilterForm
  427. table = tables.VLANTable
  428. edit_permissions = ['ipam.change_vlan', 'ipam.delete_vlan']
  429. template_name = 'ipam/vlan_list.html'
  430. def vlan(request, pk):
  431. vlan = get_object_or_404(VLAN.objects.select_related('site', 'role'), pk=pk)
  432. prefixes = Prefix.objects.filter(vlan=vlan)
  433. prefix_table = tables.PrefixBriefTable(prefixes)
  434. return render(request, 'ipam/vlan.html', {
  435. 'vlan': vlan,
  436. 'prefix_table': prefix_table,
  437. })
  438. class VLANEditView(PermissionRequiredMixin, ObjectEditView):
  439. permission_required = 'ipam.change_vlan'
  440. model = VLAN
  441. form_class = forms.VLANForm
  442. cancel_url = 'ipam:vlan_list'
  443. class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  444. permission_required = 'ipam.delete_vlan'
  445. model = VLAN
  446. redirect_url = 'ipam:vlan_list'
  447. class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
  448. permission_required = 'ipam.add_vlan'
  449. form = forms.VLANImportForm
  450. table = tables.VLANTable
  451. template_name = 'ipam/vlan_import.html'
  452. obj_list_url = 'ipam:vlan_list'
  453. class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
  454. permission_required = 'ipam.change_vlan'
  455. cls = VLAN
  456. form = forms.VLANBulkEditForm
  457. template_name = 'ipam/vlan_bulk_edit.html'
  458. default_redirect_url = 'ipam:vlan_list'
  459. def update_objects(self, pk_list, form):
  460. fields_to_update = {}
  461. if form.cleaned_data['tenant'] == 0:
  462. fields_to_update['tenant'] = None
  463. elif form.cleaned_data['tenant']:
  464. fields_to_update['tenant'] = form.cleaned_data['tenant']
  465. for field in ['site', 'group', 'status', 'role', 'description']:
  466. if form.cleaned_data[field]:
  467. fields_to_update[field] = form.cleaned_data[field]
  468. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  469. class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  470. permission_required = 'ipam.delete_vlan'
  471. cls = VLAN
  472. default_redirect_url = 'ipam:vlan_list'