views.py 27 KB

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