views.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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 aggregate(request, pk):
  104. aggregate = get_object_or_404(Aggregate, pk=pk)
  105. # Find all child prefixes contained by this aggregate
  106. child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))\
  107. .select_related('site', 'role').annotate_depth(limit=0)
  108. child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
  109. prefix_table = tables.PrefixTable(child_prefixes)
  110. prefix_table.model = Prefix
  111. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  112. prefix_table.base_columns['pk'].visible = True
  113. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(prefix_table)
  114. return render(request, 'ipam/aggregate.html', {
  115. 'aggregate': aggregate,
  116. 'prefix_table': prefix_table,
  117. })
  118. class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
  119. permission_required = 'ipam.change_aggregate'
  120. model = Aggregate
  121. form_class = forms.AggregateForm
  122. cancel_url = 'ipam:aggregate_list'
  123. class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  124. permission_required = 'ipam.delete_aggregate'
  125. model = Aggregate
  126. redirect_url = 'ipam:aggregate_list'
  127. class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
  128. permission_required = 'ipam.add_aggregate'
  129. form = forms.AggregateImportForm
  130. table = tables.AggregateTable
  131. template_name = 'ipam/aggregate_import.html'
  132. obj_list_url = 'ipam:aggregate_list'
  133. class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
  134. permission_required = 'ipam.change_aggregate'
  135. cls = Aggregate
  136. form = forms.AggregateBulkEditForm
  137. template_name = 'ipam/aggregate_bulk_edit.html'
  138. default_redirect_url = 'ipam:aggregate_list'
  139. def update_objects(self, pk_list, form):
  140. fields_to_update = {}
  141. for field in ['rir', 'date_added', 'description']:
  142. if form.cleaned_data[field]:
  143. fields_to_update[field] = form.cleaned_data[field]
  144. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  145. class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  146. permission_required = 'ipam.delete_aggregate'
  147. cls = Aggregate
  148. form = forms.AggregateBulkDeleteForm
  149. default_redirect_url = 'ipam:aggregate_list'
  150. #
  151. # Prefix/VLAN roles
  152. #
  153. class RoleListView(ObjectListView):
  154. queryset = Role.objects.all()
  155. table = tables.RoleTable
  156. edit_permissions = ['ipam.change_role', 'ipam.delete_role']
  157. template_name = 'ipam/role_list.html'
  158. class RoleEditView(PermissionRequiredMixin, ObjectEditView):
  159. permission_required = 'ipam.change_role'
  160. model = Role
  161. form_class = forms.RoleForm
  162. success_url = 'ipam:role_list'
  163. cancel_url = 'ipam:role_list'
  164. class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  165. permission_required = 'ipam.delete_role'
  166. cls = Role
  167. form = forms.RoleBulkDeleteForm
  168. default_redirect_url = 'ipam:role_list'
  169. #
  170. # Prefixes
  171. #
  172. class PrefixListView(ObjectListView):
  173. queryset = Prefix.objects.select_related('site', 'role')
  174. filter = filters.PrefixFilter
  175. filter_form = forms.PrefixFilterForm
  176. table = tables.PrefixTable
  177. edit_permissions = ['ipam.change_prefix', 'ipam.delete_prefix']
  178. template_name = 'ipam/prefix_list.html'
  179. def alter_queryset(self, request):
  180. # Show only top-level prefixes by default (unless searching)
  181. limit = None if request.GET.get('expand') or request.GET.get('q') else 0
  182. return self.queryset.annotate_depth(limit=limit)
  183. def prefix(request, pk):
  184. prefix = get_object_or_404(Prefix.objects.select_related('site', 'vlan', 'role'), pk=pk)
  185. try:
  186. aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
  187. except Aggregate.DoesNotExist:
  188. aggregate = None
  189. # Count child IP addresses
  190. ipaddress_count = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix)).count()
  191. # Parent prefixes table
  192. parent_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contains=str(prefix.prefix))\
  193. .select_related('site', 'role').annotate_depth()
  194. parent_prefix_table = tables.PrefixBriefTable(parent_prefixes)
  195. # Duplicate prefixes table
  196. duplicate_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix=str(prefix.prefix)).exclude(pk=prefix.pk)\
  197. .select_related('site', 'role')
  198. duplicate_prefix_table = tables.PrefixBriefTable(duplicate_prefixes)
  199. # Child prefixes table
  200. child_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix))\
  201. .select_related('site', 'role').annotate_depth(limit=0)
  202. if child_prefixes:
  203. child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
  204. child_prefix_table = tables.PrefixTable(child_prefixes)
  205. child_prefix_table.model = Prefix
  206. if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
  207. child_prefix_table.base_columns['pk'].visible = True
  208. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(child_prefix_table)
  209. return render(request, 'ipam/prefix.html', {
  210. 'prefix': prefix,
  211. 'aggregate': aggregate,
  212. 'ipaddress_count': ipaddress_count,
  213. 'parent_prefix_table': parent_prefix_table,
  214. 'child_prefix_table': child_prefix_table,
  215. 'duplicate_prefix_table': duplicate_prefix_table,
  216. })
  217. class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
  218. permission_required = 'ipam.change_prefix'
  219. model = Prefix
  220. form_class = forms.PrefixForm
  221. fields_initial = ['site', 'vrf', 'prefix']
  222. cancel_url = 'ipam:prefix_list'
  223. class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  224. permission_required = 'ipam.delete_prefix'
  225. model = Prefix
  226. redirect_url = 'ipam:prefix_list'
  227. class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
  228. permission_required = 'ipam.add_prefix'
  229. form = forms.PrefixImportForm
  230. table = tables.PrefixTable
  231. template_name = 'ipam/prefix_import.html'
  232. obj_list_url = 'ipam:prefix_list'
  233. class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
  234. permission_required = 'ipam.change_prefix'
  235. cls = Prefix
  236. form = forms.PrefixBulkEditForm
  237. template_name = 'ipam/prefix_bulk_edit.html'
  238. default_redirect_url = 'ipam:prefix_list'
  239. def update_objects(self, pk_list, form):
  240. fields_to_update = {}
  241. if form.cleaned_data['vrf']:
  242. fields_to_update['vrf'] = form.cleaned_data['vrf']
  243. elif form.cleaned_data['vrf_global']:
  244. fields_to_update['vrf'] = None
  245. for field in ['site', 'status', 'role', 'description']:
  246. if form.cleaned_data[field]:
  247. fields_to_update[field] = form.cleaned_data[field]
  248. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  249. class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  250. permission_required = 'ipam.delete_prefix'
  251. cls = Prefix
  252. form = forms.PrefixBulkDeleteForm
  253. default_redirect_url = 'ipam:prefix_list'
  254. def prefix_ipaddresses(request, pk):
  255. prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
  256. # Find all IPAddresses belonging to this Prefix
  257. ipaddresses = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))\
  258. .select_related('vrf', 'interface__device', 'primary_for')
  259. ip_table = tables.IPAddressTable(ipaddresses)
  260. ip_table.model = IPAddress
  261. if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
  262. ip_table.base_columns['pk'].visible = True
  263. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(ip_table)
  264. return render(request, 'ipam/prefix_ipaddresses.html', {
  265. 'prefix': prefix,
  266. 'ip_table': ip_table,
  267. })
  268. #
  269. # IP addresses
  270. #
  271. class IPAddressListView(ObjectListView):
  272. queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_for')
  273. filter = filters.IPAddressFilter
  274. filter_form = forms.IPAddressFilterForm
  275. table = tables.IPAddressTable
  276. edit_permissions = ['ipam.change_ipaddress', 'ipam.delete_ipaddress']
  277. template_name = 'ipam/ipaddress_list.html'
  278. def ipaddress(request, pk):
  279. ipaddress = get_object_or_404(IPAddress.objects.select_related('interface__device'), pk=pk)
  280. parent_prefixes = Prefix.objects.filter(vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip))
  281. related_ips = IPAddress.objects.select_related('interface__device').exclude(pk=ipaddress.pk)\
  282. .filter(vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address))
  283. related_ips_table = tables.IPAddressBriefTable(related_ips)
  284. RequestConfig(request, paginate={'klass': EnhancedPaginator}).configure(related_ips_table)
  285. return render(request, 'ipam/ipaddress.html', {
  286. 'ipaddress': ipaddress,
  287. 'parent_prefixes': parent_prefixes,
  288. 'related_ips_table': related_ips_table,
  289. })
  290. class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
  291. permission_required = 'ipam.change_ipaddress'
  292. model = IPAddress
  293. form_class = forms.IPAddressForm
  294. fields_initial = ['ipaddress']
  295. template_name = 'ipam/ipaddress_edit.html'
  296. cancel_url = 'ipam:ipaddress_list'
  297. class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  298. permission_required = 'ipam.delete_ipaddress'
  299. model = IPAddress
  300. redirect_url = 'ipam:ipaddress_list'
  301. class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
  302. permission_required = 'ipam.add_ipaddress'
  303. form = forms.IPAddressImportForm
  304. table = tables.IPAddressTable
  305. template_name = 'ipam/ipaddress_import.html'
  306. obj_list_url = 'ipam:ipaddress_list'
  307. def save_obj(self, obj):
  308. obj.save()
  309. # Update primary IP for device if needed
  310. try:
  311. device = obj.primary_for
  312. device.primary_ip = obj
  313. device.save()
  314. except Device.DoesNotExist:
  315. pass
  316. class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
  317. permission_required = 'ipam.change_ipaddress'
  318. cls = IPAddress
  319. form = forms.IPAddressBulkEditForm
  320. template_name = 'ipam/ipaddress_bulk_edit.html'
  321. default_redirect_url = 'ipam:ipaddress_list'
  322. def update_objects(self, pk_list, form):
  323. fields_to_update = {}
  324. if form.cleaned_data['vrf']:
  325. fields_to_update['vrf'] = form.cleaned_data['vrf']
  326. elif form.cleaned_data['vrf_global']:
  327. fields_to_update['vrf'] = None
  328. for field in ['description']:
  329. if form.cleaned_data[field]:
  330. fields_to_update[field] = form.cleaned_data[field]
  331. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  332. class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  333. permission_required = 'ipam.delete_ipaddress'
  334. cls = IPAddress
  335. form = forms.IPAddressBulkDeleteForm
  336. default_redirect_url = 'ipam:ipaddress_list'
  337. #
  338. # VLANs
  339. #
  340. class VLANListView(ObjectListView):
  341. queryset = VLAN.objects.select_related('site', 'role')
  342. filter = filters.VLANFilter
  343. filter_form = forms.VLANFilterForm
  344. table = tables.VLANTable
  345. edit_permissions = ['ipam.change_vlan', 'ipam.delete_vlan']
  346. template_name = 'ipam/vlan_list.html'
  347. def vlan(request, pk):
  348. vlan = get_object_or_404(VLAN.objects.select_related('site', 'role'), pk=pk)
  349. prefixes = Prefix.objects.filter(vlan=vlan)
  350. return render(request, 'ipam/vlan.html', {
  351. 'vlan': vlan,
  352. 'prefixes': prefixes,
  353. })
  354. class VLANEditView(PermissionRequiredMixin, ObjectEditView):
  355. permission_required = 'ipam.change_vlan'
  356. model = VLAN
  357. form_class = forms.VLANForm
  358. cancel_url = 'ipam:vlan_list'
  359. class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  360. permission_required = 'ipam.delete_vlan'
  361. model = VLAN
  362. redirect_url = 'ipam:vlan_list'
  363. class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
  364. permission_required = 'ipam.add_vlan'
  365. form = forms.VLANImportForm
  366. table = tables.VLANTable
  367. template_name = 'ipam/vlan_import.html'
  368. obj_list_url = 'ipam:vlan_list'
  369. class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
  370. permission_required = 'ipam.change_vlan'
  371. cls = VLAN
  372. form = forms.VLANBulkEditForm
  373. template_name = 'ipam/vlan_bulk_edit.html'
  374. default_redirect_url = 'ipam:vlan_list'
  375. def update_objects(self, pk_list, form):
  376. fields_to_update = {}
  377. for field in ['site', 'status', 'role']:
  378. if form.cleaned_data[field]:
  379. fields_to_update[field] = form.cleaned_data[field]
  380. return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  381. class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  382. permission_required = 'ipam.delete_vlan'
  383. cls = VLAN
  384. form = forms.VLANBulkDeleteForm
  385. default_redirect_url = 'ipam:vlan_list'