views.py 39 KB

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