views.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.db.models import Prefetch
  3. from django.db.models.expressions import RawSQL
  4. from django.shortcuts import get_object_or_404, redirect, render
  5. from django.urls import reverse
  6. from django.utils.translation import gettext as _
  7. from circuits.models import Provider
  8. from dcim.filtersets import InterfaceFilterSet
  9. from dcim.models import Interface, Site
  10. from netbox.views import generic
  11. from utilities.tables import get_table_ordering
  12. from utilities.utils import count_related
  13. from utilities.views import ViewTab, register_model_view
  14. from virtualization.filtersets import VMInterfaceFilterSet
  15. from virtualization.models import VMInterface
  16. from . import filtersets, forms, tables
  17. from .choices import PrefixStatusChoices
  18. from .constants import *
  19. from .models import *
  20. from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans
  21. #
  22. # VRFs
  23. #
  24. class VRFListView(generic.ObjectListView):
  25. queryset = VRF.objects.all()
  26. filterset = filtersets.VRFFilterSet
  27. filterset_form = forms.VRFFilterForm
  28. table = tables.VRFTable
  29. @register_model_view(VRF)
  30. class VRFView(generic.ObjectView):
  31. queryset = VRF.objects.all()
  32. def get_extra_context(self, request, instance):
  33. related_models = (
  34. (Prefix.objects.restrict(request.user, 'view').filter(vrf=instance), 'vrf_id'),
  35. (IPAddress.objects.restrict(request.user, 'view').filter(vrf=instance), 'vrf_id'),
  36. )
  37. import_targets_table = tables.RouteTargetTable(
  38. instance.import_targets.all(),
  39. orderable=False
  40. )
  41. export_targets_table = tables.RouteTargetTable(
  42. instance.export_targets.all(),
  43. orderable=False
  44. )
  45. return {
  46. 'related_models': related_models,
  47. 'import_targets_table': import_targets_table,
  48. 'export_targets_table': export_targets_table,
  49. }
  50. @register_model_view(VRF, 'edit')
  51. class VRFEditView(generic.ObjectEditView):
  52. queryset = VRF.objects.all()
  53. form = forms.VRFForm
  54. @register_model_view(VRF, 'delete')
  55. class VRFDeleteView(generic.ObjectDeleteView):
  56. queryset = VRF.objects.all()
  57. class VRFBulkImportView(generic.BulkImportView):
  58. queryset = VRF.objects.all()
  59. model_form = forms.VRFImportForm
  60. class VRFBulkEditView(generic.BulkEditView):
  61. queryset = VRF.objects.all()
  62. filterset = filtersets.VRFFilterSet
  63. table = tables.VRFTable
  64. form = forms.VRFBulkEditForm
  65. class VRFBulkDeleteView(generic.BulkDeleteView):
  66. queryset = VRF.objects.all()
  67. filterset = filtersets.VRFFilterSet
  68. table = tables.VRFTable
  69. #
  70. # Route targets
  71. #
  72. class RouteTargetListView(generic.ObjectListView):
  73. queryset = RouteTarget.objects.all()
  74. filterset = filtersets.RouteTargetFilterSet
  75. filterset_form = forms.RouteTargetFilterForm
  76. table = tables.RouteTargetTable
  77. @register_model_view(RouteTarget)
  78. class RouteTargetView(generic.ObjectView):
  79. queryset = RouteTarget.objects.all()
  80. @register_model_view(RouteTarget, 'edit')
  81. class RouteTargetEditView(generic.ObjectEditView):
  82. queryset = RouteTarget.objects.all()
  83. form = forms.RouteTargetForm
  84. @register_model_view(RouteTarget, 'delete')
  85. class RouteTargetDeleteView(generic.ObjectDeleteView):
  86. queryset = RouteTarget.objects.all()
  87. class RouteTargetBulkImportView(generic.BulkImportView):
  88. queryset = RouteTarget.objects.all()
  89. model_form = forms.RouteTargetImportForm
  90. class RouteTargetBulkEditView(generic.BulkEditView):
  91. queryset = RouteTarget.objects.all()
  92. filterset = filtersets.RouteTargetFilterSet
  93. table = tables.RouteTargetTable
  94. form = forms.RouteTargetBulkEditForm
  95. class RouteTargetBulkDeleteView(generic.BulkDeleteView):
  96. queryset = RouteTarget.objects.all()
  97. filterset = filtersets.RouteTargetFilterSet
  98. table = tables.RouteTargetTable
  99. #
  100. # RIRs
  101. #
  102. class RIRListView(generic.ObjectListView):
  103. queryset = RIR.objects.annotate(
  104. aggregate_count=count_related(Aggregate, 'rir')
  105. )
  106. filterset = filtersets.RIRFilterSet
  107. filterset_form = forms.RIRFilterForm
  108. table = tables.RIRTable
  109. @register_model_view(RIR)
  110. class RIRView(generic.ObjectView):
  111. queryset = RIR.objects.all()
  112. def get_extra_context(self, request, instance):
  113. related_models = (
  114. (Aggregate.objects.restrict(request.user, 'view').filter(rir=instance), 'rir_id'),
  115. )
  116. return {
  117. 'related_models': related_models,
  118. }
  119. @register_model_view(RIR, 'edit')
  120. class RIREditView(generic.ObjectEditView):
  121. queryset = RIR.objects.all()
  122. form = forms.RIRForm
  123. @register_model_view(RIR, 'delete')
  124. class RIRDeleteView(generic.ObjectDeleteView):
  125. queryset = RIR.objects.all()
  126. class RIRBulkImportView(generic.BulkImportView):
  127. queryset = RIR.objects.all()
  128. model_form = forms.RIRImportForm
  129. class RIRBulkEditView(generic.BulkEditView):
  130. queryset = RIR.objects.annotate(
  131. aggregate_count=count_related(Aggregate, 'rir')
  132. )
  133. filterset = filtersets.RIRFilterSet
  134. table = tables.RIRTable
  135. form = forms.RIRBulkEditForm
  136. class RIRBulkDeleteView(generic.BulkDeleteView):
  137. queryset = RIR.objects.annotate(
  138. aggregate_count=count_related(Aggregate, 'rir')
  139. )
  140. filterset = filtersets.RIRFilterSet
  141. table = tables.RIRTable
  142. #
  143. # ASN ranges
  144. #
  145. class ASNRangeListView(generic.ObjectListView):
  146. queryset = ASNRange.objects.annotate_asn_counts()
  147. filterset = filtersets.ASNRangeFilterSet
  148. filterset_form = forms.ASNRangeFilterForm
  149. table = tables.ASNRangeTable
  150. @register_model_view(ASNRange)
  151. class ASNRangeView(generic.ObjectView):
  152. queryset = ASNRange.objects.all()
  153. @register_model_view(ASNRange, 'asns')
  154. class ASNRangeASNsView(generic.ObjectChildrenView):
  155. queryset = ASNRange.objects.all()
  156. child_model = ASN
  157. table = tables.ASNTable
  158. filterset = filtersets.ASNFilterSet
  159. template_name = 'generic/object_children.html'
  160. tab = ViewTab(
  161. label=_('ASNs'),
  162. badge=lambda x: x.get_child_asns().count(),
  163. permission='ipam.view_asn',
  164. weight=500
  165. )
  166. def get_children(self, request, parent):
  167. return parent.get_child_asns().restrict(request.user, 'view').annotate(
  168. site_count=count_related(Site, 'asns'),
  169. provider_count=count_related(Provider, 'asns')
  170. )
  171. @register_model_view(ASNRange, 'edit')
  172. class ASNRangeEditView(generic.ObjectEditView):
  173. queryset = ASNRange.objects.all()
  174. form = forms.ASNRangeForm
  175. @register_model_view(ASNRange, 'delete')
  176. class ASNRangeDeleteView(generic.ObjectDeleteView):
  177. queryset = ASNRange.objects.all()
  178. class ASNRangeBulkImportView(generic.BulkImportView):
  179. queryset = ASNRange.objects.all()
  180. model_form = forms.ASNRangeImportForm
  181. class ASNRangeBulkEditView(generic.BulkEditView):
  182. queryset = ASNRange.objects.annotate_asn_counts()
  183. filterset = filtersets.ASNRangeFilterSet
  184. table = tables.ASNRangeTable
  185. form = forms.ASNRangeBulkEditForm
  186. class ASNRangeBulkDeleteView(generic.BulkDeleteView):
  187. queryset = ASNRange.objects.annotate_asn_counts()
  188. filterset = filtersets.ASNRangeFilterSet
  189. table = tables.ASNRangeTable
  190. #
  191. # ASNs
  192. #
  193. class ASNListView(generic.ObjectListView):
  194. queryset = ASN.objects.annotate(
  195. site_count=count_related(Site, 'asns'),
  196. provider_count=count_related(Provider, 'asns')
  197. )
  198. filterset = filtersets.ASNFilterSet
  199. filterset_form = forms.ASNFilterForm
  200. table = tables.ASNTable
  201. @register_model_view(ASN)
  202. class ASNView(generic.ObjectView):
  203. queryset = ASN.objects.all()
  204. def get_extra_context(self, request, instance):
  205. related_models = (
  206. (Site.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
  207. (Provider.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
  208. )
  209. return {
  210. 'related_models': related_models,
  211. }
  212. @register_model_view(ASN, 'edit')
  213. class ASNEditView(generic.ObjectEditView):
  214. queryset = ASN.objects.all()
  215. form = forms.ASNForm
  216. @register_model_view(ASN, 'delete')
  217. class ASNDeleteView(generic.ObjectDeleteView):
  218. queryset = ASN.objects.all()
  219. class ASNBulkImportView(generic.BulkImportView):
  220. queryset = ASN.objects.all()
  221. model_form = forms.ASNImportForm
  222. class ASNBulkEditView(generic.BulkEditView):
  223. queryset = ASN.objects.annotate(
  224. site_count=count_related(Site, 'asns')
  225. )
  226. filterset = filtersets.ASNFilterSet
  227. table = tables.ASNTable
  228. form = forms.ASNBulkEditForm
  229. class ASNBulkDeleteView(generic.BulkDeleteView):
  230. queryset = ASN.objects.annotate(
  231. site_count=count_related(Site, 'asns')
  232. )
  233. filterset = filtersets.ASNFilterSet
  234. table = tables.ASNTable
  235. #
  236. # Aggregates
  237. #
  238. class AggregateListView(generic.ObjectListView):
  239. queryset = Aggregate.objects.annotate(
  240. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  241. )
  242. filterset = filtersets.AggregateFilterSet
  243. filterset_form = forms.AggregateFilterForm
  244. table = tables.AggregateTable
  245. @register_model_view(Aggregate)
  246. class AggregateView(generic.ObjectView):
  247. queryset = Aggregate.objects.all()
  248. @register_model_view(Aggregate, 'prefixes')
  249. class AggregatePrefixesView(generic.ObjectChildrenView):
  250. queryset = Aggregate.objects.all()
  251. child_model = Prefix
  252. table = tables.PrefixTable
  253. filterset = filtersets.PrefixFilterSet
  254. template_name = 'ipam/aggregate/prefixes.html'
  255. tab = ViewTab(
  256. label=_('Prefixes'),
  257. badge=lambda x: x.get_child_prefixes().count(),
  258. permission='ipam.view_prefix',
  259. weight=500
  260. )
  261. def get_children(self, request, parent):
  262. return Prefix.objects.restrict(request.user, 'view').filter(
  263. prefix__net_contained_or_equal=str(parent.prefix)
  264. ).prefetch_related('site', 'role', 'tenant', 'tenant__group', 'vlan')
  265. def prep_table_data(self, request, queryset, parent):
  266. # Determine whether to show assigned prefixes, available prefixes, or both
  267. show_available = bool(request.GET.get('show_available', 'true') == 'true')
  268. show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
  269. return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
  270. def get_extra_context(self, request, instance):
  271. return {
  272. 'bulk_querystring': f'within={instance.prefix}',
  273. 'first_available_prefix': instance.get_first_available_prefix(),
  274. 'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
  275. 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
  276. }
  277. @register_model_view(Aggregate, 'edit')
  278. class AggregateEditView(generic.ObjectEditView):
  279. queryset = Aggregate.objects.all()
  280. form = forms.AggregateForm
  281. @register_model_view(Aggregate, 'delete')
  282. class AggregateDeleteView(generic.ObjectDeleteView):
  283. queryset = Aggregate.objects.all()
  284. class AggregateBulkImportView(generic.BulkImportView):
  285. queryset = Aggregate.objects.all()
  286. model_form = forms.AggregateImportForm
  287. class AggregateBulkEditView(generic.BulkEditView):
  288. queryset = Aggregate.objects.annotate(
  289. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  290. )
  291. filterset = filtersets.AggregateFilterSet
  292. table = tables.AggregateTable
  293. form = forms.AggregateBulkEditForm
  294. class AggregateBulkDeleteView(generic.BulkDeleteView):
  295. queryset = Aggregate.objects.annotate(
  296. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  297. )
  298. filterset = filtersets.AggregateFilterSet
  299. table = tables.AggregateTable
  300. #
  301. # Prefix/VLAN roles
  302. #
  303. class RoleListView(generic.ObjectListView):
  304. queryset = Role.objects.annotate(
  305. prefix_count=count_related(Prefix, 'role'),
  306. iprange_count=count_related(IPRange, 'role'),
  307. vlan_count=count_related(VLAN, 'role')
  308. )
  309. filterset = filtersets.RoleFilterSet
  310. filterset_form = forms.RoleFilterForm
  311. table = tables.RoleTable
  312. @register_model_view(Role)
  313. class RoleView(generic.ObjectView):
  314. queryset = Role.objects.all()
  315. def get_extra_context(self, request, instance):
  316. related_models = (
  317. (Prefix.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
  318. (IPRange.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
  319. (VLAN.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
  320. )
  321. return {
  322. 'related_models': related_models,
  323. }
  324. @register_model_view(Role, 'edit')
  325. class RoleEditView(generic.ObjectEditView):
  326. queryset = Role.objects.all()
  327. form = forms.RoleForm
  328. @register_model_view(Role, 'delete')
  329. class RoleDeleteView(generic.ObjectDeleteView):
  330. queryset = Role.objects.all()
  331. class RoleBulkImportView(generic.BulkImportView):
  332. queryset = Role.objects.all()
  333. model_form = forms.RoleImportForm
  334. class RoleBulkEditView(generic.BulkEditView):
  335. queryset = Role.objects.all()
  336. filterset = filtersets.RoleFilterSet
  337. table = tables.RoleTable
  338. form = forms.RoleBulkEditForm
  339. class RoleBulkDeleteView(generic.BulkDeleteView):
  340. queryset = Role.objects.all()
  341. filterset = filtersets.RoleFilterSet
  342. table = tables.RoleTable
  343. #
  344. # Prefixes
  345. #
  346. class PrefixListView(generic.ObjectListView):
  347. queryset = Prefix.objects.all()
  348. filterset = filtersets.PrefixFilterSet
  349. filterset_form = forms.PrefixFilterForm
  350. table = tables.PrefixTable
  351. template_name = 'ipam/prefix_list.html'
  352. @register_model_view(Prefix)
  353. class PrefixView(generic.ObjectView):
  354. queryset = Prefix.objects.all()
  355. def get_extra_context(self, request, instance):
  356. try:
  357. aggregate = Aggregate.objects.restrict(request.user, 'view').get(
  358. prefix__net_contains_or_equals=str(instance.prefix)
  359. )
  360. except Aggregate.DoesNotExist:
  361. aggregate = None
  362. # Parent prefixes table
  363. parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  364. Q(vrf=instance.vrf) | Q(vrf__isnull=True, status=PrefixStatusChoices.STATUS_CONTAINER)
  365. ).filter(
  366. prefix__net_contains=str(instance.prefix)
  367. ).prefetch_related(
  368. 'site', 'role', 'tenant', 'vlan',
  369. )
  370. parent_prefix_table = tables.PrefixTable(
  371. list(parent_prefixes),
  372. exclude=('vrf', 'utilization'),
  373. orderable=False
  374. )
  375. # Duplicate prefixes table
  376. duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  377. vrf=instance.vrf, prefix=str(instance.prefix)
  378. ).exclude(
  379. pk=instance.pk
  380. ).prefetch_related(
  381. 'site', 'role', 'tenant', 'vlan',
  382. )
  383. duplicate_prefix_table = tables.PrefixTable(
  384. list(duplicate_prefixes),
  385. exclude=('vrf', 'utilization'),
  386. orderable=False
  387. )
  388. return {
  389. 'aggregate': aggregate,
  390. 'parent_prefix_table': parent_prefix_table,
  391. 'duplicate_prefix_table': duplicate_prefix_table,
  392. }
  393. @register_model_view(Prefix, 'prefixes')
  394. class PrefixPrefixesView(generic.ObjectChildrenView):
  395. queryset = Prefix.objects.all()
  396. child_model = Prefix
  397. table = tables.PrefixTable
  398. filterset = filtersets.PrefixFilterSet
  399. template_name = 'ipam/prefix/prefixes.html'
  400. tab = ViewTab(
  401. label=_('Child Prefixes'),
  402. badge=lambda x: x.get_child_prefixes().count(),
  403. permission='ipam.view_prefix',
  404. weight=500
  405. )
  406. def get_children(self, request, parent):
  407. return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
  408. 'site', 'vrf', 'vlan', 'role', 'tenant', 'tenant__group'
  409. )
  410. def prep_table_data(self, request, queryset, parent):
  411. # Determine whether to show assigned prefixes, available prefixes, or both
  412. show_available = bool(request.GET.get('show_available', 'true') == 'true')
  413. show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
  414. return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
  415. def get_extra_context(self, request, instance):
  416. return {
  417. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}",
  418. 'first_available_prefix': instance.get_first_available_prefix(),
  419. 'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
  420. 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
  421. }
  422. @register_model_view(Prefix, 'ipranges', path='ip-ranges')
  423. class PrefixIPRangesView(generic.ObjectChildrenView):
  424. queryset = Prefix.objects.all()
  425. child_model = IPRange
  426. table = tables.IPRangeTable
  427. filterset = filtersets.IPRangeFilterSet
  428. template_name = 'ipam/prefix/ip_ranges.html'
  429. tab = ViewTab(
  430. label=_('Child Ranges'),
  431. badge=lambda x: x.get_child_ranges().count(),
  432. permission='ipam.view_iprange',
  433. weight=600
  434. )
  435. def get_children(self, request, parent):
  436. return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
  437. 'tenant__group',
  438. )
  439. def get_extra_context(self, request, instance):
  440. return {
  441. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
  442. 'first_available_ip': instance.get_first_available_ip(),
  443. }
  444. @register_model_view(Prefix, 'ipaddresses', path='ip-addresses')
  445. class PrefixIPAddressesView(generic.ObjectChildrenView):
  446. queryset = Prefix.objects.all()
  447. child_model = IPAddress
  448. table = tables.IPAddressTable
  449. filterset = filtersets.IPAddressFilterSet
  450. template_name = 'ipam/prefix/ip_addresses.html'
  451. tab = ViewTab(
  452. label=_('IP Addresses'),
  453. badge=lambda x: x.get_child_ips().count(),
  454. permission='ipam.view_ipaddress',
  455. weight=700
  456. )
  457. def get_children(self, request, parent):
  458. return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
  459. def prep_table_data(self, request, queryset, parent):
  460. if not get_table_ordering(request, self.table):
  461. return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool)
  462. return queryset
  463. def get_extra_context(self, request, instance):
  464. return {
  465. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
  466. 'first_available_ip': instance.get_first_available_ip(),
  467. }
  468. @register_model_view(Prefix, 'edit')
  469. class PrefixEditView(generic.ObjectEditView):
  470. queryset = Prefix.objects.all()
  471. form = forms.PrefixForm
  472. @register_model_view(Prefix, 'delete')
  473. class PrefixDeleteView(generic.ObjectDeleteView):
  474. queryset = Prefix.objects.all()
  475. class PrefixBulkImportView(generic.BulkImportView):
  476. queryset = Prefix.objects.all()
  477. model_form = forms.PrefixImportForm
  478. class PrefixBulkEditView(generic.BulkEditView):
  479. queryset = Prefix.objects.prefetch_related('vrf__tenant')
  480. filterset = filtersets.PrefixFilterSet
  481. table = tables.PrefixTable
  482. form = forms.PrefixBulkEditForm
  483. class PrefixBulkDeleteView(generic.BulkDeleteView):
  484. queryset = Prefix.objects.prefetch_related('vrf__tenant')
  485. filterset = filtersets.PrefixFilterSet
  486. table = tables.PrefixTable
  487. #
  488. # IP Ranges
  489. #
  490. class IPRangeListView(generic.ObjectListView):
  491. queryset = IPRange.objects.all()
  492. filterset = filtersets.IPRangeFilterSet
  493. filterset_form = forms.IPRangeFilterForm
  494. table = tables.IPRangeTable
  495. @register_model_view(IPRange)
  496. class IPRangeView(generic.ObjectView):
  497. queryset = IPRange.objects.all()
  498. @register_model_view(IPRange, 'ipaddresses', path='ip-addresses')
  499. class IPRangeIPAddressesView(generic.ObjectChildrenView):
  500. queryset = IPRange.objects.all()
  501. child_model = IPAddress
  502. table = tables.IPAddressTable
  503. filterset = filtersets.IPAddressFilterSet
  504. template_name = 'ipam/iprange/ip_addresses.html'
  505. tab = ViewTab(
  506. label=_('IP Addresses'),
  507. badge=lambda x: x.get_child_ips().count(),
  508. permission='ipam.view_ipaddress',
  509. weight=500
  510. )
  511. def get_children(self, request, parent):
  512. return parent.get_child_ips().restrict(request.user, 'view')
  513. @register_model_view(IPRange, 'edit')
  514. class IPRangeEditView(generic.ObjectEditView):
  515. queryset = IPRange.objects.all()
  516. form = forms.IPRangeForm
  517. @register_model_view(IPRange, 'delete')
  518. class IPRangeDeleteView(generic.ObjectDeleteView):
  519. queryset = IPRange.objects.all()
  520. class IPRangeBulkImportView(generic.BulkImportView):
  521. queryset = IPRange.objects.all()
  522. model_form = forms.IPRangeImportForm
  523. class IPRangeBulkEditView(generic.BulkEditView):
  524. queryset = IPRange.objects.all()
  525. filterset = filtersets.IPRangeFilterSet
  526. table = tables.IPRangeTable
  527. form = forms.IPRangeBulkEditForm
  528. class IPRangeBulkDeleteView(generic.BulkDeleteView):
  529. queryset = IPRange.objects.all()
  530. filterset = filtersets.IPRangeFilterSet
  531. table = tables.IPRangeTable
  532. #
  533. # IP addresses
  534. #
  535. class IPAddressListView(generic.ObjectListView):
  536. queryset = IPAddress.objects.all()
  537. filterset = filtersets.IPAddressFilterSet
  538. filterset_form = forms.IPAddressFilterForm
  539. table = tables.IPAddressTable
  540. @register_model_view(IPAddress)
  541. class IPAddressView(generic.ObjectView):
  542. queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
  543. def get_extra_context(self, request, instance):
  544. # Parent prefixes table
  545. parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  546. vrf=instance.vrf,
  547. prefix__net_contains_or_equals=str(instance.address.ip)
  548. ).prefetch_related(
  549. 'site', 'role'
  550. )
  551. parent_prefixes_table = tables.PrefixTable(
  552. list(parent_prefixes),
  553. exclude=('vrf', 'utilization'),
  554. orderable=False
  555. )
  556. # Duplicate IPs table
  557. duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
  558. vrf=instance.vrf,
  559. address=str(instance.address)
  560. ).exclude(
  561. pk=instance.pk
  562. ).prefetch_related(
  563. 'nat_inside'
  564. )
  565. # Exclude anycast IPs if this IP is anycast
  566. if instance.role == IPAddressRoleChoices.ROLE_ANYCAST:
  567. duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
  568. # Limit to a maximum of 10 duplicates displayed here
  569. duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
  570. return {
  571. 'parent_prefixes_table': parent_prefixes_table,
  572. 'duplicate_ips_table': duplicate_ips_table,
  573. }
  574. @register_model_view(IPAddress, 'edit')
  575. class IPAddressEditView(generic.ObjectEditView):
  576. queryset = IPAddress.objects.all()
  577. form = forms.IPAddressForm
  578. template_name = 'ipam/ipaddress_edit.html'
  579. def alter_object(self, obj, request, url_args, url_kwargs):
  580. if 'interface' in request.GET:
  581. try:
  582. obj.assigned_object = Interface.objects.get(pk=request.GET['interface'])
  583. except (ValueError, Interface.DoesNotExist):
  584. pass
  585. elif 'vminterface' in request.GET:
  586. try:
  587. obj.assigned_object = VMInterface.objects.get(pk=request.GET['vminterface'])
  588. except (ValueError, VMInterface.DoesNotExist):
  589. pass
  590. elif 'fhrpgroup' in request.GET:
  591. try:
  592. obj.assigned_object = FHRPGroup.objects.get(pk=request.GET['fhrpgroup'])
  593. except (ValueError, FHRPGroup.DoesNotExist):
  594. pass
  595. return obj
  596. # TODO: Standardize or remove this view
  597. class IPAddressAssignView(generic.ObjectView):
  598. """
  599. Search for IPAddresses to be assigned to an Interface.
  600. """
  601. queryset = IPAddress.objects.all()
  602. def dispatch(self, request, *args, **kwargs):
  603. # Redirect user if an interface has not been provided
  604. if 'interface' not in request.GET and 'vminterface' not in request.GET:
  605. return redirect('ipam:ipaddress_add')
  606. return super().dispatch(request, *args, **kwargs)
  607. def get(self, request):
  608. form = forms.IPAddressAssignForm()
  609. return render(request, 'ipam/ipaddress_assign.html', {
  610. 'form': form,
  611. 'return_url': request.GET.get('return_url', ''),
  612. })
  613. def post(self, request):
  614. form = forms.IPAddressAssignForm(request.POST)
  615. table = None
  616. if form.is_valid():
  617. addresses = self.queryset.prefetch_related('vrf', 'tenant')
  618. # Limit to 100 results
  619. addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
  620. table = tables.IPAddressAssignTable(addresses)
  621. return render(request, 'ipam/ipaddress_assign.html', {
  622. 'form': form,
  623. 'table': table,
  624. 'return_url': request.GET.get('return_url'),
  625. })
  626. @register_model_view(IPAddress, 'delete')
  627. class IPAddressDeleteView(generic.ObjectDeleteView):
  628. queryset = IPAddress.objects.all()
  629. class IPAddressBulkCreateView(generic.BulkCreateView):
  630. queryset = IPAddress.objects.all()
  631. form = forms.IPAddressBulkCreateForm
  632. model_form = forms.IPAddressBulkAddForm
  633. pattern_target = 'address'
  634. template_name = 'ipam/ipaddress_bulk_add.html'
  635. class IPAddressBulkImportView(generic.BulkImportView):
  636. queryset = IPAddress.objects.all()
  637. model_form = forms.IPAddressImportForm
  638. class IPAddressBulkEditView(generic.BulkEditView):
  639. queryset = IPAddress.objects.prefetch_related('vrf__tenant')
  640. filterset = filtersets.IPAddressFilterSet
  641. table = tables.IPAddressTable
  642. form = forms.IPAddressBulkEditForm
  643. class IPAddressBulkDeleteView(generic.BulkDeleteView):
  644. queryset = IPAddress.objects.prefetch_related('vrf__tenant')
  645. filterset = filtersets.IPAddressFilterSet
  646. table = tables.IPAddressTable
  647. @register_model_view(IPAddress, 'related_ips', path='related-ip-addresses')
  648. class IPAddressRelatedIPsView(generic.ObjectChildrenView):
  649. queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
  650. child_model = IPAddress
  651. table = tables.IPAddressTable
  652. filterset = filtersets.IPAddressFilterSet
  653. template_name = 'generic/object_children.html'
  654. tab = ViewTab(
  655. label=_('Related IPs'),
  656. badge=lambda x: x.get_related_ips().count(),
  657. weight=500,
  658. hide_if_empty=True,
  659. )
  660. def get_children(self, request, parent):
  661. return parent.get_related_ips().restrict(request.user, 'view')
  662. #
  663. # VLAN groups
  664. #
  665. class VLANGroupListView(generic.ObjectListView):
  666. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  667. filterset = filtersets.VLANGroupFilterSet
  668. filterset_form = forms.VLANGroupFilterForm
  669. table = tables.VLANGroupTable
  670. @register_model_view(VLANGroup)
  671. class VLANGroupView(generic.ObjectView):
  672. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  673. def get_extra_context(self, request, instance):
  674. related_models = (
  675. (VLAN.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
  676. )
  677. return {
  678. 'related_models': related_models,
  679. }
  680. @register_model_view(VLANGroup, 'edit')
  681. class VLANGroupEditView(generic.ObjectEditView):
  682. queryset = VLANGroup.objects.all()
  683. form = forms.VLANGroupForm
  684. @register_model_view(VLANGroup, 'delete')
  685. class VLANGroupDeleteView(generic.ObjectDeleteView):
  686. queryset = VLANGroup.objects.all()
  687. class VLANGroupBulkImportView(generic.BulkImportView):
  688. queryset = VLANGroup.objects.all()
  689. model_form = forms.VLANGroupImportForm
  690. class VLANGroupBulkEditView(generic.BulkEditView):
  691. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  692. filterset = filtersets.VLANGroupFilterSet
  693. table = tables.VLANGroupTable
  694. form = forms.VLANGroupBulkEditForm
  695. class VLANGroupBulkDeleteView(generic.BulkDeleteView):
  696. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  697. filterset = filtersets.VLANGroupFilterSet
  698. table = tables.VLANGroupTable
  699. @register_model_view(VLANGroup, 'vlans')
  700. class VLANGroupVLANsView(generic.ObjectChildrenView):
  701. queryset = VLANGroup.objects.all()
  702. child_model = VLAN
  703. table = tables.VLANTable
  704. filterset = filtersets.VLANFilterSet
  705. template_name = 'generic/object_children.html'
  706. tab = ViewTab(
  707. label=_('VLANs'),
  708. badge=lambda x: x.get_child_vlans().count(),
  709. permission='ipam.view_vlan',
  710. weight=500
  711. )
  712. def get_children(self, request, parent):
  713. return parent.get_child_vlans().restrict(request.user, 'view').prefetch_related(
  714. Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user)),
  715. 'tenant', 'site', 'role',
  716. )
  717. def prep_table_data(self, request, queryset, parent):
  718. if not get_table_ordering(request, self.table):
  719. return add_available_vlans(queryset, parent)
  720. return queryset
  721. #
  722. # FHRP groups
  723. #
  724. class FHRPGroupListView(generic.ObjectListView):
  725. queryset = FHRPGroup.objects.annotate(
  726. member_count=count_related(FHRPGroupAssignment, 'group')
  727. )
  728. filterset = filtersets.FHRPGroupFilterSet
  729. filterset_form = forms.FHRPGroupFilterForm
  730. table = tables.FHRPGroupTable
  731. @register_model_view(FHRPGroup)
  732. class FHRPGroupView(generic.ObjectView):
  733. queryset = FHRPGroup.objects.all()
  734. def get_extra_context(self, request, instance):
  735. # Get assigned interfaces
  736. members_table = tables.FHRPGroupAssignmentTable(
  737. data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance),
  738. orderable=False
  739. )
  740. members_table.columns.hide('group')
  741. return {
  742. 'members_table': members_table,
  743. 'member_count': FHRPGroupAssignment.objects.filter(group=instance).count(),
  744. }
  745. @register_model_view(FHRPGroup, 'edit')
  746. class FHRPGroupEditView(generic.ObjectEditView):
  747. queryset = FHRPGroup.objects.all()
  748. form = forms.FHRPGroupForm
  749. def get_return_url(self, request, obj=None):
  750. return_url = super().get_return_url(request, obj)
  751. # If we're redirecting the user to the FHRPGroupAssignment creation form,
  752. # initialize the group field with the FHRPGroup we just saved.
  753. if return_url.startswith(reverse('ipam:fhrpgroupassignment_add')):
  754. return_url += f'&group={obj.pk}'
  755. return return_url
  756. def alter_object(self, obj, request, url_args, url_kwargs):
  757. # Workaround to solve #10719. Capture the current user on the FHRPGroup instance so that
  758. # we can evaluate permissions during the creation of a new IPAddress within the form.
  759. obj._user = request.user
  760. return obj
  761. @register_model_view(FHRPGroup, 'delete')
  762. class FHRPGroupDeleteView(generic.ObjectDeleteView):
  763. queryset = FHRPGroup.objects.all()
  764. class FHRPGroupBulkImportView(generic.BulkImportView):
  765. queryset = FHRPGroup.objects.all()
  766. model_form = forms.FHRPGroupImportForm
  767. class FHRPGroupBulkEditView(generic.BulkEditView):
  768. queryset = FHRPGroup.objects.all()
  769. filterset = filtersets.FHRPGroupFilterSet
  770. table = tables.FHRPGroupTable
  771. form = forms.FHRPGroupBulkEditForm
  772. class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
  773. queryset = FHRPGroup.objects.all()
  774. filterset = filtersets.FHRPGroupFilterSet
  775. table = tables.FHRPGroupTable
  776. #
  777. # FHRP group assignments
  778. #
  779. @register_model_view(FHRPGroupAssignment, 'edit')
  780. class FHRPGroupAssignmentEditView(generic.ObjectEditView):
  781. queryset = FHRPGroupAssignment.objects.all()
  782. form = forms.FHRPGroupAssignmentForm
  783. template_name = 'ipam/fhrpgroupassignment_edit.html'
  784. def alter_object(self, instance, request, args, kwargs):
  785. if not instance.pk:
  786. # Assign the interface based on URL kwargs
  787. content_type = get_object_or_404(ContentType, pk=request.GET.get('interface_type'))
  788. instance.interface = get_object_or_404(content_type.model_class(), pk=request.GET.get('interface_id'))
  789. return instance
  790. @register_model_view(FHRPGroupAssignment, 'delete')
  791. class FHRPGroupAssignmentDeleteView(generic.ObjectDeleteView):
  792. queryset = FHRPGroupAssignment.objects.all()
  793. #
  794. # VLANs
  795. #
  796. class VLANListView(generic.ObjectListView):
  797. queryset = VLAN.objects.all()
  798. filterset = filtersets.VLANFilterSet
  799. filterset_form = forms.VLANFilterForm
  800. table = tables.VLANTable
  801. @register_model_view(VLAN)
  802. class VLANView(generic.ObjectView):
  803. queryset = VLAN.objects.all()
  804. def get_extra_context(self, request, instance):
  805. prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related(
  806. 'vrf', 'site', 'role', 'tenant'
  807. )
  808. prefix_table = tables.PrefixTable(list(prefixes), exclude=('vlan', 'utilization'), orderable=False)
  809. return {
  810. 'prefix_table': prefix_table,
  811. }
  812. @register_model_view(VLAN, 'interfaces')
  813. class VLANInterfacesView(generic.ObjectChildrenView):
  814. queryset = VLAN.objects.all()
  815. child_model = Interface
  816. table = tables.VLANDevicesTable
  817. filterset = InterfaceFilterSet
  818. template_name = 'generic/object_children.html'
  819. tab = ViewTab(
  820. label=_('Device Interfaces'),
  821. badge=lambda x: x.get_interfaces().count(),
  822. permission='dcim.view_interface',
  823. weight=500
  824. )
  825. def get_children(self, request, parent):
  826. return parent.get_interfaces().restrict(request.user, 'view')
  827. @register_model_view(VLAN, 'vminterfaces', path='vm-interfaces')
  828. class VLANVMInterfacesView(generic.ObjectChildrenView):
  829. queryset = VLAN.objects.all()
  830. child_model = VMInterface
  831. table = tables.VLANVirtualMachinesTable
  832. filterset = VMInterfaceFilterSet
  833. template_name = 'generic/object_children.html'
  834. tab = ViewTab(
  835. label=_('VM Interfaces'),
  836. badge=lambda x: x.get_vminterfaces().count(),
  837. permission='virtualization.view_vminterface',
  838. weight=510
  839. )
  840. def get_children(self, request, parent):
  841. return parent.get_vminterfaces().restrict(request.user, 'view')
  842. @register_model_view(VLAN, 'edit')
  843. class VLANEditView(generic.ObjectEditView):
  844. queryset = VLAN.objects.all()
  845. form = forms.VLANForm
  846. template_name = 'ipam/vlan_edit.html'
  847. @register_model_view(VLAN, 'delete')
  848. class VLANDeleteView(generic.ObjectDeleteView):
  849. queryset = VLAN.objects.all()
  850. class VLANBulkImportView(generic.BulkImportView):
  851. queryset = VLAN.objects.all()
  852. model_form = forms.VLANImportForm
  853. class VLANBulkEditView(generic.BulkEditView):
  854. queryset = VLAN.objects.all()
  855. filterset = filtersets.VLANFilterSet
  856. table = tables.VLANTable
  857. form = forms.VLANBulkEditForm
  858. class VLANBulkDeleteView(generic.BulkDeleteView):
  859. queryset = VLAN.objects.all()
  860. filterset = filtersets.VLANFilterSet
  861. table = tables.VLANTable
  862. #
  863. # Service templates
  864. #
  865. class ServiceTemplateListView(generic.ObjectListView):
  866. queryset = ServiceTemplate.objects.all()
  867. filterset = filtersets.ServiceTemplateFilterSet
  868. filterset_form = forms.ServiceTemplateFilterForm
  869. table = tables.ServiceTemplateTable
  870. @register_model_view(ServiceTemplate)
  871. class ServiceTemplateView(generic.ObjectView):
  872. queryset = ServiceTemplate.objects.all()
  873. @register_model_view(ServiceTemplate, 'edit')
  874. class ServiceTemplateEditView(generic.ObjectEditView):
  875. queryset = ServiceTemplate.objects.all()
  876. form = forms.ServiceTemplateForm
  877. @register_model_view(ServiceTemplate, 'delete')
  878. class ServiceTemplateDeleteView(generic.ObjectDeleteView):
  879. queryset = ServiceTemplate.objects.all()
  880. class ServiceTemplateBulkImportView(generic.BulkImportView):
  881. queryset = ServiceTemplate.objects.all()
  882. model_form = forms.ServiceTemplateImportForm
  883. class ServiceTemplateBulkEditView(generic.BulkEditView):
  884. queryset = ServiceTemplate.objects.all()
  885. filterset = filtersets.ServiceTemplateFilterSet
  886. table = tables.ServiceTemplateTable
  887. form = forms.ServiceTemplateBulkEditForm
  888. class ServiceTemplateBulkDeleteView(generic.BulkDeleteView):
  889. queryset = ServiceTemplate.objects.all()
  890. filterset = filtersets.ServiceTemplateFilterSet
  891. table = tables.ServiceTemplateTable
  892. #
  893. # Services
  894. #
  895. class ServiceListView(generic.ObjectListView):
  896. queryset = Service.objects.prefetch_related('device', 'virtual_machine')
  897. filterset = filtersets.ServiceFilterSet
  898. filterset_form = forms.ServiceFilterForm
  899. table = tables.ServiceTable
  900. @register_model_view(Service)
  901. class ServiceView(generic.ObjectView):
  902. queryset = Service.objects.all()
  903. class ServiceCreateView(generic.ObjectEditView):
  904. queryset = Service.objects.all()
  905. form = forms.ServiceCreateForm
  906. template_name = 'ipam/service_create.html'
  907. @register_model_view(Service, 'edit')
  908. class ServiceEditView(generic.ObjectEditView):
  909. queryset = Service.objects.all()
  910. form = forms.ServiceForm
  911. template_name = 'ipam/service_edit.html'
  912. @register_model_view(Service, 'delete')
  913. class ServiceDeleteView(generic.ObjectDeleteView):
  914. queryset = Service.objects.all()
  915. class ServiceBulkImportView(generic.BulkImportView):
  916. queryset = Service.objects.all()
  917. model_form = forms.ServiceImportForm
  918. class ServiceBulkEditView(generic.BulkEditView):
  919. queryset = Service.objects.prefetch_related('device', 'virtual_machine')
  920. filterset = filtersets.ServiceFilterSet
  921. table = tables.ServiceTable
  922. form = forms.ServiceBulkEditForm
  923. class ServiceBulkDeleteView(generic.BulkDeleteView):
  924. queryset = Service.objects.prefetch_related('device', 'virtual_machine')
  925. filterset = filtersets.ServiceFilterSet
  926. table = tables.ServiceTable