views.py 18 KB

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