views.py 25 KB

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