views.py 30 KB

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