views.py 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991
  1. import django_filters
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.db.models import Prefetch
  4. from django.db.models.expressions import RawSQL
  5. from django.shortcuts import get_object_or_404, redirect, render
  6. from django.urls import reverse
  7. from django.utils.translation import gettext_lazy as _
  8. from django_filters.constants import EMPTY_VALUES
  9. from circuits.models import Provider
  10. from dcim.filtersets import InterfaceFilterSet
  11. from dcim.forms import InterfaceFilterForm
  12. from dcim.models import Device, Interface, Site
  13. from extras.ui.panels import CustomFieldsPanel, TagsPanel
  14. from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport
  15. from netbox.ui import actions, layout
  16. from netbox.ui.panels import (
  17. CommentsPanel,
  18. ContextTablePanel,
  19. ObjectsTablePanel,
  20. PluginContentPanel,
  21. RelatedObjectsPanel,
  22. TemplatePanel,
  23. )
  24. from netbox.views import generic
  25. from utilities.query import count_related
  26. from utilities.tables import get_table_ordering
  27. from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
  28. from virtualization.filtersets import VMInterfaceFilterSet
  29. from virtualization.forms import VMInterfaceFilterForm
  30. from virtualization.models import VirtualMachine, VMInterface
  31. from . import filtersets, forms, tables
  32. from .choices import PrefixStatusChoices
  33. from .constants import *
  34. from .models import *
  35. from .ui import panels
  36. from .utils import add_available_vlans, add_requested_prefixes, annotate_ip_space
  37. #
  38. # VRFs
  39. #
  40. @register_model_view(VRF, 'list', path='', detail=False)
  41. class VRFListView(generic.ObjectListView):
  42. queryset = VRF.objects.all()
  43. filterset = filtersets.VRFFilterSet
  44. filterset_form = forms.VRFFilterForm
  45. table = tables.VRFTable
  46. @register_model_view(VRF)
  47. class VRFView(GetRelatedModelsMixin, generic.ObjectView):
  48. queryset = VRF.objects.all()
  49. template_name = 'generic/object.html'
  50. layout = layout.Layout(
  51. layout.Row(
  52. layout.Column(
  53. panels.VRFPanel(),
  54. TagsPanel(),
  55. PluginContentPanel('left_page'),
  56. ),
  57. layout.Column(
  58. RelatedObjectsPanel(),
  59. CustomFieldsPanel(),
  60. CommentsPanel(),
  61. PluginContentPanel('right_page'),
  62. ),
  63. ),
  64. layout.Row(
  65. layout.Column(
  66. ContextTablePanel('import_targets_table', title=_('Import route targets')),
  67. ),
  68. layout.Column(
  69. ContextTablePanel('export_targets_table', title=_('Export route targets')),
  70. ),
  71. ),
  72. layout.Row(
  73. layout.Column(
  74. PluginContentPanel('full_width_page'),
  75. ),
  76. ),
  77. )
  78. def get_extra_context(self, request, instance):
  79. import_targets_table = tables.RouteTargetTable(
  80. instance.import_targets.all(),
  81. orderable=False
  82. )
  83. import_targets_table.configure(request)
  84. export_targets_table = tables.RouteTargetTable(
  85. instance.export_targets.all(),
  86. orderable=False
  87. )
  88. export_targets_table.configure(request)
  89. related_models = self.get_related_models(
  90. request,
  91. instance,
  92. omit=(Interface, VMInterface),
  93. extra=(
  94. (
  95. Interface.objects.restrict(request.user, 'view').filter(vrf=instance),
  96. 'vrf_id',
  97. _('Device Interfaces')
  98. ),
  99. (
  100. VMInterface.objects.restrict(request.user, 'view').filter(vrf=instance),
  101. 'vrf_id',
  102. _('VM Interfaces')
  103. ),
  104. ),
  105. )
  106. return {
  107. 'related_models': related_models,
  108. 'import_targets_table': import_targets_table,
  109. 'export_targets_table': export_targets_table,
  110. }
  111. @register_model_view(VRF, 'add', detail=False)
  112. @register_model_view(VRF, 'edit')
  113. class VRFEditView(generic.ObjectEditView):
  114. queryset = VRF.objects.all()
  115. form = forms.VRFForm
  116. @register_model_view(VRF, 'delete')
  117. class VRFDeleteView(generic.ObjectDeleteView):
  118. queryset = VRF.objects.all()
  119. @register_model_view(VRF, 'bulk_import', path='import', detail=False)
  120. class VRFBulkImportView(generic.BulkImportView):
  121. queryset = VRF.objects.all()
  122. model_form = forms.VRFImportForm
  123. @register_model_view(VRF, 'bulk_edit', path='edit', detail=False)
  124. class VRFBulkEditView(generic.BulkEditView):
  125. queryset = VRF.objects.all()
  126. filterset = filtersets.VRFFilterSet
  127. table = tables.VRFTable
  128. form = forms.VRFBulkEditForm
  129. @register_model_view(VRF, 'bulk_rename', path='rename', detail=False)
  130. class VRFBulkRenameView(generic.BulkRenameView):
  131. queryset = VRF.objects.all()
  132. filterset = filtersets.VRFFilterSet
  133. @register_model_view(VRF, 'bulk_delete', path='delete', detail=False)
  134. class VRFBulkDeleteView(generic.BulkDeleteView):
  135. queryset = VRF.objects.all()
  136. filterset = filtersets.VRFFilterSet
  137. table = tables.VRFTable
  138. #
  139. # Route targets
  140. #
  141. @register_model_view(RouteTarget, 'list', path='', detail=False)
  142. class RouteTargetListView(generic.ObjectListView):
  143. queryset = RouteTarget.objects.all()
  144. filterset = filtersets.RouteTargetFilterSet
  145. filterset_form = forms.RouteTargetFilterForm
  146. table = tables.RouteTargetTable
  147. @register_model_view(RouteTarget)
  148. class RouteTargetView(generic.ObjectView):
  149. queryset = RouteTarget.objects.all()
  150. template_name = 'generic/object.html'
  151. layout = layout.Layout(
  152. layout.Row(
  153. layout.Column(
  154. panels.RouteTargetPanel(),
  155. TagsPanel(),
  156. PluginContentPanel('left_page'),
  157. ),
  158. layout.Column(
  159. CustomFieldsPanel(),
  160. CommentsPanel(),
  161. PluginContentPanel('right_page'),
  162. ),
  163. ),
  164. layout.Row(
  165. layout.Column(
  166. ObjectsTablePanel(
  167. 'ipam.vrf',
  168. filters={'import_target_id': lambda ctx: ctx['object'].pk},
  169. title=_('Importing VRFs'),
  170. ),
  171. ),
  172. layout.Column(
  173. ObjectsTablePanel(
  174. 'ipam.vrf',
  175. filters={'export_target_id': lambda ctx: ctx['object'].pk},
  176. title=_('Exporting VRFs'),
  177. ),
  178. ),
  179. ),
  180. layout.Row(
  181. layout.Column(
  182. ObjectsTablePanel(
  183. 'vpn.l2vpn',
  184. filters={'import_target_id': lambda ctx: ctx['object'].pk},
  185. title=_('Importing L2VPNs'),
  186. ),
  187. ),
  188. layout.Column(
  189. ObjectsTablePanel(
  190. 'vpn.l2vpn',
  191. filters={'export_target_id': lambda ctx: ctx['object'].pk},
  192. title=_('Exporting L2VPNs'),
  193. ),
  194. ),
  195. ),
  196. layout.Row(
  197. layout.Column(
  198. PluginContentPanel('full_width_page'),
  199. ),
  200. ),
  201. )
  202. @register_model_view(RouteTarget, 'add', detail=False)
  203. @register_model_view(RouteTarget, 'edit')
  204. class RouteTargetEditView(generic.ObjectEditView):
  205. queryset = RouteTarget.objects.all()
  206. form = forms.RouteTargetForm
  207. @register_model_view(RouteTarget, 'delete')
  208. class RouteTargetDeleteView(generic.ObjectDeleteView):
  209. queryset = RouteTarget.objects.all()
  210. @register_model_view(RouteTarget, 'bulk_import', path='import', detail=False)
  211. class RouteTargetBulkImportView(generic.BulkImportView):
  212. queryset = RouteTarget.objects.all()
  213. model_form = forms.RouteTargetImportForm
  214. @register_model_view(RouteTarget, 'bulk_edit', path='edit', detail=False)
  215. class RouteTargetBulkEditView(generic.BulkEditView):
  216. queryset = RouteTarget.objects.all()
  217. filterset = filtersets.RouteTargetFilterSet
  218. table = tables.RouteTargetTable
  219. form = forms.RouteTargetBulkEditForm
  220. @register_model_view(RouteTarget, 'bulk_rename', path='rename', detail=False)
  221. class RouteTargetBulkRenameView(generic.BulkRenameView):
  222. queryset = RouteTarget.objects.all()
  223. filterset = filtersets.RouteTargetFilterSet
  224. @register_model_view(RouteTarget, 'bulk_delete', path='delete', detail=False)
  225. class RouteTargetBulkDeleteView(generic.BulkDeleteView):
  226. queryset = RouteTarget.objects.all()
  227. filterset = filtersets.RouteTargetFilterSet
  228. table = tables.RouteTargetTable
  229. #
  230. # RIRs
  231. #
  232. @register_model_view(RIR, 'list', path='', detail=False)
  233. class RIRListView(generic.ObjectListView):
  234. queryset = RIR.objects.annotate(
  235. aggregate_count=count_related(Aggregate, 'rir')
  236. )
  237. filterset = filtersets.RIRFilterSet
  238. filterset_form = forms.RIRFilterForm
  239. table = tables.RIRTable
  240. @register_model_view(RIR)
  241. class RIRView(GetRelatedModelsMixin, generic.ObjectView):
  242. queryset = RIR.objects.all()
  243. layout = layout.SimpleLayout(
  244. left_panels=[
  245. panels.RIRPanel(),
  246. TagsPanel(),
  247. ],
  248. right_panels=[
  249. RelatedObjectsPanel(),
  250. CommentsPanel(),
  251. CustomFieldsPanel(),
  252. ],
  253. )
  254. def get_extra_context(self, request, instance):
  255. return {
  256. 'related_models': self.get_related_models(request, instance),
  257. }
  258. @register_model_view(RIR, 'add', detail=False)
  259. @register_model_view(RIR, 'edit')
  260. class RIREditView(generic.ObjectEditView):
  261. queryset = RIR.objects.all()
  262. form = forms.RIRForm
  263. @register_model_view(RIR, 'delete')
  264. class RIRDeleteView(generic.ObjectDeleteView):
  265. queryset = RIR.objects.all()
  266. @register_model_view(RIR, 'bulk_import', path='import', detail=False)
  267. class RIRBulkImportView(generic.BulkImportView):
  268. queryset = RIR.objects.all()
  269. model_form = forms.RIRImportForm
  270. @register_model_view(RIR, 'bulk_edit', path='edit', detail=False)
  271. class RIRBulkEditView(generic.BulkEditView):
  272. queryset = RIR.objects.annotate(
  273. aggregate_count=count_related(Aggregate, 'rir')
  274. )
  275. filterset = filtersets.RIRFilterSet
  276. table = tables.RIRTable
  277. form = forms.RIRBulkEditForm
  278. @register_model_view(RIR, 'bulk_rename', path='rename', detail=False)
  279. class RIRBulkRenameView(generic.BulkRenameView):
  280. queryset = RIR.objects.all()
  281. filterset = filtersets.RIRFilterSet
  282. @register_model_view(RIR, 'bulk_delete', path='delete', detail=False)
  283. class RIRBulkDeleteView(generic.BulkDeleteView):
  284. queryset = RIR.objects.annotate(
  285. aggregate_count=count_related(Aggregate, 'rir')
  286. )
  287. filterset = filtersets.RIRFilterSet
  288. table = tables.RIRTable
  289. #
  290. # ASN ranges
  291. #
  292. @register_model_view(ASNRange, 'list', path='', detail=False)
  293. class ASNRangeListView(generic.ObjectListView):
  294. queryset = ASNRange.objects.annotate_asn_counts()
  295. filterset = filtersets.ASNRangeFilterSet
  296. filterset_form = forms.ASNRangeFilterForm
  297. table = tables.ASNRangeTable
  298. @register_model_view(ASNRange)
  299. class ASNRangeView(generic.ObjectView):
  300. queryset = ASNRange.objects.all()
  301. layout = layout.SimpleLayout(
  302. left_panels=[
  303. panels.ASNRangePanel(),
  304. TagsPanel(),
  305. ],
  306. right_panels=[
  307. CommentsPanel(),
  308. CustomFieldsPanel(),
  309. ],
  310. )
  311. @register_model_view(ASNRange, 'asns')
  312. class ASNRangeASNsView(generic.ObjectChildrenView):
  313. queryset = ASNRange.objects.all()
  314. child_model = ASN
  315. table = tables.ASNTable
  316. filterset = filtersets.ASNFilterSet
  317. filterset_form = forms.ASNFilterForm
  318. tab = ViewTab(
  319. label=_('ASNs'),
  320. badge=lambda x: x.get_child_asns().count(),
  321. permission='ipam.view_asn',
  322. weight=500
  323. )
  324. def get_children(self, request, parent):
  325. return parent.get_child_asns().restrict(request.user, 'view').annotate(
  326. site_count=count_related(Site, 'asns'),
  327. provider_count=count_related(Provider, 'asns')
  328. )
  329. @register_model_view(ASNRange, 'add', detail=False)
  330. @register_model_view(ASNRange, 'edit')
  331. class ASNRangeEditView(generic.ObjectEditView):
  332. queryset = ASNRange.objects.all()
  333. form = forms.ASNRangeForm
  334. @register_model_view(ASNRange, 'delete')
  335. class ASNRangeDeleteView(generic.ObjectDeleteView):
  336. queryset = ASNRange.objects.all()
  337. @register_model_view(ASNRange, 'bulk_import', path='import', detail=False)
  338. class ASNRangeBulkImportView(generic.BulkImportView):
  339. queryset = ASNRange.objects.all()
  340. model_form = forms.ASNRangeImportForm
  341. @register_model_view(ASNRange, 'bulk_edit', path='edit', detail=False)
  342. class ASNRangeBulkEditView(generic.BulkEditView):
  343. queryset = ASNRange.objects.annotate_asn_counts()
  344. filterset = filtersets.ASNRangeFilterSet
  345. table = tables.ASNRangeTable
  346. form = forms.ASNRangeBulkEditForm
  347. @register_model_view(ASNRange, 'bulk_rename', path='rename', detail=False)
  348. class ASNRangeBulkRenameView(generic.BulkRenameView):
  349. queryset = ASNRange.objects.all()
  350. filterset = filtersets.ASNRangeFilterSet
  351. @register_model_view(ASNRange, 'bulk_delete', path='delete', detail=False)
  352. class ASNRangeBulkDeleteView(generic.BulkDeleteView):
  353. queryset = ASNRange.objects.annotate_asn_counts()
  354. filterset = filtersets.ASNRangeFilterSet
  355. table = tables.ASNRangeTable
  356. #
  357. # ASNs
  358. #
  359. @register_model_view(ASN, 'list', path='', detail=False)
  360. class ASNListView(generic.ObjectListView):
  361. queryset = ASN.objects.annotate(
  362. site_count=count_related(Site, 'asns'),
  363. provider_count=count_related(Provider, 'asns')
  364. )
  365. filterset = filtersets.ASNFilterSet
  366. filterset_form = forms.ASNFilterForm
  367. table = tables.ASNTable
  368. @register_model_view(ASN)
  369. class ASNView(GetRelatedModelsMixin, generic.ObjectView):
  370. queryset = ASN.objects.all()
  371. layout = layout.SimpleLayout(
  372. left_panels=[
  373. panels.ASNPanel(),
  374. TagsPanel(),
  375. ],
  376. right_panels=[
  377. RelatedObjectsPanel(),
  378. CustomFieldsPanel(),
  379. CommentsPanel(),
  380. ],
  381. )
  382. def get_extra_context(self, request, instance):
  383. return {
  384. 'related_models': self.get_related_models(
  385. request,
  386. instance,
  387. extra=(
  388. (Site.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
  389. (Provider.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
  390. ),
  391. ),
  392. }
  393. @register_model_view(ASN, 'add', detail=False)
  394. @register_model_view(ASN, 'edit')
  395. class ASNEditView(generic.ObjectEditView):
  396. queryset = ASN.objects.all()
  397. form = forms.ASNForm
  398. @register_model_view(ASN, 'delete')
  399. class ASNDeleteView(generic.ObjectDeleteView):
  400. queryset = ASN.objects.all()
  401. @register_model_view(ASN, 'bulk_import', path='import', detail=False)
  402. class ASNBulkImportView(generic.BulkImportView):
  403. queryset = ASN.objects.all()
  404. model_form = forms.ASNImportForm
  405. @register_model_view(ASN, 'bulk_edit', path='edit', detail=False)
  406. class ASNBulkEditView(generic.BulkEditView):
  407. queryset = ASN.objects.annotate(
  408. site_count=count_related(Site, 'asns')
  409. )
  410. filterset = filtersets.ASNFilterSet
  411. table = tables.ASNTable
  412. form = forms.ASNBulkEditForm
  413. @register_model_view(ASN, 'bulk_rename', path='rename', detail=False)
  414. class ASNBulkRenameView(generic.BulkRenameView):
  415. queryset = ASN.objects.all()
  416. filterset = filtersets.ASNFilterSet
  417. @register_model_view(ASN, 'bulk_delete', path='delete', detail=False)
  418. class ASNBulkDeleteView(generic.BulkDeleteView):
  419. queryset = ASN.objects.annotate(
  420. site_count=count_related(Site, 'asns')
  421. )
  422. filterset = filtersets.ASNFilterSet
  423. table = tables.ASNTable
  424. #
  425. # Aggregates
  426. #
  427. @register_model_view(Aggregate, 'list', path='', detail=False)
  428. class AggregateListView(generic.ObjectListView):
  429. queryset = Aggregate.objects.annotate(
  430. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  431. )
  432. filterset = filtersets.AggregateFilterSet
  433. filterset_form = forms.AggregateFilterForm
  434. table = tables.AggregateTable
  435. actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
  436. @register_model_view(Aggregate)
  437. class AggregateView(generic.ObjectView):
  438. queryset = Aggregate.objects.all()
  439. layout = layout.SimpleLayout(
  440. left_panels=[
  441. panels.AggregatePanel(),
  442. ],
  443. right_panels=[
  444. CustomFieldsPanel(),
  445. TagsPanel(),
  446. CommentsPanel(),
  447. ],
  448. )
  449. class ChildAvailabilityMixin:
  450. """
  451. Mixin for ObjectChildrenView subclasses that render synthetic "available" rows
  452. (available IP space, prefixes, or VLANs) and must suppress them when the request
  453. activates a child object filter, so objects excluded by the filter are not
  454. misrepresented as available space.
  455. """
  456. def _get_detection_filterset(self, request):
  457. """
  458. Return the bound FilterSet used to evaluate the request, or None if the view declares no
  459. filterset. ObjectChildrenView.get() has already built one and validated its form while
  460. resolving the child queryset, so reuse it rather than paying for a second FilterSet:
  461. get_filters() regenerates every dynamic lookup variant and NetBoxModelFilterSet.__init__
  462. queries the custom fields for the model. Build one only for direct calls where get() has
  463. not run.
  464. """
  465. if self.filterset_instance is not None:
  466. return self.filterset_instance
  467. if self.filterset is None:
  468. return None
  469. return self.filterset(request.GET, request=request)
  470. def _has_active_child_filters(self, request):
  471. """
  472. Return True if the request supplies a valid, non-empty value for any filter declared by
  473. the view's filterset. Saved filters are expanded during filterset instantiation and
  474. dynamic custom field filters are registered on the instance, so both are detected.
  475. Permission constraints and parent scoping never appear in the request, so they cannot
  476. affect the result. The result is memoized because a single request evaluates it from
  477. both prep_table_data and get_extra_context.
  478. """
  479. if hasattr(self, '_active_child_filters'):
  480. return self._active_child_filters
  481. self._active_child_filters = False
  482. # An empty request cannot activate a filter, so skip validating a form for nothing.
  483. if not request.GET:
  484. return False
  485. filterset = self._get_detection_filterset(request)
  486. if filterset is None:
  487. return False
  488. # A non-empty cleaned value means the request activated a declared filter. Emptiness
  489. # follows each filter's own semantics, so absent multi-value fields stay inactive.
  490. # This is a no-op for a reused FilterSet: .qs validated the form to build the queryset.
  491. filterset.form.is_valid()
  492. for name, value in filterset.form.cleaned_data.items():
  493. if isinstance(filterset.filters[name], django_filters.MultipleChoiceFilter):
  494. if value: # mirrors MultipleChoiceFilter.filter()
  495. self._active_child_filters = True
  496. break
  497. elif value not in EMPTY_VALUES: # mirrors Filter.filter()
  498. self._active_child_filters = True
  499. break
  500. return self._active_child_filters
  501. @register_model_view(Aggregate, 'prefixes')
  502. class AggregatePrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
  503. queryset = Aggregate.objects.all()
  504. child_model = Prefix
  505. table = tables.PrefixTable
  506. filterset = filtersets.PrefixFilterSet
  507. filterset_form = forms.PrefixFilterForm
  508. template_name = 'ipam/aggregate/prefixes.html'
  509. tab = ViewTab(
  510. label=_('Prefixes'),
  511. badge=lambda x: x.get_child_prefixes().count(),
  512. permission='ipam.view_prefix',
  513. weight=500
  514. )
  515. def get_children(self, request, parent):
  516. return Prefix.objects.restrict(request.user, 'view').filter(
  517. prefix__net_contained_or_equal=str(parent.prefix)
  518. ).prefetch_related('scope', 'role', 'tenant', 'tenant__group', 'vlan')
  519. def prep_table_data(self, request, queryset, parent):
  520. # Determine whether to show assigned prefixes, available prefixes, or both
  521. show_available = bool(request.GET.get('show_available', 'true') == 'true')
  522. show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
  523. if show_available and self._has_active_child_filters(request):
  524. show_available = False
  525. return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
  526. def get_extra_context(self, request, instance):
  527. show_available = (
  528. bool(request.GET.get('show_available', 'true') == 'true') and
  529. not self._has_active_child_filters(request)
  530. )
  531. return {
  532. 'bulk_querystring': f'within={instance.prefix}',
  533. 'first_available_prefix': instance.get_first_available_prefix(),
  534. 'show_available': show_available,
  535. 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
  536. }
  537. @register_model_view(Aggregate, 'add', detail=False)
  538. @register_model_view(Aggregate, 'edit')
  539. class AggregateEditView(generic.ObjectEditView):
  540. queryset = Aggregate.objects.all()
  541. form = forms.AggregateForm
  542. @register_model_view(Aggregate, 'delete')
  543. class AggregateDeleteView(generic.ObjectDeleteView):
  544. queryset = Aggregate.objects.all()
  545. @register_model_view(Aggregate, 'bulk_import', path='import', detail=False)
  546. class AggregateBulkImportView(generic.BulkImportView):
  547. queryset = Aggregate.objects.all()
  548. model_form = forms.AggregateImportForm
  549. @register_model_view(Aggregate, 'bulk_edit', path='edit', detail=False)
  550. class AggregateBulkEditView(generic.BulkEditView):
  551. queryset = Aggregate.objects.annotate(
  552. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  553. )
  554. filterset = filtersets.AggregateFilterSet
  555. table = tables.AggregateTable
  556. form = forms.AggregateBulkEditForm
  557. @register_model_view(Aggregate, 'bulk_delete', path='delete', detail=False)
  558. class AggregateBulkDeleteView(generic.BulkDeleteView):
  559. queryset = Aggregate.objects.annotate(
  560. child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
  561. )
  562. filterset = filtersets.AggregateFilterSet
  563. table = tables.AggregateTable
  564. #
  565. # Prefix/VLAN roles
  566. #
  567. @register_model_view(Role, 'list', path='', detail=False)
  568. class RoleListView(generic.ObjectListView):
  569. queryset = Role.objects.annotate(
  570. prefix_count=count_related(Prefix, 'role'),
  571. iprange_count=count_related(IPRange, 'role'),
  572. vlan_count=count_related(VLAN, 'role'),
  573. asn_count=count_related(ASN, 'role')
  574. )
  575. filterset = filtersets.RoleFilterSet
  576. filterset_form = forms.RoleFilterForm
  577. table = tables.RoleTable
  578. @register_model_view(Role)
  579. class RoleView(GetRelatedModelsMixin, generic.ObjectView):
  580. queryset = Role.objects.all()
  581. layout = layout.SimpleLayout(
  582. left_panels=[
  583. panels.RolePanel(),
  584. TagsPanel(),
  585. ],
  586. right_panels=[
  587. RelatedObjectsPanel(),
  588. CommentsPanel(),
  589. CustomFieldsPanel(),
  590. ],
  591. )
  592. def get_extra_context(self, request, instance):
  593. return {
  594. 'related_models': self.get_related_models(request, instance),
  595. }
  596. @register_model_view(Role, 'add', detail=False)
  597. @register_model_view(Role, 'edit')
  598. class RoleEditView(generic.ObjectEditView):
  599. queryset = Role.objects.all()
  600. form = forms.RoleForm
  601. @register_model_view(Role, 'delete')
  602. class RoleDeleteView(generic.ObjectDeleteView):
  603. queryset = Role.objects.all()
  604. @register_model_view(Role, 'bulk_import', path='import', detail=False)
  605. class RoleBulkImportView(generic.BulkImportView):
  606. queryset = Role.objects.all()
  607. model_form = forms.RoleImportForm
  608. @register_model_view(Role, 'bulk_edit', path='edit', detail=False)
  609. class RoleBulkEditView(generic.BulkEditView):
  610. queryset = Role.objects.all()
  611. filterset = filtersets.RoleFilterSet
  612. table = tables.RoleTable
  613. form = forms.RoleBulkEditForm
  614. @register_model_view(Role, 'bulk_rename', path='rename', detail=False)
  615. class RoleBulkRenameView(generic.BulkRenameView):
  616. queryset = Role.objects.all()
  617. filterset = filtersets.RoleFilterSet
  618. @register_model_view(Role, 'bulk_delete', path='delete', detail=False)
  619. class RoleBulkDeleteView(generic.BulkDeleteView):
  620. queryset = Role.objects.all()
  621. filterset = filtersets.RoleFilterSet
  622. table = tables.RoleTable
  623. #
  624. # Prefixes
  625. #
  626. @register_model_view(Prefix, 'list', path='', detail=False)
  627. class PrefixListView(generic.ObjectListView):
  628. queryset = Prefix.objects.all()
  629. filterset = filtersets.PrefixFilterSet
  630. filterset_form = forms.PrefixFilterForm
  631. table = tables.PrefixTable
  632. template_name = 'ipam/prefix_list.html'
  633. actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
  634. @register_model_view(Prefix)
  635. class PrefixView(generic.ObjectView):
  636. queryset = Prefix.objects.all()
  637. layout = layout.SimpleLayout(
  638. left_panels=[
  639. panels.PrefixPanel(),
  640. ],
  641. right_panels=[
  642. TemplatePanel('ipam/panels/prefix_addressing.html'),
  643. CustomFieldsPanel(),
  644. TagsPanel(),
  645. CommentsPanel(),
  646. ],
  647. bottom_panels=[
  648. ContextTablePanel('duplicate_prefix_table', title=_('Duplicate prefixes')),
  649. ContextTablePanel('parent_prefix_table', title=_('Parent prefixes')),
  650. ],
  651. )
  652. def get_extra_context(self, request, instance):
  653. # Parent prefixes table
  654. parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  655. Q(vrf=instance.vrf) | Q(vrf__isnull=True, status=PrefixStatusChoices.STATUS_CONTAINER)
  656. ).filter(
  657. prefix__net_contains=str(instance.prefix)
  658. ).prefetch_related(
  659. 'scope', 'role', 'tenant', 'vlan',
  660. )
  661. parent_prefix_table = tables.PrefixTable(
  662. list(parent_prefixes),
  663. exclude=('vrf', 'utilization'),
  664. orderable=False
  665. )
  666. parent_prefix_table.configure(request)
  667. # Duplicate prefixes table
  668. duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  669. vrf=instance.vrf, prefix=str(instance.prefix)
  670. ).exclude(
  671. pk=instance.pk
  672. ).prefetch_related(
  673. 'scope', 'role', 'tenant', 'vlan',
  674. )
  675. duplicate_prefix_table = tables.PrefixTable(
  676. list(duplicate_prefixes),
  677. exclude=('vrf', 'utilization'),
  678. orderable=False
  679. )
  680. duplicate_prefix_table.configure(request)
  681. context = {
  682. 'parent_prefix_table': parent_prefix_table,
  683. }
  684. if duplicate_prefixes.exists():
  685. context['duplicate_prefix_table'] = duplicate_prefix_table
  686. return context
  687. @register_model_view(Prefix, 'prefixes')
  688. class PrefixPrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
  689. queryset = Prefix.objects.all()
  690. child_model = Prefix
  691. table = tables.PrefixTable
  692. filterset = filtersets.PrefixFilterSet
  693. filterset_form = forms.PrefixFilterForm
  694. template_name = 'ipam/prefix/prefixes.html'
  695. tab = ViewTab(
  696. label=_('Child Prefixes'),
  697. badge=lambda x: x.get_child_prefixes().count(),
  698. permission='ipam.view_prefix',
  699. weight=500
  700. )
  701. def get_children(self, request, parent):
  702. return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
  703. 'scope', 'vrf', 'vlan', 'role', 'tenant', 'tenant__group'
  704. )
  705. def prep_table_data(self, request, queryset, parent):
  706. # Determine whether to show assigned prefixes, available prefixes, or both
  707. show_available = bool(request.GET.get('show_available', 'true') == 'true')
  708. show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
  709. if show_available and self._has_active_child_filters(request):
  710. show_available = False
  711. return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
  712. def get_extra_context(self, request, instance):
  713. show_available = (
  714. bool(request.GET.get('show_available', 'true') == 'true') and
  715. not self._has_active_child_filters(request)
  716. )
  717. return {
  718. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}",
  719. 'first_available_prefix': instance.get_first_available_prefix(),
  720. 'show_available': show_available,
  721. 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
  722. }
  723. @register_model_view(Prefix, 'ipranges', path='ip-ranges')
  724. class PrefixIPRangesView(generic.ObjectChildrenView):
  725. queryset = Prefix.objects.all()
  726. child_model = IPRange
  727. table = tables.IPRangeTable
  728. filterset = filtersets.IPRangeFilterSet
  729. filterset_form = forms.IPRangeFilterForm
  730. template_name = 'ipam/prefix/ip_ranges.html'
  731. tab = ViewTab(
  732. label=_('Child Ranges'),
  733. badge=lambda x: x.get_child_ranges().count(),
  734. permission='ipam.view_iprange',
  735. weight=600
  736. )
  737. def get_children(self, request, parent):
  738. return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
  739. 'tenant__group',
  740. )
  741. def get_extra_context(self, request, instance):
  742. return {
  743. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
  744. 'first_available_ip': instance.get_first_available_ip(),
  745. }
  746. @register_model_view(Prefix, 'ipaddresses', path='ip-addresses')
  747. class PrefixIPAddressesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
  748. queryset = Prefix.objects.all()
  749. child_model = IPAddress
  750. table = tables.AnnotatedIPAddressTable
  751. filterset = filtersets.IPAddressFilterSet
  752. filterset_form = forms.IPAddressFilterForm
  753. template_name = 'ipam/prefix/ip_addresses.html'
  754. tab = ViewTab(
  755. label=_('IP Addresses'),
  756. badge=lambda x: x.get_child_ips().count(),
  757. permission='ipam.view_ipaddress',
  758. weight=700
  759. )
  760. def get_children(self, request, parent):
  761. return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
  762. def prep_table_data(self, request, queryset, parent):
  763. # Ordering is checked first: it reads request.GET directly, so a sorted request never
  764. # builds the detection filterset.
  765. if not get_table_ordering(request, self.table) and not self._has_active_child_filters(request):
  766. ip_ranges = parent.get_child_ranges(mark_populated=True).restrict(request.user, 'view')
  767. return annotate_ip_space(parent, ip_addresses=queryset, ip_ranges=ip_ranges)
  768. return super().prep_table_data(request, queryset, parent)
  769. def get_extra_context(self, request, instance):
  770. return {
  771. 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
  772. 'first_available_ip': instance.get_first_available_ip(),
  773. }
  774. @register_model_view(Prefix, 'add', detail=False)
  775. @register_model_view(Prefix, 'edit')
  776. class PrefixEditView(generic.ObjectEditView):
  777. queryset = Prefix.objects.all()
  778. form = forms.PrefixForm
  779. template_name = 'ipam/prefix_edit.html'
  780. @register_model_view(Prefix, 'delete')
  781. class PrefixDeleteView(generic.ObjectDeleteView):
  782. queryset = Prefix.objects.all()
  783. @register_model_view(Prefix, 'bulk_add', path='bulk-add', detail=False)
  784. class PrefixBulkCreateView(generic.BulkCreateView):
  785. queryset = Prefix.objects.all()
  786. form = forms.IPNetworkBulkCreateForm
  787. model_form = forms.PrefixBulkAddForm
  788. pattern_target = 'prefix'
  789. template_name = 'ipam/prefix_bulk_add.html'
  790. @register_model_view(Prefix, 'bulk_import', path='import', detail=False)
  791. class PrefixBulkImportView(generic.BulkImportView):
  792. queryset = Prefix.objects.all()
  793. model_form = forms.PrefixImportForm
  794. @register_model_view(Prefix, 'bulk_edit', path='edit', detail=False)
  795. class PrefixBulkEditView(generic.BulkEditView):
  796. queryset = Prefix.objects.prefetch_related('vrf__tenant')
  797. filterset = filtersets.PrefixFilterSet
  798. table = tables.PrefixTable
  799. form = forms.PrefixBulkEditForm
  800. @register_model_view(Prefix, 'bulk_delete', path='delete', detail=False)
  801. class PrefixBulkDeleteView(generic.BulkDeleteView):
  802. queryset = Prefix.objects.prefetch_related('vrf__tenant')
  803. filterset = filtersets.PrefixFilterSet
  804. table = tables.PrefixTable
  805. #
  806. # IP Ranges
  807. #
  808. @register_model_view(IPRange, 'list', path='', detail=False)
  809. class IPRangeListView(generic.ObjectListView):
  810. queryset = IPRange.objects.all()
  811. filterset = filtersets.IPRangeFilterSet
  812. filterset_form = forms.IPRangeFilterForm
  813. table = tables.IPRangeTable
  814. @register_model_view(IPRange)
  815. class IPRangeView(generic.ObjectView):
  816. queryset = IPRange.objects.all()
  817. layout = layout.SimpleLayout(
  818. left_panels=[
  819. panels.IPRangePanel(),
  820. ],
  821. right_panels=[
  822. TagsPanel(),
  823. CustomFieldsPanel(),
  824. CommentsPanel(),
  825. ],
  826. bottom_panels=[
  827. ContextTablePanel('parent_prefixes_table', title=_('Parent prefixes')),
  828. ],
  829. )
  830. def get_extra_context(self, request, instance):
  831. # Parent prefixes table
  832. parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  833. Q(prefix__net_contains_or_equals=str(instance.start_address.ip)),
  834. Q(prefix__net_contains_or_equals=str(instance.end_address.ip)),
  835. vrf=instance.vrf
  836. ).prefetch_related(
  837. 'scope', 'role', 'tenant', 'vlan', 'role'
  838. )
  839. parent_prefixes_table = tables.PrefixTable(
  840. list(parent_prefixes),
  841. exclude=('vrf', 'utilization'),
  842. orderable=False
  843. )
  844. parent_prefixes_table.configure(request)
  845. return {
  846. 'parent_prefixes_table': parent_prefixes_table,
  847. }
  848. @register_model_view(IPRange, 'ipaddresses', path='ip-addresses')
  849. class IPRangeIPAddressesView(generic.ObjectChildrenView):
  850. queryset = IPRange.objects.all()
  851. child_model = IPAddress
  852. table = tables.IPAddressTable
  853. filterset = filtersets.IPAddressFilterSet
  854. filterset_form = forms.IPRangeFilterForm
  855. template_name = 'ipam/iprange/ip_addresses.html'
  856. tab = ViewTab(
  857. label=_('IP Addresses'),
  858. badge=lambda x: x.get_child_ips().count(),
  859. permission='ipam.view_ipaddress',
  860. weight=500
  861. )
  862. def get_children(self, request, parent):
  863. return parent.get_child_ips().restrict(request.user, 'view')
  864. @register_model_view(IPRange, 'add', detail=False)
  865. @register_model_view(IPRange, 'edit')
  866. class IPRangeEditView(generic.ObjectEditView):
  867. queryset = IPRange.objects.all()
  868. form = forms.IPRangeForm
  869. @register_model_view(IPRange, 'delete')
  870. class IPRangeDeleteView(generic.ObjectDeleteView):
  871. queryset = IPRange.objects.all()
  872. @register_model_view(IPRange, 'bulk_import', path='import', detail=False)
  873. class IPRangeBulkImportView(generic.BulkImportView):
  874. queryset = IPRange.objects.all()
  875. model_form = forms.IPRangeImportForm
  876. @register_model_view(IPRange, 'bulk_edit', path='edit', detail=False)
  877. class IPRangeBulkEditView(generic.BulkEditView):
  878. queryset = IPRange.objects.all()
  879. filterset = filtersets.IPRangeFilterSet
  880. table = tables.IPRangeTable
  881. form = forms.IPRangeBulkEditForm
  882. @register_model_view(IPRange, 'bulk_rename', path='rename', detail=False)
  883. class IPRangeBulkRenameView(generic.BulkRenameView):
  884. queryset = IPRange.objects.all()
  885. filterset = filtersets.IPRangeFilterSet
  886. @register_model_view(IPRange, 'bulk_delete', path='delete', detail=False)
  887. class IPRangeBulkDeleteView(generic.BulkDeleteView):
  888. queryset = IPRange.objects.all()
  889. filterset = filtersets.IPRangeFilterSet
  890. table = tables.IPRangeTable
  891. #
  892. # IP addresses
  893. #
  894. @register_model_view(IPAddress, 'list', path='', detail=False)
  895. class IPAddressListView(generic.ObjectListView):
  896. queryset = IPAddress.objects.all()
  897. filterset = filtersets.IPAddressFilterSet
  898. filterset_form = forms.IPAddressFilterForm
  899. table = tables.IPAddressTable
  900. actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
  901. @register_model_view(IPAddress)
  902. class IPAddressView(generic.ObjectView):
  903. queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
  904. layout = layout.SimpleLayout(
  905. left_panels=[
  906. panels.IPAddressPanel(),
  907. TagsPanel(),
  908. CustomFieldsPanel(),
  909. CommentsPanel(),
  910. ],
  911. right_panels=[
  912. ContextTablePanel('parent_prefixes_table', title=_('Parent prefixes')),
  913. ContextTablePanel('duplicate_ips_table', title=_('Duplicate IPs')),
  914. ObjectsTablePanel(
  915. 'ipam.service',
  916. filters={'ip_address_id': lambda ctx: ctx['object'].pk},
  917. title=_('Application services'),
  918. ),
  919. ],
  920. )
  921. def get_extra_context(self, request, instance):
  922. # Parent prefixes table
  923. parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
  924. vrf=instance.vrf,
  925. prefix__net_contains_or_equals=str(instance.address.ip)
  926. ).prefetch_related(
  927. 'scope', 'role'
  928. )
  929. parent_prefixes_table = tables.PrefixTable(
  930. list(parent_prefixes),
  931. exclude=('vrf', 'utilization'),
  932. orderable=False
  933. )
  934. parent_prefixes_table.configure(request)
  935. # Duplicate IPs table
  936. duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
  937. vrf=instance.vrf,
  938. address=str(instance.address)
  939. ).exclude(
  940. pk=instance.pk
  941. ).prefetch_related(
  942. 'nat_inside'
  943. )
  944. # Exclude anycast IPs if this IP is anycast
  945. if instance.role == IPAddressRoleChoices.ROLE_ANYCAST:
  946. duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
  947. # Limit to a maximum of 10 duplicates displayed here
  948. duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
  949. duplicate_ips_table.configure(request)
  950. context = {
  951. 'parent_prefixes_table': parent_prefixes_table,
  952. }
  953. if duplicate_ips.exists():
  954. context['duplicate_ips_table'] = duplicate_ips_table
  955. return context
  956. @register_model_view(IPAddress, 'add', detail=False)
  957. @register_model_view(IPAddress, 'edit')
  958. class IPAddressEditView(generic.ObjectEditView):
  959. queryset = IPAddress.objects.all()
  960. form = forms.IPAddressForm
  961. template_name = 'ipam/ipaddress_edit.html'
  962. def alter_object(self, obj, request, url_args, url_kwargs):
  963. if 'interface' in request.GET:
  964. try:
  965. obj.assigned_object = Interface.objects.get(pk=request.GET['interface'])
  966. except (ValueError, Interface.DoesNotExist):
  967. pass
  968. elif 'vminterface' in request.GET:
  969. try:
  970. obj.assigned_object = VMInterface.objects.get(pk=request.GET['vminterface'])
  971. except (ValueError, VMInterface.DoesNotExist):
  972. pass
  973. elif 'fhrpgroup' in request.GET:
  974. try:
  975. obj.assigned_object = FHRPGroup.objects.get(pk=request.GET['fhrpgroup'])
  976. except (ValueError, FHRPGroup.DoesNotExist):
  977. pass
  978. return obj
  979. def get_extra_addanother_params(self, request):
  980. if 'interface' in request.GET:
  981. return {'interface': request.GET['interface']}
  982. if 'vminterface' in request.GET:
  983. return {'vminterface': request.GET['vminterface']}
  984. return {}
  985. # TODO: Standardize or remove this view
  986. @register_model_view(IPAddress, 'assign', path='assign', detail=False)
  987. class IPAddressAssignView(generic.ObjectView):
  988. """
  989. Search for IPAddresses to be assigned to an Interface.
  990. """
  991. queryset = IPAddress.objects.all()
  992. def dispatch(self, request, *args, **kwargs):
  993. # Redirect user if an interface has not been provided
  994. if 'interface' not in request.GET and 'vminterface' not in request.GET:
  995. return redirect('ipam:ipaddress_add')
  996. return super().dispatch(request, *args, **kwargs)
  997. def get(self, request):
  998. form = forms.IPAddressAssignForm()
  999. return render(request, 'ipam/ipaddress_assign.html', {
  1000. 'form': form,
  1001. 'return_url': request.GET.get('return_url', ''),
  1002. })
  1003. def post(self, request):
  1004. form = forms.IPAddressAssignForm(request.POST)
  1005. table = None
  1006. if form.is_valid():
  1007. addresses = self.queryset.prefetch_related('vrf', 'tenant')
  1008. # Limit to 100 results
  1009. addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
  1010. table = tables.IPAddressAssignTable(addresses)
  1011. table.configure(request)
  1012. return render(request, 'ipam/ipaddress_assign.html', {
  1013. 'form': form,
  1014. 'table': table,
  1015. 'return_url': request.GET.get('return_url'),
  1016. })
  1017. @register_model_view(IPAddress, 'delete')
  1018. class IPAddressDeleteView(generic.ObjectDeleteView):
  1019. queryset = IPAddress.objects.all()
  1020. @register_model_view(IPAddress, 'bulk_add', path='bulk-add', detail=False)
  1021. class IPAddressBulkCreateView(generic.BulkCreateView):
  1022. queryset = IPAddress.objects.all()
  1023. form = forms.IPNetworkBulkCreateForm
  1024. model_form = forms.IPAddressBulkAddForm
  1025. pattern_target = 'address'
  1026. template_name = 'ipam/ipaddress_bulk_add.html'
  1027. @register_model_view(IPAddress, 'bulk_import', path='import', detail=False)
  1028. class IPAddressBulkImportView(generic.BulkImportView):
  1029. queryset = IPAddress.objects.all()
  1030. model_form = forms.IPAddressImportForm
  1031. @register_model_view(IPAddress, 'bulk_edit', path='edit', detail=False)
  1032. class IPAddressBulkEditView(generic.BulkEditView):
  1033. queryset = IPAddress.objects.prefetch_related('vrf__tenant')
  1034. filterset = filtersets.IPAddressFilterSet
  1035. table = tables.IPAddressTable
  1036. form = forms.IPAddressBulkEditForm
  1037. @register_model_view(IPAddress, 'bulk_delete', path='delete', detail=False)
  1038. class IPAddressBulkDeleteView(generic.BulkDeleteView):
  1039. queryset = IPAddress.objects.prefetch_related('vrf__tenant')
  1040. filterset = filtersets.IPAddressFilterSet
  1041. table = tables.IPAddressTable
  1042. @register_model_view(IPAddress, 'related_ips', path='related-ip-addresses')
  1043. class IPAddressRelatedIPsView(generic.ObjectChildrenView):
  1044. queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
  1045. child_model = IPAddress
  1046. table = tables.IPAddressTable
  1047. filterset = filtersets.IPAddressFilterSet
  1048. filterset_form = forms.IPAddressFilterForm
  1049. tab = ViewTab(
  1050. label=_('Related IPs'),
  1051. badge=lambda x: x.get_related_ips().count(),
  1052. weight=500,
  1053. hide_if_empty=True,
  1054. )
  1055. def get_children(self, request, parent):
  1056. return parent.get_related_ips().restrict(request.user, 'view')
  1057. #
  1058. # VLAN groups
  1059. #
  1060. @register_model_view(VLANGroup, 'list', path='', detail=False)
  1061. class VLANGroupListView(generic.ObjectListView):
  1062. queryset = VLANGroup.objects.annotate_utilization()
  1063. filterset = filtersets.VLANGroupFilterSet
  1064. filterset_form = forms.VLANGroupFilterForm
  1065. table = tables.VLANGroupTable
  1066. @register_model_view(VLANGroup)
  1067. class VLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
  1068. queryset = VLANGroup.objects.annotate_utilization()
  1069. layout = layout.SimpleLayout(
  1070. left_panels=[
  1071. panels.VLANGroupPanel(),
  1072. TagsPanel(),
  1073. ],
  1074. right_panels=[
  1075. RelatedObjectsPanel(),
  1076. CommentsPanel(),
  1077. CustomFieldsPanel(),
  1078. ],
  1079. )
  1080. def get_extra_context(self, request, instance):
  1081. return {
  1082. 'related_models': self.get_related_models(request, instance),
  1083. }
  1084. @register_model_view(VLANGroup, 'add', detail=False)
  1085. @register_model_view(VLANGroup, 'edit')
  1086. class VLANGroupEditView(generic.ObjectEditView):
  1087. queryset = VLANGroup.objects.all()
  1088. form = forms.VLANGroupForm
  1089. @register_model_view(VLANGroup, 'delete')
  1090. class VLANGroupDeleteView(generic.ObjectDeleteView):
  1091. queryset = VLANGroup.objects.all()
  1092. @register_model_view(VLANGroup, 'bulk_import', path='import', detail=False)
  1093. class VLANGroupBulkImportView(generic.BulkImportView):
  1094. queryset = VLANGroup.objects.all()
  1095. model_form = forms.VLANGroupImportForm
  1096. @register_model_view(VLANGroup, 'bulk_edit', path='edit', detail=False)
  1097. class VLANGroupBulkEditView(generic.BulkEditView):
  1098. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  1099. filterset = filtersets.VLANGroupFilterSet
  1100. table = tables.VLANGroupTable
  1101. form = forms.VLANGroupBulkEditForm
  1102. @register_model_view(VLANGroup, 'bulk_rename', path='rename', detail=False)
  1103. class VLANGroupBulkRenameView(generic.BulkRenameView):
  1104. queryset = VLANGroup.objects.all()
  1105. filterset = filtersets.VLANGroupFilterSet
  1106. @register_model_view(VLANGroup, 'bulk_delete', path='delete', detail=False)
  1107. class VLANGroupBulkDeleteView(generic.BulkDeleteView):
  1108. queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
  1109. filterset = filtersets.VLANGroupFilterSet
  1110. table = tables.VLANGroupTable
  1111. @register_model_view(VLANGroup, 'vlans')
  1112. class VLANGroupVLANsView(ChildAvailabilityMixin, generic.ObjectChildrenView):
  1113. queryset = VLANGroup.objects.all()
  1114. child_model = VLAN
  1115. table = tables.VLANTable
  1116. filterset = filtersets.VLANFilterSet
  1117. filterset_form = forms.VLANFilterForm
  1118. tab = ViewTab(
  1119. label=_('VLANs'),
  1120. badge=lambda x: x.get_child_vlans().count(),
  1121. permission='ipam.view_vlan',
  1122. weight=500
  1123. )
  1124. def get_children(self, request, parent):
  1125. return parent.get_child_vlans().restrict(request.user, 'view').prefetch_related(
  1126. Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user)),
  1127. 'tenant', 'site', 'role',
  1128. )
  1129. def prep_table_data(self, request, queryset, parent):
  1130. # Skip synthetic available rows under active filters: filtered-out VLANs would otherwise look available.
  1131. if not get_table_ordering(request, self.table) and not self._has_active_child_filters(request):
  1132. return add_available_vlans(queryset, parent)
  1133. return super().prep_table_data(request, queryset, parent)
  1134. #
  1135. # VLAN Translation Policies
  1136. #
  1137. @register_model_view(VLANTranslationPolicy, 'list', path='', detail=False)
  1138. class VLANTranslationPolicyListView(generic.ObjectListView):
  1139. queryset = VLANTranslationPolicy.objects.annotate(
  1140. rule_count=count_related(VLANTranslationRule, 'policy'),
  1141. )
  1142. filterset = filtersets.VLANTranslationPolicyFilterSet
  1143. filterset_form = forms.VLANTranslationPolicyFilterForm
  1144. table = tables.VLANTranslationPolicyTable
  1145. @register_model_view(VLANTranslationPolicy)
  1146. class VLANTranslationPolicyView(generic.ObjectView):
  1147. queryset = VLANTranslationPolicy.objects.all()
  1148. template_name = 'generic/object.html'
  1149. layout = layout.SimpleLayout(
  1150. left_panels=[
  1151. panels.VLANTranslationPolicyPanel(),
  1152. ],
  1153. right_panels=[
  1154. TagsPanel(),
  1155. CustomFieldsPanel(),
  1156. CommentsPanel(),
  1157. ],
  1158. bottom_panels=[
  1159. ObjectsTablePanel(
  1160. 'ipam.vlantranslationrule',
  1161. filters={'policy_id': lambda ctx: ctx['object'].pk},
  1162. title=_('VLAN translation rules'),
  1163. exclude_columns=['policy'],
  1164. actions=[
  1165. actions.AddObject(
  1166. 'ipam.vlantranslationrule',
  1167. url_params={'policy': lambda ctx: ctx['object'].pk},
  1168. label=_('Add Rule'),
  1169. ),
  1170. ],
  1171. ),
  1172. ],
  1173. )
  1174. @register_model_view(VLANTranslationPolicy, 'add', detail=False)
  1175. @register_model_view(VLANTranslationPolicy, 'edit')
  1176. class VLANTranslationPolicyEditView(generic.ObjectEditView):
  1177. queryset = VLANTranslationPolicy.objects.all()
  1178. form = forms.VLANTranslationPolicyForm
  1179. @register_model_view(VLANTranslationPolicy, 'delete')
  1180. class VLANTranslationPolicyDeleteView(generic.ObjectDeleteView):
  1181. queryset = VLANTranslationPolicy.objects.all()
  1182. @register_model_view(VLANTranslationPolicy, 'bulk_import', path='import', detail=False)
  1183. class VLANTranslationPolicyBulkImportView(generic.BulkImportView):
  1184. queryset = VLANTranslationPolicy.objects.all()
  1185. model_form = forms.VLANTranslationPolicyImportForm
  1186. @register_model_view(VLANTranslationPolicy, 'bulk_edit', path='edit', detail=False)
  1187. class VLANTranslationPolicyBulkEditView(generic.BulkEditView):
  1188. queryset = VLANTranslationPolicy.objects.all()
  1189. filterset = filtersets.VLANTranslationPolicyFilterSet
  1190. table = tables.VLANTranslationPolicyTable
  1191. form = forms.VLANTranslationPolicyBulkEditForm
  1192. @register_model_view(VLANTranslationPolicy, 'bulk_rename', path='rename', detail=False)
  1193. class VLANTranslationPolicyBulkRenameView(generic.BulkRenameView):
  1194. queryset = VLANTranslationPolicy.objects.all()
  1195. filterset = filtersets.VLANTranslationPolicyFilterSet
  1196. @register_model_view(VLANTranslationPolicy, 'bulk_delete', path='delete', detail=False)
  1197. class VLANTranslationPolicyBulkDeleteView(generic.BulkDeleteView):
  1198. queryset = VLANTranslationPolicy.objects.all()
  1199. filterset = filtersets.VLANTranslationPolicyFilterSet
  1200. table = tables.VLANTranslationPolicyTable
  1201. #
  1202. # VLAN Translation Rules
  1203. #
  1204. @register_model_view(VLANTranslationRule, 'list', path='', detail=False)
  1205. class VLANTranslationRuleListView(generic.ObjectListView):
  1206. queryset = VLANTranslationRule.objects.all()
  1207. filterset = filtersets.VLANTranslationRuleFilterSet
  1208. filterset_form = forms.VLANTranslationRuleFilterForm
  1209. table = tables.VLANTranslationRuleTable
  1210. actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
  1211. @register_model_view(VLANTranslationRule)
  1212. class VLANTranslationRuleView(generic.ObjectView):
  1213. queryset = VLANTranslationRule.objects.all()
  1214. template_name = 'generic/object.html'
  1215. layout = layout.SimpleLayout(
  1216. left_panels=[
  1217. panels.VLANTranslationRulePanel(),
  1218. ],
  1219. right_panels=[
  1220. TagsPanel(),
  1221. CustomFieldsPanel(),
  1222. ],
  1223. )
  1224. @register_model_view(VLANTranslationRule, 'add', detail=False)
  1225. @register_model_view(VLANTranslationRule, 'edit')
  1226. class VLANTranslationRuleEditView(generic.ObjectEditView):
  1227. queryset = VLANTranslationRule.objects.all()
  1228. form = forms.VLANTranslationRuleForm
  1229. @register_model_view(VLANTranslationRule, 'delete')
  1230. class VLANTranslationRuleDeleteView(generic.ObjectDeleteView):
  1231. queryset = VLANTranslationRule.objects.all()
  1232. @register_model_view(VLANTranslationRule, 'bulk_import', path='import', detail=False)
  1233. class VLANTranslationRuleBulkImportView(generic.BulkImportView):
  1234. queryset = VLANTranslationRule.objects.all()
  1235. model_form = forms.VLANTranslationRuleImportForm
  1236. @register_model_view(VLANTranslationRule, 'bulk_edit', path='edit', detail=False)
  1237. class VLANTranslationRuleBulkEditView(generic.BulkEditView):
  1238. queryset = VLANTranslationRule.objects.all()
  1239. filterset = filtersets.VLANTranslationRuleFilterSet
  1240. table = tables.VLANTranslationRuleTable
  1241. form = forms.VLANTranslationRuleBulkEditForm
  1242. @register_model_view(VLANTranslationRule, 'bulk_delete', path='delete', detail=False)
  1243. class VLANTranslationRuleBulkDeleteView(generic.BulkDeleteView):
  1244. queryset = VLANTranslationRule.objects.all()
  1245. filterset = filtersets.VLANTranslationRuleFilterSet
  1246. table = tables.VLANTranslationRuleTable
  1247. #
  1248. # FHRP groups
  1249. #
  1250. @register_model_view(FHRPGroup, 'list', path='', detail=False)
  1251. class FHRPGroupListView(generic.ObjectListView):
  1252. queryset = FHRPGroup.objects.annotate(
  1253. member_count=count_related(FHRPGroupAssignment, 'group')
  1254. )
  1255. filterset = filtersets.FHRPGroupFilterSet
  1256. filterset_form = forms.FHRPGroupFilterForm
  1257. table = tables.FHRPGroupTable
  1258. @register_model_view(FHRPGroup)
  1259. class FHRPGroupView(GetRelatedModelsMixin, generic.ObjectView):
  1260. queryset = FHRPGroup.objects.annotate(
  1261. member_count=count_related(FHRPGroupAssignment, 'group')
  1262. )
  1263. layout = layout.SimpleLayout(
  1264. left_panels=[
  1265. panels.FHRPGroupPanel(),
  1266. TagsPanel(),
  1267. CommentsPanel(),
  1268. ],
  1269. right_panels=[
  1270. panels.FHRPGroupAuthPanel(),
  1271. RelatedObjectsPanel(),
  1272. CustomFieldsPanel(),
  1273. ],
  1274. bottom_panels=[
  1275. ObjectsTablePanel(
  1276. 'ipam.ipaddress',
  1277. filters={'fhrpgroup_id': lambda ctx: ctx['object'].pk},
  1278. title=_('Virtual IP addresses'),
  1279. actions=[
  1280. actions.AddObject(
  1281. 'ipam.ipaddress',
  1282. url_params={'fhrpgroup': lambda ctx: ctx['object'].pk},
  1283. label=_('Add IP Address'),
  1284. ),
  1285. ],
  1286. ),
  1287. ContextTablePanel('members_table', title=_('Members')),
  1288. ],
  1289. )
  1290. def get_extra_context(self, request, instance):
  1291. # Get assigned interfaces
  1292. members_table = tables.FHRPGroupAssignmentTable(
  1293. data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance),
  1294. orderable=False
  1295. )
  1296. members_table.configure(request)
  1297. members_table.columns.hide('group')
  1298. return {
  1299. 'related_models': self.get_related_models(
  1300. request, instance,
  1301. extra=(
  1302. (
  1303. Service.objects.restrict(request.user, 'view').filter(
  1304. parent_object_type=ContentType.objects.get_for_model(FHRPGroup),
  1305. parent_object_id=instance.id,
  1306. ),
  1307. 'fhrpgroup_id'
  1308. ),
  1309. ),
  1310. ),
  1311. 'members_table': members_table,
  1312. }
  1313. @register_model_view(FHRPGroup, 'add', detail=False)
  1314. @register_model_view(FHRPGroup, 'edit')
  1315. class FHRPGroupEditView(generic.ObjectEditView):
  1316. queryset = FHRPGroup.objects.all()
  1317. form = forms.FHRPGroupForm
  1318. def get_return_url(self, request, obj=None):
  1319. return_url = super().get_return_url(request, obj)
  1320. # If we're redirecting the user to the FHRPGroupAssignment creation form,
  1321. # initialize the group field with the FHRPGroup we just saved.
  1322. if return_url.startswith(reverse('ipam:fhrpgroupassignment_add')):
  1323. return_url += f'&group={obj.pk}'
  1324. return return_url
  1325. def alter_object(self, obj, request, url_args, url_kwargs):
  1326. # Workaround to solve #10719. Capture the current user on the FHRPGroup instance so that
  1327. # we can evaluate permissions during the creation of a new IPAddress within the form.
  1328. obj._user = request.user
  1329. return obj
  1330. @register_model_view(FHRPGroup, 'delete')
  1331. class FHRPGroupDeleteView(generic.ObjectDeleteView):
  1332. queryset = FHRPGroup.objects.all()
  1333. @register_model_view(FHRPGroup, 'bulk_import', path='import', detail=False)
  1334. class FHRPGroupBulkImportView(generic.BulkImportView):
  1335. queryset = FHRPGroup.objects.all()
  1336. model_form = forms.FHRPGroupImportForm
  1337. @register_model_view(FHRPGroup, 'bulk_edit', path='edit', detail=False)
  1338. class FHRPGroupBulkEditView(generic.BulkEditView):
  1339. queryset = FHRPGroup.objects.all()
  1340. filterset = filtersets.FHRPGroupFilterSet
  1341. table = tables.FHRPGroupTable
  1342. form = forms.FHRPGroupBulkEditForm
  1343. @register_model_view(FHRPGroup, 'bulk_rename', path='rename', detail=False)
  1344. class FHRPGroupBulkRenameView(generic.BulkRenameView):
  1345. queryset = FHRPGroup.objects.all()
  1346. filterset = filtersets.FHRPGroupFilterSet
  1347. @register_model_view(FHRPGroup, 'bulk_delete', path='delete', detail=False)
  1348. class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
  1349. queryset = FHRPGroup.objects.all()
  1350. filterset = filtersets.FHRPGroupFilterSet
  1351. table = tables.FHRPGroupTable
  1352. #
  1353. # FHRP group assignments
  1354. #
  1355. @register_model_view(FHRPGroupAssignment, 'add', detail=False)
  1356. @register_model_view(FHRPGroupAssignment, 'edit')
  1357. class FHRPGroupAssignmentEditView(generic.ObjectEditView):
  1358. queryset = FHRPGroupAssignment.objects.all()
  1359. form = forms.FHRPGroupAssignmentForm
  1360. def alter_object(self, instance, request, args, kwargs):
  1361. if not instance.pk:
  1362. # Assign the interface based on URL kwargs
  1363. content_type = get_object_or_404(ContentType, pk=request.GET.get('interface_type'))
  1364. instance.interface = get_object_or_404(content_type.model_class(), pk=request.GET.get('interface_id'))
  1365. return instance
  1366. def get_extra_addanother_params(self, request):
  1367. return {
  1368. 'interface_type': request.GET.get('interface_type'),
  1369. 'interface_id': request.GET.get('interface_id'),
  1370. }
  1371. @register_model_view(FHRPGroupAssignment, 'delete')
  1372. class FHRPGroupAssignmentDeleteView(generic.ObjectDeleteView):
  1373. queryset = FHRPGroupAssignment.objects.all()
  1374. #
  1375. # VLANs
  1376. #
  1377. @register_model_view(VLAN, 'list', path='', detail=False)
  1378. class VLANListView(generic.ObjectListView):
  1379. queryset = VLAN.objects.all()
  1380. filterset = filtersets.VLANFilterSet
  1381. filterset_form = forms.VLANFilterForm
  1382. table = tables.VLANTable
  1383. @register_model_view(VLAN)
  1384. class VLANView(generic.ObjectView):
  1385. queryset = VLAN.objects.all()
  1386. layout = layout.SimpleLayout(
  1387. left_panels=[
  1388. panels.VLANPanel(),
  1389. ],
  1390. right_panels=[
  1391. CustomFieldsPanel(),
  1392. TagsPanel(),
  1393. CommentsPanel(),
  1394. ],
  1395. bottom_panels=[
  1396. ObjectsTablePanel(
  1397. 'ipam.prefix',
  1398. filters={'vlan_id': lambda ctx: ctx['object'].pk},
  1399. title=_('Prefixes'),
  1400. exclude_columns=['vlan'],
  1401. actions=[
  1402. actions.AddObject(
  1403. 'ipam.prefix',
  1404. url_params={
  1405. 'tenant': lambda ctx: ctx['object'].tenant_id,
  1406. 'scope_type': lambda ctx: (
  1407. ContentType.objects.get_for_model(Site).pk if ctx['object'].site_id else None
  1408. ),
  1409. 'scope': lambda ctx: ctx['object'].site_id,
  1410. 'vlan': lambda ctx: ctx['object'].pk,
  1411. },
  1412. label=_('Add a Prefix'),
  1413. ),
  1414. ],
  1415. ),
  1416. panels.VLANCustomerVLANsPanel(),
  1417. ],
  1418. )
  1419. @register_model_view(VLAN, 'interfaces')
  1420. class VLANInterfacesView(generic.ObjectChildrenView):
  1421. queryset = VLAN.objects.all()
  1422. child_model = Interface
  1423. table = tables.VLANDevicesTable
  1424. filterset = InterfaceFilterSet
  1425. filterset_form = InterfaceFilterForm
  1426. tab = ViewTab(
  1427. label=_('Device Interfaces'),
  1428. badge=lambda x: x.get_interfaces().count(),
  1429. permission='dcim.view_interface',
  1430. weight=500
  1431. )
  1432. def get_children(self, request, parent):
  1433. return parent.get_interfaces().restrict(request.user, 'view')
  1434. @register_model_view(VLAN, 'vminterfaces', path='vm-interfaces')
  1435. class VLANVMInterfacesView(generic.ObjectChildrenView):
  1436. queryset = VLAN.objects.all()
  1437. child_model = VMInterface
  1438. table = tables.VLANVirtualMachinesTable
  1439. filterset = VMInterfaceFilterSet
  1440. filterset_form = VMInterfaceFilterForm
  1441. tab = ViewTab(
  1442. label=_('VM Interfaces'),
  1443. badge=lambda x: x.get_vminterfaces().count(),
  1444. permission='virtualization.view_vminterface',
  1445. weight=510
  1446. )
  1447. def get_children(self, request, parent):
  1448. return parent.get_vminterfaces().restrict(request.user, 'view')
  1449. @register_model_view(VLAN, 'add', detail=False)
  1450. @register_model_view(VLAN, 'edit')
  1451. class VLANEditView(generic.ObjectEditView):
  1452. queryset = VLAN.objects.all()
  1453. form = forms.VLANForm
  1454. template_name = 'ipam/vlan_edit.html'
  1455. @register_model_view(VLAN, 'delete')
  1456. class VLANDeleteView(generic.ObjectDeleteView):
  1457. queryset = VLAN.objects.all()
  1458. @register_model_view(VLAN, 'bulk_add', path='bulk-add', detail=False)
  1459. class VLANBulkCreateView(generic.BulkCreateView):
  1460. queryset = VLAN.objects.all()
  1461. form = forms.VLANIDBulkCreateForm
  1462. model_form = forms.VLANBulkAddForm
  1463. pattern_target = 'vid'
  1464. pattern_template_fields = ('name',)
  1465. template_name = 'ipam/vlan_bulk_add.html'
  1466. @register_model_view(VLAN, 'bulk_import', path='import', detail=False)
  1467. class VLANBulkImportView(generic.BulkImportView):
  1468. queryset = VLAN.objects.all()
  1469. model_form = forms.VLANImportForm
  1470. @register_model_view(VLAN, 'bulk_edit', path='edit', detail=False)
  1471. class VLANBulkEditView(generic.BulkEditView):
  1472. queryset = VLAN.objects.all()
  1473. filterset = filtersets.VLANFilterSet
  1474. table = tables.VLANTable
  1475. form = forms.VLANBulkEditForm
  1476. @register_model_view(VLAN, 'bulk_rename', path='rename', detail=False)
  1477. class VLANBulkRenameView(generic.BulkRenameView):
  1478. queryset = VLAN.objects.all()
  1479. filterset = filtersets.VLANFilterSet
  1480. @register_model_view(VLAN, 'bulk_delete', path='delete', detail=False)
  1481. class VLANBulkDeleteView(generic.BulkDeleteView):
  1482. queryset = VLAN.objects.all()
  1483. filterset = filtersets.VLANFilterSet
  1484. table = tables.VLANTable
  1485. #
  1486. # Service templates
  1487. #
  1488. @register_model_view(ServiceTemplate, 'list', path='', detail=False)
  1489. class ServiceTemplateListView(generic.ObjectListView):
  1490. queryset = ServiceTemplate.objects.all()
  1491. filterset = filtersets.ServiceTemplateFilterSet
  1492. filterset_form = forms.ServiceTemplateFilterForm
  1493. table = tables.ServiceTemplateTable
  1494. @register_model_view(ServiceTemplate)
  1495. class ServiceTemplateView(generic.ObjectView):
  1496. queryset = ServiceTemplate.objects.all()
  1497. template_name = 'generic/object.html'
  1498. layout = layout.SimpleLayout(
  1499. left_panels=[
  1500. panels.ServiceTemplatePanel(),
  1501. ],
  1502. right_panels=[
  1503. CustomFieldsPanel(),
  1504. TagsPanel(),
  1505. CommentsPanel(),
  1506. ],
  1507. )
  1508. @register_model_view(ServiceTemplate, 'add', detail=False)
  1509. @register_model_view(ServiceTemplate, 'edit')
  1510. class ServiceTemplateEditView(generic.ObjectEditView):
  1511. queryset = ServiceTemplate.objects.all()
  1512. form = forms.ServiceTemplateForm
  1513. @register_model_view(ServiceTemplate, 'delete')
  1514. class ServiceTemplateDeleteView(generic.ObjectDeleteView):
  1515. queryset = ServiceTemplate.objects.all()
  1516. @register_model_view(ServiceTemplate, 'bulk_import', path='import', detail=False)
  1517. class ServiceTemplateBulkImportView(generic.BulkImportView):
  1518. queryset = ServiceTemplate.objects.all()
  1519. model_form = forms.ServiceTemplateImportForm
  1520. @register_model_view(ServiceTemplate, 'bulk_edit', path='edit', detail=False)
  1521. class ServiceTemplateBulkEditView(generic.BulkEditView):
  1522. queryset = ServiceTemplate.objects.all()
  1523. filterset = filtersets.ServiceTemplateFilterSet
  1524. table = tables.ServiceTemplateTable
  1525. form = forms.ServiceTemplateBulkEditForm
  1526. @register_model_view(ServiceTemplate, 'bulk_rename', path='rename', detail=False)
  1527. class ServiceTemplateBulkRenameView(generic.BulkRenameView):
  1528. queryset = ServiceTemplate.objects.all()
  1529. filterset = filtersets.ServiceTemplateFilterSet
  1530. @register_model_view(ServiceTemplate, 'bulk_delete', path='delete', detail=False)
  1531. class ServiceTemplateBulkDeleteView(generic.BulkDeleteView):
  1532. queryset = ServiceTemplate.objects.all()
  1533. filterset = filtersets.ServiceTemplateFilterSet
  1534. table = tables.ServiceTemplateTable
  1535. #
  1536. # Services
  1537. #
  1538. @register_model_view(Service, 'list', path='', detail=False)
  1539. class ServiceListView(generic.ObjectListView):
  1540. queryset = Service.objects.prefetch_related('parent')
  1541. filterset = filtersets.ServiceFilterSet
  1542. filterset_form = forms.ServiceFilterForm
  1543. table = tables.ServiceTable
  1544. @register_model_view(Service)
  1545. class ServiceView(generic.ObjectView):
  1546. queryset = Service.objects.all()
  1547. layout = layout.SimpleLayout(
  1548. left_panels=[
  1549. panels.ServicePanel(),
  1550. ],
  1551. right_panels=[
  1552. CustomFieldsPanel(),
  1553. TagsPanel(),
  1554. CommentsPanel(),
  1555. ],
  1556. )
  1557. def get_extra_context(self, request, instance):
  1558. context = {}
  1559. match instance.parent:
  1560. case Device():
  1561. context['breadcrumb_queryparam'] = 'device_id'
  1562. case VirtualMachine():
  1563. context['breadcrumb_queryparam'] = 'virtual_machine_id'
  1564. case FHRPGroup():
  1565. context['breadcrumb_queryparam'] = 'fhrpgroup_id'
  1566. return context
  1567. @register_model_view(Service, 'add', detail=False)
  1568. class ServiceCreateView(generic.ObjectEditView):
  1569. queryset = Service.objects.all()
  1570. form = forms.ServiceCreateForm
  1571. @register_model_view(Service, 'edit')
  1572. class ServiceEditView(generic.ObjectEditView):
  1573. queryset = Service.objects.all()
  1574. form = forms.ServiceForm
  1575. @register_model_view(Service, 'delete')
  1576. class ServiceDeleteView(generic.ObjectDeleteView):
  1577. queryset = Service.objects.all()
  1578. @register_model_view(Service, 'bulk_import', path='import', detail=False)
  1579. class ServiceBulkImportView(generic.BulkImportView):
  1580. queryset = Service.objects.all()
  1581. model_form = forms.ServiceImportForm
  1582. @register_model_view(Service, 'bulk_edit', path='edit', detail=False)
  1583. class ServiceBulkEditView(generic.BulkEditView):
  1584. queryset = Service.objects.prefetch_related('parent')
  1585. filterset = filtersets.ServiceFilterSet
  1586. table = tables.ServiceTable
  1587. form = forms.ServiceBulkEditForm
  1588. @register_model_view(Service, 'bulk_rename', path='rename', detail=False)
  1589. class ServiceBulkRenameView(generic.BulkRenameView):
  1590. queryset = Service.objects.all()
  1591. filterset = filtersets.ServiceFilterSet
  1592. @register_model_view(Service, 'bulk_delete', path='delete', detail=False)
  1593. class ServiceBulkDeleteView(generic.BulkDeleteView):
  1594. queryset = Service.objects.prefetch_related('parent')
  1595. filterset = filtersets.ServiceFilterSet
  1596. table = tables.ServiceTable