views.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. from netaddr import IPSet
  2. from django_tables2 import RequestConfig
  3. from django.conf import settings
  4. from django.contrib import messages
  5. from django.contrib.auth.decorators import permission_required
  6. from django.contrib.auth.mixins import PermissionRequiredMixin
  7. from django.core.urlresolvers import reverse
  8. from django.db.models import ProtectedError
  9. from django.http import HttpResponseRedirect
  10. from django.shortcuts import get_object_or_404, redirect, render
  11. from django.utils.http import urlencode
  12. from dcim.models import Device
  13. from utilities.error_handlers import handle_protectederror
  14. from utilities.forms import ConfirmationForm
  15. from utilities.paginator import EnhancedPaginator
  16. from utilities.views import BulkImportView, BulkEditView, BulkDeleteView, ObjectListView
  17. from .filters import AggregateFilter, PrefixFilter, IPAddressFilter, VLANFilter, VRFFilter
  18. from .forms import AggregateForm, AggregateImportForm, AggregateBulkEditForm, AggregateBulkDeleteForm, \
  19. AggregateFilterForm, PrefixForm, PrefixImportForm, PrefixBulkEditForm, PrefixBulkDeleteForm, PrefixFilterForm, \
  20. IPAddressForm, IPAddressImportForm, IPAddressBulkEditForm, IPAddressBulkDeleteForm, IPAddressFilterForm, VLANForm, \
  21. VLANImportForm, VLANBulkEditForm, VLANBulkDeleteForm, VRFForm, VRFImportForm, VRFBulkEditForm, VRFBulkDeleteForm, \
  22. VLANFilterForm
  23. from .models import VRF, Aggregate, Prefix, VLAN
  24. from .tables import AggregateTable, AggregateBulkEditTable, PrefixTable, PrefixBriefTable, PrefixBulkEditTable, \
  25. IPAddress, IPAddressBriefTable, IPAddressTable, IPAddressBulkEditTable, VLANTable, VLANBulkEditTable, VRFTable, \
  26. VRFBulkEditTable
  27. def add_available_prefixes(parent, prefix_list):
  28. """
  29. Create fake Prefix objects for all unallocated space within a prefix.
  30. """
  31. # Find all unallocated space
  32. available_prefixes = IPSet(parent) ^ IPSet([p.prefix for p in prefix_list])
  33. available_prefixes = [Prefix(prefix=p) for p in available_prefixes.iter_cidrs()]
  34. # Concatenate and sort complete list of children
  35. prefix_list = list(prefix_list) + available_prefixes
  36. prefix_list.sort(key=lambda p: p.prefix)
  37. return prefix_list
  38. #
  39. # VRFs
  40. #
  41. class VRFListView(ObjectListView):
  42. queryset = VRF.objects.all()
  43. filter = VRFFilter
  44. table = VRFTable
  45. edit_table = VRFBulkEditTable
  46. edit_table_permissions = ['ipam.change_vrf', 'ipam.delete_vrf']
  47. template_name = 'ipam/vrf_list.html'
  48. def vrf(request, pk):
  49. vrf = get_object_or_404(VRF.objects.all(), pk=pk)
  50. prefixes = Prefix.objects.filter(vrf=vrf)
  51. return render(request, 'ipam/vrf.html', {
  52. 'vrf': vrf,
  53. 'prefixes': prefixes,
  54. })
  55. @permission_required('ipam.add_vrf')
  56. def vrf_add(request):
  57. if request.method == 'POST':
  58. form = VRFForm(request.POST)
  59. if form.is_valid():
  60. vrf = form.save()
  61. messages.success(request, "Added new VRF: {0}".format(vrf))
  62. if '_addanother' in request.POST:
  63. return redirect('ipam:vrf_add')
  64. else:
  65. return redirect('ipam:vrf', pk=vrf.pk)
  66. else:
  67. form = VRFForm()
  68. return render(request, 'ipam/vrf_edit.html', {
  69. 'form': form,
  70. 'cancel_url': reverse('ipam:vrf_list'),
  71. })
  72. @permission_required('ipam.change_vrf')
  73. def vrf_edit(request, pk):
  74. vrf = get_object_or_404(VRF, pk=pk)
  75. if request.method == 'POST':
  76. form = VRFForm(request.POST, instance=vrf)
  77. if form.is_valid():
  78. vrf = form.save()
  79. messages.success(request, "Modified VRF {0}".format(vrf))
  80. return redirect('ipam:vrf', pk=vrf.pk)
  81. else:
  82. form = VRFForm(instance=vrf)
  83. return render(request, 'ipam/vrf_edit.html', {
  84. 'vrf': vrf,
  85. 'form': form,
  86. 'cancel_url': reverse('ipam:vrf', kwargs={'pk': vrf.pk}),
  87. })
  88. @permission_required('ipam.delete_vrf')
  89. def vrf_delete(request, pk):
  90. vrf = get_object_or_404(VRF, pk=pk)
  91. if request.method == 'POST':
  92. form = ConfirmationForm(request.POST)
  93. if form.is_valid():
  94. try:
  95. vrf.delete()
  96. messages.success(request, "VRF {0} has been deleted".format(vrf))
  97. return redirect('ipam:vrf_list')
  98. except ProtectedError, e:
  99. handle_protectederror(vrf, request, e)
  100. return redirect('ipam:vrf', pk=vrf.pk)
  101. else:
  102. form = ConfirmationForm()
  103. return render(request, 'ipam/vrf_delete.html', {
  104. 'vrf': vrf,
  105. 'form': form,
  106. 'cancel_url': reverse('ipam:vrf', kwargs={'pk': vrf.pk})
  107. })
  108. class VRFBulkImportView(PermissionRequiredMixin, BulkImportView):
  109. permission_required = 'ipam.add_vrf'
  110. form = VRFImportForm
  111. table = VRFTable
  112. template_name = 'ipam/vrf_import.html'
  113. obj_list_url = 'ipam:vrf_list'
  114. class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
  115. permission_required = 'ipam.change_vrf'
  116. cls = VRF
  117. form = VRFBulkEditForm
  118. template_name = 'ipam/vrf_bulk_edit.html'
  119. default_redirect_url = 'ipam:vrf_list'
  120. def update_objects(self, pk_list, form):
  121. fields_to_update = {}
  122. for field in ['description']:
  123. if form.cleaned_data[field]:
  124. fields_to_update[field] = form.cleaned_data[field]
  125. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  126. messages.success(self.request, "Updated {} VRFs".format(updated_count))
  127. class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  128. permission_required = 'ipam.delete_vrf'
  129. cls = VRF
  130. form = VRFBulkDeleteForm
  131. template_name = 'ipam/vrf_bulk_delete.html'
  132. default_redirect_url = 'ipam:vrf_list'
  133. #
  134. # Aggregates
  135. #
  136. class AggregateListView(ObjectListView):
  137. queryset = Aggregate.objects.select_related('rir').extra(select={
  138. 'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
  139. })
  140. filter = AggregateFilter
  141. filter_form = AggregateFilterForm
  142. table = AggregateTable
  143. edit_table = AggregateBulkEditTable
  144. edit_table_permissions = ['ipam.change_aggregate', 'ipam.delete_aggregate']
  145. template_name = 'ipam/aggregate_list.html'
  146. def aggregate(request, pk):
  147. aggregate = get_object_or_404(Aggregate, pk=pk)
  148. # Find all child prefixes contained by this aggregate
  149. child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))\
  150. .select_related('site', 'status', 'role').annotate_depth(limit=0)
  151. child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
  152. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  153. prefix_table = PrefixBulkEditTable(child_prefixes)
  154. else:
  155. prefix_table = PrefixTable(child_prefixes)
  156. RequestConfig(request, paginate={'per_page': settings.PAGINATE_COUNT, 'klass': EnhancedPaginator})\
  157. .configure(prefix_table)
  158. return render(request, 'ipam/aggregate.html', {
  159. 'aggregate': aggregate,
  160. 'prefix_table': prefix_table,
  161. })
  162. @permission_required('ipam.add_aggregate')
  163. def aggregate_add(request):
  164. if request.method == 'POST':
  165. form = AggregateForm(request.POST)
  166. if form.is_valid():
  167. aggregate = form.save()
  168. messages.success(request, "Added new aggregate: {0}".format(aggregate.prefix))
  169. if '_addanother' in request.POST:
  170. return redirect('ipam:aggregate_add')
  171. else:
  172. return redirect('ipam:aggregate', pk=aggregate.pk)
  173. else:
  174. form = AggregateForm()
  175. return render(request, 'ipam/aggregate_edit.html', {
  176. 'form': form,
  177. 'cancel_url': reverse('ipam:aggregate_list'),
  178. })
  179. @permission_required('ipam.change_aggregate')
  180. def aggregate_edit(request, pk):
  181. aggregate = get_object_or_404(Aggregate, pk=pk)
  182. if request.method == 'POST':
  183. form = AggregateForm(request.POST, instance=aggregate)
  184. if form.is_valid():
  185. aggregate = form.save()
  186. messages.success(request, "Modified aggregate {0}".format(aggregate.prefix))
  187. return redirect('ipam:aggregate', pk=aggregate.pk)
  188. else:
  189. form = AggregateForm(instance=aggregate)
  190. return render(request, 'ipam/aggregate_edit.html', {
  191. 'aggregate': aggregate,
  192. 'form': form,
  193. 'cancel_url': reverse('ipam:aggregate', kwargs={'pk': aggregate.pk}),
  194. })
  195. @permission_required('ipam.delete_aggregate')
  196. def aggregate_delete(request, pk):
  197. aggregate = get_object_or_404(Aggregate, pk=pk)
  198. if request.method == 'POST':
  199. form = ConfirmationForm(request.POST)
  200. if form.is_valid():
  201. try:
  202. aggregate.delete()
  203. messages.success(request, "Aggregate {0} has been deleted".format(aggregate))
  204. return redirect('ipam:aggregate_list')
  205. except ProtectedError, e:
  206. handle_protectederror(aggregate, request, e)
  207. return redirect('ipam:aggregate', pk=aggregate.pk)
  208. else:
  209. form = ConfirmationForm()
  210. return render(request, 'ipam/aggregate_delete.html', {
  211. 'aggregate': aggregate,
  212. 'form': form,
  213. 'cancel_url': reverse('ipam:aggregate', kwargs={'pk': aggregate.pk})
  214. })
  215. class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
  216. permission_required = 'ipam.add_aggregate'
  217. form = AggregateImportForm
  218. table = AggregateTable
  219. template_name = 'ipam/aggregate_import.html'
  220. obj_list_url = 'ipam:aggregate_list'
  221. class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
  222. permission_required = 'ipam.change_aggregate'
  223. cls = Aggregate
  224. form = AggregateBulkEditForm
  225. template_name = 'ipam/aggregate_bulk_edit.html'
  226. default_redirect_url = 'ipam:aggregate_list'
  227. def update_objects(self, pk_list, form):
  228. fields_to_update = {}
  229. for field in ['rir', 'date_added', 'description']:
  230. if form.cleaned_data[field]:
  231. fields_to_update[field] = form.cleaned_data[field]
  232. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  233. messages.success(self.request, "Updated {} aggregates".format(updated_count))
  234. class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  235. permission_required = 'ipam.delete_aggregate'
  236. cls = Aggregate
  237. form = AggregateBulkDeleteForm
  238. template_name = 'ipam/aggregate_bulk_delete.html'
  239. default_redirect_url = 'ipam:aggregate_list'
  240. #
  241. # Prefixes
  242. #
  243. class PrefixListView(ObjectListView):
  244. queryset = Prefix.objects.select_related('site', 'status', 'role')
  245. filter = PrefixFilter
  246. filter_form = PrefixFilterForm
  247. table = PrefixTable
  248. edit_table = PrefixBulkEditTable
  249. edit_table_permissions = ['ipam.change_prefix', 'ipam.delete_prefix']
  250. template_name = 'ipam/prefix_list.html'
  251. def alter_queryset(self, request):
  252. # Show only top-level prefixes by default (unless searching)
  253. limit = None if request.GET.get('expand') or request.GET.get('q') else 0
  254. return self.queryset.annotate_depth(limit=limit)
  255. def prefix(request, pk):
  256. prefix = get_object_or_404(Prefix.objects.select_related('site', 'vlan', 'status', 'role'), pk=pk)
  257. try:
  258. aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
  259. except Aggregate.DoesNotExist:
  260. aggregate = None
  261. # Count child IP addresses
  262. ipaddress_count = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix)).count()
  263. # Parent prefixes table
  264. parent_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contains=str(prefix.prefix))\
  265. .select_related('site', 'status', 'role').annotate_depth()
  266. parent_prefix_table = PrefixBriefTable(parent_prefixes)
  267. # Duplicate prefixes table
  268. duplicate_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix=str(prefix.prefix)).exclude(pk=prefix.pk)\
  269. .select_related('site', 'status', 'role')
  270. duplicate_prefix_table = PrefixBriefTable(duplicate_prefixes)
  271. # Child prefixes table
  272. child_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix))\
  273. .select_related('site', 'status', 'role').annotate_depth(limit=0)
  274. if child_prefixes:
  275. child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
  276. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  277. child_prefix_table = PrefixBulkEditTable(child_prefixes)
  278. else:
  279. child_prefix_table = PrefixTable(child_prefixes)
  280. RequestConfig(request, paginate={'per_page': settings.PAGINATE_COUNT, 'klass': EnhancedPaginator})\
  281. .configure(child_prefix_table)
  282. return render(request, 'ipam/prefix.html', {
  283. 'prefix': prefix,
  284. 'aggregate': aggregate,
  285. 'ipaddress_count': ipaddress_count,
  286. 'parent_prefix_table': parent_prefix_table,
  287. 'child_prefix_table': child_prefix_table,
  288. 'duplicate_prefix_table': duplicate_prefix_table,
  289. })
  290. @permission_required('ipam.add_prefix')
  291. def prefix_add(request):
  292. if request.method == 'POST':
  293. form = PrefixForm(request.POST)
  294. if form.is_valid():
  295. prefix = form.save()
  296. messages.success(request, "Added new prefix: {0}".format(prefix.prefix))
  297. if '_addanother' in request.POST:
  298. return redirect('ipam:prefix_add')
  299. else:
  300. return redirect('ipam:prefix', pk=prefix.pk)
  301. else:
  302. form = PrefixForm(initial={
  303. 'site': request.GET.get('site'),
  304. 'vrf': request.GET.get('vrf'),
  305. 'prefix': request.GET.get('prefix'),
  306. })
  307. return render(request, 'ipam/prefix_edit.html', {
  308. 'form': form,
  309. 'cancel_url': reverse('ipam:prefix_list'),
  310. })
  311. @permission_required('ipam.change_prefix')
  312. def prefix_edit(request, pk):
  313. prefix = get_object_or_404(Prefix, pk=pk)
  314. if request.method == 'POST':
  315. form = PrefixForm(request.POST, instance=prefix)
  316. if form.is_valid():
  317. prefix = form.save()
  318. messages.success(request, "Modified prefix {0}".format(prefix.prefix))
  319. return redirect('ipam:prefix', pk=prefix.pk)
  320. else:
  321. form = PrefixForm(instance=prefix)
  322. return render(request, 'ipam/prefix_edit.html', {
  323. 'prefix': prefix,
  324. 'form': form,
  325. 'cancel_url': reverse('ipam:prefix', kwargs={'pk': prefix.pk}),
  326. })
  327. @permission_required('ipam.delete_prefix')
  328. def prefix_delete(request, pk):
  329. prefix = get_object_or_404(Prefix, pk=pk)
  330. if request.method == 'POST':
  331. form = ConfirmationForm(request.POST)
  332. if form.is_valid():
  333. try:
  334. prefix.delete()
  335. messages.success(request, "Prefix {0} has been deleted".format(prefix))
  336. return redirect('ipam:prefix_list')
  337. except ProtectedError, e:
  338. handle_protectederror(prefix, request, e)
  339. return redirect('ipam:prefix', pk=prefix.pk)
  340. else:
  341. form = ConfirmationForm()
  342. return render(request, 'ipam/prefix_delete.html', {
  343. 'prefix': prefix,
  344. 'form': form,
  345. 'cancel_url': reverse('ipam:prefix', kwargs={'pk': prefix.pk})
  346. })
  347. class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
  348. permission_required = 'ipam.add_prefix'
  349. form = PrefixImportForm
  350. table = PrefixTable
  351. template_name = 'ipam/prefix_import.html'
  352. obj_list_url = 'ipam:prefix_list'
  353. class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
  354. permission_required = 'ipam.change_prefix'
  355. cls = Prefix
  356. form = PrefixBulkEditForm
  357. template_name = 'ipam/prefix_bulk_edit.html'
  358. default_redirect_url = 'ipam:prefix_list'
  359. def update_objects(self, pk_list, form):
  360. fields_to_update = {}
  361. if form.cleaned_data['vrf']:
  362. fields_to_update['vrf'] = form.cleaned_data['vrf']
  363. elif form.cleaned_data['vrf_global']:
  364. fields_to_update['vrf'] = None
  365. for field in ['site', 'status', 'role', 'description']:
  366. if form.cleaned_data[field]:
  367. fields_to_update[field] = form.cleaned_data[field]
  368. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  369. messages.success(self.request, "Updated {} prefixes".format(updated_count))
  370. class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  371. permission_required = 'ipam.delete_prefix'
  372. cls = Prefix
  373. form = PrefixBulkDeleteForm
  374. template_name = 'ipam/prefix_bulk_delete.html'
  375. default_redirect_url = 'ipam:prefix_list'
  376. def prefix_ipaddresses(request, pk):
  377. prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
  378. # Find all IPAddresses belonging to this Prefix
  379. ipaddresses = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))\
  380. .select_related('vrf', 'interface__device', 'primary_for')
  381. if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
  382. ip_table = IPAddressBulkEditTable(ipaddresses)
  383. else:
  384. ip_table = IPAddressTable(ipaddresses)
  385. RequestConfig(request, paginate={'per_page': settings.PAGINATE_COUNT, 'klass': EnhancedPaginator})\
  386. .configure(ip_table)
  387. return render(request, 'ipam/prefix_ipaddresses.html', {
  388. 'prefix': prefix,
  389. 'ip_table': ip_table,
  390. })
  391. #
  392. # IP addresses
  393. #
  394. class IPAddressListView(ObjectListView):
  395. queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_for')
  396. filter = IPAddressFilter
  397. filter_form = IPAddressFilterForm
  398. table = IPAddressTable
  399. edit_table = IPAddressBulkEditTable
  400. edit_table_permissions = ['ipam.change_ipaddress', 'ipam.delete_ipaddress']
  401. template_name = 'ipam/ipaddress_list.html'
  402. def ipaddress(request, pk):
  403. ipaddress = get_object_or_404(IPAddress.objects.select_related('interface__device'), pk=pk)
  404. parent_prefixes = Prefix.objects.filter(vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip))
  405. related_ips = IPAddress.objects.select_related('interface__device').exclude(pk=ipaddress.pk).filter(vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address))
  406. related_ips_table = IPAddressBriefTable(related_ips)
  407. RequestConfig(request, paginate={'per_page': settings.PAGINATE_COUNT, 'klass': EnhancedPaginator}).configure(related_ips_table)
  408. return render(request, 'ipam/ipaddress.html', {
  409. 'ipaddress': ipaddress,
  410. 'parent_prefixes': parent_prefixes,
  411. 'related_ips_table': related_ips_table,
  412. })
  413. @permission_required('ipam.add_ipaddress')
  414. def ipaddress_add(request):
  415. if request.method == 'POST':
  416. form = IPAddressForm(request.POST)
  417. if form.is_valid():
  418. ipaddress = form.save()
  419. messages.success(request, "Created new IP Address: {0}".format(ipaddress))
  420. if '_addanother' in request.POST:
  421. return redirect('ipam:ipaddress_add')
  422. else:
  423. return redirect('ipam:ipaddress', pk=ipaddress.pk)
  424. else:
  425. form = IPAddressForm(initial={
  426. 'ipaddress': request.GET.get('ipaddress', None),
  427. })
  428. return render(request, 'ipam/ipaddress_edit.html', {
  429. 'form': form,
  430. 'cancel_url': reverse('ipam:ipaddress_list'),
  431. })
  432. @permission_required('ipam.change_ipaddress')
  433. def ipaddress_edit(request, pk):
  434. ipaddress = get_object_or_404(IPAddress, pk=pk)
  435. if request.method == 'POST':
  436. form = IPAddressForm(request.POST, instance=ipaddress)
  437. if form.is_valid():
  438. ipaddress = form.save()
  439. messages.success(request, "Modified IP address {0}".format(ipaddress))
  440. return redirect('ipam:ipaddress', pk=ipaddress.pk)
  441. else:
  442. form = IPAddressForm(instance=ipaddress)
  443. return render(request, 'ipam/ipaddress_edit.html', {
  444. 'ipaddress': ipaddress,
  445. 'form': form,
  446. 'cancel_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}),
  447. })
  448. @permission_required('ipam.delete_ipaddress')
  449. def ipaddress_delete(request, pk):
  450. ipaddress = get_object_or_404(IPAddress, pk=pk)
  451. if request.method == 'POST':
  452. form = ConfirmationForm(request.POST)
  453. if form.is_valid():
  454. try:
  455. ipaddress.delete()
  456. messages.success(request, "IP address {0} has been deleted".format(ipaddress))
  457. if ipaddress.interface:
  458. return redirect('dcim:device', pk=ipaddress.interface.device.pk)
  459. else:
  460. return redirect('ipam:ipaddress_list')
  461. except ProtectedError, e:
  462. handle_protectederror(ipaddress, request, e)
  463. return redirect('ipam:ipaddress', pk=ipaddress.pk)
  464. else:
  465. form = ConfirmationForm()
  466. # Upon cancellation, redirect to the assigned device if one exists
  467. if ipaddress.interface:
  468. cancel_url = reverse('dcim:device', kwargs={'pk': ipaddress.interface.device.pk})
  469. else:
  470. cancel_url = reverse('ipam:ipaddress_list')
  471. return render(request, 'ipam/ipaddress_delete.html', {
  472. 'ipaddress': ipaddress,
  473. 'form': form,
  474. 'cancel_url': cancel_url,
  475. })
  476. class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
  477. permission_required = 'ipam.add_ipaddress'
  478. form = IPAddressImportForm
  479. table = IPAddressTable
  480. template_name = 'ipam/ipaddress_import.html'
  481. obj_list_url = 'ipam:ipaddress_list'
  482. def save_obj(self, obj):
  483. obj.save()
  484. # Update primary IP for device if needed
  485. try:
  486. device = obj.primary_for
  487. device.primary_ip = obj
  488. device.save()
  489. except Device.DoesNotExist:
  490. pass
  491. class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
  492. permission_required = 'ipam.change_ipaddress'
  493. cls = IPAddress
  494. form = IPAddressBulkEditForm
  495. template_name = 'ipam/ipaddress_bulk_edit.html'
  496. default_redirect_url = 'ipam:ipaddress_list'
  497. def update_objects(self, pk_list, form):
  498. fields_to_update = {}
  499. if form.cleaned_data['vrf']:
  500. fields_to_update['vrf'] = form.cleaned_data['vrf']
  501. elif form.cleaned_data['vrf_global']:
  502. fields_to_update['vrf'] = None
  503. for field in ['description']:
  504. if form.cleaned_data[field]:
  505. fields_to_update[field] = form.cleaned_data[field]
  506. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  507. messages.success(self.request, "Updated {} IP addresses".format(updated_count))
  508. class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  509. permission_required = 'ipam.delete_ipaddress'
  510. cls = IPAddress
  511. form = IPAddressBulkDeleteForm
  512. template_name = 'ipam/ipaddress_bulk_delete.html'
  513. default_redirect_url = 'ipam:ipaddress_list'
  514. #
  515. # VLANs
  516. #
  517. class VLANListView(ObjectListView):
  518. queryset = VLAN.objects.select_related('site', 'status', 'role')
  519. filter = VLANFilter
  520. filter_form = VLANFilterForm
  521. table = VLANTable
  522. edit_table = VLANBulkEditTable
  523. edit_table_permissions = ['ipam.change_vlan', 'ipam.delete_vlan']
  524. template_name = 'ipam/vlan_list.html'
  525. def vlan(request, pk):
  526. vlan = get_object_or_404(VLAN.objects.select_related('site', 'status', 'role'), pk=pk)
  527. prefixes = Prefix.objects.filter(vlan=vlan)
  528. return render(request, 'ipam/vlan.html', {
  529. 'vlan': vlan,
  530. 'prefixes': prefixes,
  531. })
  532. @permission_required('ipam.add_vlan')
  533. def vlan_add(request):
  534. if request.method == 'POST':
  535. form = VLANForm(request.POST)
  536. if form.is_valid():
  537. vlan = form.save()
  538. messages.success(request, "Added new VLAN: {0}".format(vlan))
  539. if '_addanother' in request.POST:
  540. base_url = reverse('ipam:vlan_add')
  541. params = urlencode({
  542. 'site': vlan.site.pk,
  543. })
  544. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  545. else:
  546. return redirect('ipam:vlan', pk=vlan.pk)
  547. else:
  548. form = VLANForm()
  549. return render(request, 'ipam/vlan_edit.html', {
  550. 'form': form,
  551. 'cancel_url': reverse('ipam:vlan_list'),
  552. })
  553. @permission_required('ipam.change_vlan')
  554. def vlan_edit(request, pk):
  555. vlan = get_object_or_404(VLAN, pk=pk)
  556. if request.method == 'POST':
  557. form = VLANForm(request.POST, instance=vlan)
  558. if form.is_valid():
  559. vlan = form.save()
  560. messages.success(request, "Modified VLAN {0}".format(vlan))
  561. return redirect('ipam:vlan', pk=vlan.pk)
  562. else:
  563. form = VLANForm(instance=vlan)
  564. return render(request, 'ipam/vlan_edit.html', {
  565. 'vlan': vlan,
  566. 'form': form,
  567. 'cancel_url': reverse('ipam:vlan', kwargs={'pk': vlan.pk}),
  568. })
  569. @permission_required('ipam.delete_vlan')
  570. def vlan_delete(request, pk):
  571. vlan = get_object_or_404(VLAN, pk=pk)
  572. if request.method == 'POST':
  573. form = ConfirmationForm(request.POST)
  574. if form.is_valid():
  575. try:
  576. vlan.delete()
  577. messages.success(request, "VLAN {0} has been deleted".format(vlan))
  578. return redirect('ipam:vlan_list')
  579. except ProtectedError, e:
  580. handle_protectederror(vlan, request, e)
  581. return redirect('ipam:vlan', pk=vlan.pk)
  582. else:
  583. form = ConfirmationForm()
  584. return render(request, 'ipam/vlan_delete.html', {
  585. 'vlan': vlan,
  586. 'form': form,
  587. 'cancel_url': reverse('ipam:vlan', kwargs={'pk': vlan.pk})
  588. })
  589. class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
  590. permission_required = 'ipam.add_vlan'
  591. form = VLANImportForm
  592. table = VLANTable
  593. template_name = 'ipam/vlan_import.html'
  594. obj_list_url = 'ipam:vlan_list'
  595. class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
  596. permission_required = 'ipam.change_vlan'
  597. cls = VLAN
  598. form = VLANBulkEditForm
  599. template_name = 'ipam/vlan_bulk_edit.html'
  600. default_redirect_url = 'ipam:vlan_list'
  601. def update_objects(self, pk_list, form):
  602. fields_to_update = {}
  603. for field in ['site', 'status', 'role']:
  604. if form.cleaned_data[field]:
  605. fields_to_update[field] = form.cleaned_data[field]
  606. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  607. messages.success(self.request, "Updated {} VLANs".format(updated_count))
  608. class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  609. permission_required = 'ipam.delete_vlan'
  610. cls = VLAN
  611. form = VLANBulkDeleteForm
  612. template_name = 'ipam/vlan_bulk_delete.html'
  613. default_redirect_url = 'ipam:vlan_list'