views.py 21 KB

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