forms.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.utils.translation import gettext as _
  4. from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
  5. from extras.forms import (
  6. AddRemoveTagsForm, CustomFieldModelBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm,
  7. CustomFieldModelFilterForm,
  8. )
  9. from extras.models import Tag
  10. from tenancy.forms import TenancyFilterForm, TenancyForm
  11. from tenancy.models import Tenant
  12. from utilities.forms import (
  13. add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, ContentTypeChoiceField, CSVChoiceField,
  14. CSVContentTypeField, CSVModelChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
  15. ExpandableIPAddressField, NumericArrayField, SlugField, StaticSelect, StaticSelectMultiple, TagFilterField,
  16. BOOLEAN_WITH_BLANK_CHOICES,
  17. )
  18. from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
  19. from .choices import *
  20. from .constants import *
  21. from .models import *
  22. PREFIX_MASK_LENGTH_CHOICES = add_blank_choice([
  23. (i, i) for i in range(PREFIX_LENGTH_MIN, PREFIX_LENGTH_MAX + 1)
  24. ])
  25. IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
  26. (i, i) for i in range(IPADDRESS_MASK_LENGTH_MIN, IPADDRESS_MASK_LENGTH_MAX + 1)
  27. ])
  28. #
  29. # VRFs
  30. #
  31. class VRFForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  32. import_targets = DynamicModelMultipleChoiceField(
  33. queryset=RouteTarget.objects.all(),
  34. required=False
  35. )
  36. export_targets = DynamicModelMultipleChoiceField(
  37. queryset=RouteTarget.objects.all(),
  38. required=False
  39. )
  40. tags = DynamicModelMultipleChoiceField(
  41. queryset=Tag.objects.all(),
  42. required=False
  43. )
  44. class Meta:
  45. model = VRF
  46. fields = [
  47. 'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
  48. 'tags',
  49. ]
  50. fieldsets = (
  51. ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
  52. ('Route Targets', ('import_targets', 'export_targets')),
  53. ('Tenancy', ('tenant_group', 'tenant')),
  54. )
  55. labels = {
  56. 'rd': "RD",
  57. }
  58. help_texts = {
  59. 'rd': "Route distinguisher in any format",
  60. }
  61. class VRFCSVForm(CustomFieldModelCSVForm):
  62. tenant = CSVModelChoiceField(
  63. queryset=Tenant.objects.all(),
  64. required=False,
  65. to_field_name='name',
  66. help_text='Assigned tenant'
  67. )
  68. class Meta:
  69. model = VRF
  70. fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description')
  71. class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  72. pk = forms.ModelMultipleChoiceField(
  73. queryset=VRF.objects.all(),
  74. widget=forms.MultipleHiddenInput()
  75. )
  76. tenant = DynamicModelChoiceField(
  77. queryset=Tenant.objects.all(),
  78. required=False
  79. )
  80. enforce_unique = forms.NullBooleanField(
  81. required=False,
  82. widget=BulkEditNullBooleanSelect(),
  83. label='Enforce unique space'
  84. )
  85. description = forms.CharField(
  86. max_length=100,
  87. required=False
  88. )
  89. class Meta:
  90. nullable_fields = [
  91. 'tenant', 'description',
  92. ]
  93. class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  94. model = VRF
  95. field_groups = [
  96. ['q', 'tag'],
  97. ['import_target_id', 'export_target_id'],
  98. ['tenant_group_id', 'tenant_id'],
  99. ]
  100. q = forms.CharField(
  101. required=False,
  102. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  103. label=_('Search')
  104. )
  105. import_target_id = DynamicModelMultipleChoiceField(
  106. queryset=RouteTarget.objects.all(),
  107. required=False,
  108. label=_('Import targets'),
  109. fetch_trigger='open'
  110. )
  111. export_target_id = DynamicModelMultipleChoiceField(
  112. queryset=RouteTarget.objects.all(),
  113. required=False,
  114. label=_('Export targets'),
  115. fetch_trigger='open'
  116. )
  117. tag = TagFilterField(model)
  118. #
  119. # Route targets
  120. #
  121. class RouteTargetForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  122. tags = DynamicModelMultipleChoiceField(
  123. queryset=Tag.objects.all(),
  124. required=False
  125. )
  126. class Meta:
  127. model = RouteTarget
  128. fields = [
  129. 'name', 'description', 'tenant_group', 'tenant', 'tags',
  130. ]
  131. fieldsets = (
  132. ('Route Target', ('name', 'description', 'tags')),
  133. ('Tenancy', ('tenant_group', 'tenant')),
  134. )
  135. class RouteTargetCSVForm(CustomFieldModelCSVForm):
  136. tenant = CSVModelChoiceField(
  137. queryset=Tenant.objects.all(),
  138. required=False,
  139. to_field_name='name',
  140. help_text='Assigned tenant'
  141. )
  142. class Meta:
  143. model = RouteTarget
  144. fields = ('name', 'description', 'tenant')
  145. class RouteTargetBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  146. pk = forms.ModelMultipleChoiceField(
  147. queryset=RouteTarget.objects.all(),
  148. widget=forms.MultipleHiddenInput()
  149. )
  150. tenant = DynamicModelChoiceField(
  151. queryset=Tenant.objects.all(),
  152. required=False
  153. )
  154. description = forms.CharField(
  155. max_length=200,
  156. required=False
  157. )
  158. class Meta:
  159. nullable_fields = [
  160. 'tenant', 'description',
  161. ]
  162. class RouteTargetFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  163. model = RouteTarget
  164. field_groups = [
  165. ['q', 'tag'],
  166. ['importing_vrf_id', 'exporting_vrf_id'],
  167. ['tenant_group_id', 'tenant_id'],
  168. ]
  169. q = forms.CharField(
  170. required=False,
  171. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  172. label=_('Search')
  173. )
  174. importing_vrf_id = DynamicModelMultipleChoiceField(
  175. queryset=VRF.objects.all(),
  176. required=False,
  177. label=_('Imported by VRF'),
  178. fetch_trigger='open'
  179. )
  180. exporting_vrf_id = DynamicModelMultipleChoiceField(
  181. queryset=VRF.objects.all(),
  182. required=False,
  183. label=_('Exported by VRF'),
  184. fetch_trigger='open'
  185. )
  186. tag = TagFilterField(model)
  187. #
  188. # RIRs
  189. #
  190. class RIRForm(BootstrapMixin, CustomFieldModelForm):
  191. slug = SlugField()
  192. class Meta:
  193. model = RIR
  194. fields = [
  195. 'name', 'slug', 'is_private', 'description',
  196. ]
  197. class RIRCSVForm(CustomFieldModelCSVForm):
  198. slug = SlugField()
  199. class Meta:
  200. model = RIR
  201. fields = ('name', 'slug', 'is_private', 'description')
  202. help_texts = {
  203. 'name': 'RIR name',
  204. }
  205. class RIRBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
  206. pk = forms.ModelMultipleChoiceField(
  207. queryset=RIR.objects.all(),
  208. widget=forms.MultipleHiddenInput
  209. )
  210. is_private = forms.NullBooleanField(
  211. required=False,
  212. widget=BulkEditNullBooleanSelect
  213. )
  214. description = forms.CharField(
  215. max_length=200,
  216. required=False
  217. )
  218. class Meta:
  219. nullable_fields = ['is_private', 'description']
  220. class RIRFilterForm(BootstrapMixin, forms.Form):
  221. is_private = forms.NullBooleanField(
  222. required=False,
  223. label=_('Private'),
  224. widget=StaticSelect(
  225. choices=BOOLEAN_WITH_BLANK_CHOICES
  226. )
  227. )
  228. #
  229. # Aggregates
  230. #
  231. class AggregateForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  232. rir = DynamicModelChoiceField(
  233. queryset=RIR.objects.all(),
  234. label='RIR'
  235. )
  236. tags = DynamicModelMultipleChoiceField(
  237. queryset=Tag.objects.all(),
  238. required=False
  239. )
  240. class Meta:
  241. model = Aggregate
  242. fields = [
  243. 'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
  244. ]
  245. fieldsets = (
  246. ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
  247. ('Tenancy', ('tenant_group', 'tenant')),
  248. )
  249. help_texts = {
  250. 'prefix': "IPv4 or IPv6 network",
  251. 'rir': "Regional Internet Registry responsible for this prefix",
  252. }
  253. widgets = {
  254. 'date_added': DatePicker(),
  255. }
  256. class AggregateCSVForm(CustomFieldModelCSVForm):
  257. rir = CSVModelChoiceField(
  258. queryset=RIR.objects.all(),
  259. to_field_name='name',
  260. help_text='Assigned RIR'
  261. )
  262. tenant = CSVModelChoiceField(
  263. queryset=Tenant.objects.all(),
  264. required=False,
  265. to_field_name='name',
  266. help_text='Assigned tenant'
  267. )
  268. class Meta:
  269. model = Aggregate
  270. fields = ('prefix', 'rir', 'tenant', 'date_added', 'description')
  271. class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  272. pk = forms.ModelMultipleChoiceField(
  273. queryset=Aggregate.objects.all(),
  274. widget=forms.MultipleHiddenInput()
  275. )
  276. rir = DynamicModelChoiceField(
  277. queryset=RIR.objects.all(),
  278. required=False,
  279. label='RIR'
  280. )
  281. tenant = DynamicModelChoiceField(
  282. queryset=Tenant.objects.all(),
  283. required=False
  284. )
  285. date_added = forms.DateField(
  286. required=False
  287. )
  288. description = forms.CharField(
  289. max_length=100,
  290. required=False
  291. )
  292. class Meta:
  293. nullable_fields = [
  294. 'date_added', 'description',
  295. ]
  296. widgets = {
  297. 'date_added': DatePicker(),
  298. }
  299. class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  300. model = Aggregate
  301. field_groups = [
  302. ['q', 'tag'],
  303. ['family', 'rir_id'],
  304. ['tenant_group_id', 'tenant_id']
  305. ]
  306. q = forms.CharField(
  307. required=False,
  308. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  309. label=_('Search')
  310. )
  311. family = forms.ChoiceField(
  312. required=False,
  313. choices=add_blank_choice(IPAddressFamilyChoices),
  314. label=_('Address family'),
  315. widget=StaticSelect()
  316. )
  317. rir_id = DynamicModelMultipleChoiceField(
  318. queryset=RIR.objects.all(),
  319. required=False,
  320. label=_('RIR'),
  321. fetch_trigger='open'
  322. )
  323. tag = TagFilterField(model)
  324. #
  325. # Roles
  326. #
  327. class RoleForm(BootstrapMixin, CustomFieldModelForm):
  328. slug = SlugField()
  329. class Meta:
  330. model = Role
  331. fields = [
  332. 'name', 'slug', 'weight', 'description',
  333. ]
  334. class RoleCSVForm(CustomFieldModelCSVForm):
  335. slug = SlugField()
  336. class Meta:
  337. model = Role
  338. fields = ('name', 'slug', 'weight', 'description')
  339. class RoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
  340. pk = forms.ModelMultipleChoiceField(
  341. queryset=Role.objects.all(),
  342. widget=forms.MultipleHiddenInput
  343. )
  344. weight = forms.IntegerField(
  345. required=False
  346. )
  347. description = forms.CharField(
  348. max_length=200,
  349. required=False
  350. )
  351. class Meta:
  352. nullable_fields = ['description']
  353. #
  354. # Prefixes
  355. #
  356. class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  357. vrf = DynamicModelChoiceField(
  358. queryset=VRF.objects.all(),
  359. required=False,
  360. label='VRF'
  361. )
  362. region = DynamicModelChoiceField(
  363. queryset=Region.objects.all(),
  364. required=False,
  365. initial_params={
  366. 'sites': '$site'
  367. }
  368. )
  369. site_group = DynamicModelChoiceField(
  370. queryset=SiteGroup.objects.all(),
  371. required=False,
  372. initial_params={
  373. 'sites': '$site'
  374. }
  375. )
  376. site = DynamicModelChoiceField(
  377. queryset=Site.objects.all(),
  378. required=False,
  379. null_option='None',
  380. query_params={
  381. 'region_id': '$region',
  382. 'group_id': '$site_group',
  383. }
  384. )
  385. vlan_group = DynamicModelChoiceField(
  386. queryset=VLANGroup.objects.all(),
  387. required=False,
  388. label='VLAN group',
  389. null_option='None',
  390. query_params={
  391. 'site_id': '$site'
  392. },
  393. initial_params={
  394. 'vlans': '$vlan'
  395. }
  396. )
  397. vlan = DynamicModelChoiceField(
  398. queryset=VLAN.objects.all(),
  399. required=False,
  400. label='VLAN',
  401. query_params={
  402. 'site_id': '$site',
  403. 'group_id': '$vlan_group',
  404. }
  405. )
  406. role = DynamicModelChoiceField(
  407. queryset=Role.objects.all(),
  408. required=False
  409. )
  410. tags = DynamicModelMultipleChoiceField(
  411. queryset=Tag.objects.all(),
  412. required=False
  413. )
  414. class Meta:
  415. model = Prefix
  416. fields = [
  417. 'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
  418. 'tenant_group', 'tenant', 'tags',
  419. ]
  420. fieldsets = (
  421. ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
  422. ('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
  423. ('Tenancy', ('tenant_group', 'tenant')),
  424. )
  425. widgets = {
  426. 'status': StaticSelect(),
  427. }
  428. class PrefixCSVForm(CustomFieldModelCSVForm):
  429. vrf = CSVModelChoiceField(
  430. queryset=VRF.objects.all(),
  431. to_field_name='name',
  432. required=False,
  433. help_text='Assigned VRF'
  434. )
  435. tenant = CSVModelChoiceField(
  436. queryset=Tenant.objects.all(),
  437. required=False,
  438. to_field_name='name',
  439. help_text='Assigned tenant'
  440. )
  441. site = CSVModelChoiceField(
  442. queryset=Site.objects.all(),
  443. required=False,
  444. to_field_name='name',
  445. help_text='Assigned site'
  446. )
  447. vlan_group = CSVModelChoiceField(
  448. queryset=VLANGroup.objects.all(),
  449. required=False,
  450. to_field_name='name',
  451. help_text="VLAN's group (if any)"
  452. )
  453. vlan = CSVModelChoiceField(
  454. queryset=VLAN.objects.all(),
  455. required=False,
  456. to_field_name='vid',
  457. help_text="Assigned VLAN"
  458. )
  459. status = CSVChoiceField(
  460. choices=PrefixStatusChoices,
  461. help_text='Operational status'
  462. )
  463. role = CSVModelChoiceField(
  464. queryset=Role.objects.all(),
  465. required=False,
  466. to_field_name='name',
  467. help_text='Functional role'
  468. )
  469. class Meta:
  470. model = Prefix
  471. fields = (
  472. 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
  473. 'description',
  474. )
  475. def __init__(self, data=None, *args, **kwargs):
  476. super().__init__(data, *args, **kwargs)
  477. if data:
  478. # Limit VLAN queryset by assigned site and/or group (if specified)
  479. params = {}
  480. if data.get('site'):
  481. params[f"site__{self.fields['site'].to_field_name}"] = data.get('site')
  482. if data.get('vlan_group'):
  483. params[f"group__{self.fields['vlan_group'].to_field_name}"] = data.get('vlan_group')
  484. if params:
  485. self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params)
  486. class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  487. pk = forms.ModelMultipleChoiceField(
  488. queryset=Prefix.objects.all(),
  489. widget=forms.MultipleHiddenInput()
  490. )
  491. region = DynamicModelChoiceField(
  492. queryset=Region.objects.all(),
  493. required=False
  494. )
  495. site_group = DynamicModelChoiceField(
  496. queryset=SiteGroup.objects.all(),
  497. required=False
  498. )
  499. site = DynamicModelChoiceField(
  500. queryset=Site.objects.all(),
  501. required=False,
  502. query_params={
  503. 'region_id': '$region',
  504. 'group_id': '$site_group',
  505. }
  506. )
  507. vrf = DynamicModelChoiceField(
  508. queryset=VRF.objects.all(),
  509. required=False,
  510. label='VRF'
  511. )
  512. prefix_length = forms.IntegerField(
  513. min_value=PREFIX_LENGTH_MIN,
  514. max_value=PREFIX_LENGTH_MAX,
  515. required=False
  516. )
  517. tenant = DynamicModelChoiceField(
  518. queryset=Tenant.objects.all(),
  519. required=False
  520. )
  521. status = forms.ChoiceField(
  522. choices=add_blank_choice(PrefixStatusChoices),
  523. required=False,
  524. widget=StaticSelect()
  525. )
  526. role = DynamicModelChoiceField(
  527. queryset=Role.objects.all(),
  528. required=False
  529. )
  530. is_pool = forms.NullBooleanField(
  531. required=False,
  532. widget=BulkEditNullBooleanSelect(),
  533. label='Is a pool'
  534. )
  535. mark_utilized = forms.NullBooleanField(
  536. required=False,
  537. widget=BulkEditNullBooleanSelect(),
  538. label='Treat as 100% utilized'
  539. )
  540. description = forms.CharField(
  541. max_length=100,
  542. required=False
  543. )
  544. class Meta:
  545. nullable_fields = [
  546. 'site', 'vrf', 'tenant', 'role', 'description',
  547. ]
  548. class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  549. model = Prefix
  550. field_groups = [
  551. ['q', 'tag'],
  552. ['within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized'],
  553. ['vrf_id', 'present_in_vrf_id'],
  554. ['region_id', 'site_group_id', 'site_id'],
  555. ['tenant_group_id', 'tenant_id']
  556. ]
  557. q = forms.CharField(
  558. required=False,
  559. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  560. label=_('Search')
  561. )
  562. mask_length__lte = forms.IntegerField(
  563. widget=forms.HiddenInput()
  564. )
  565. within_include = forms.CharField(
  566. required=False,
  567. widget=forms.TextInput(
  568. attrs={
  569. 'placeholder': 'Prefix',
  570. }
  571. ),
  572. label=_('Search within')
  573. )
  574. family = forms.ChoiceField(
  575. required=False,
  576. choices=add_blank_choice(IPAddressFamilyChoices),
  577. label=_('Address family'),
  578. widget=StaticSelect()
  579. )
  580. mask_length = forms.MultipleChoiceField(
  581. required=False,
  582. choices=PREFIX_MASK_LENGTH_CHOICES,
  583. label=_('Mask length'),
  584. widget=StaticSelectMultiple()
  585. )
  586. vrf_id = DynamicModelMultipleChoiceField(
  587. queryset=VRF.objects.all(),
  588. required=False,
  589. label=_('Assigned VRF'),
  590. null_option='Global',
  591. fetch_trigger='open'
  592. )
  593. present_in_vrf_id = DynamicModelChoiceField(
  594. queryset=VRF.objects.all(),
  595. required=False,
  596. label=_('Present in VRF'),
  597. fetch_trigger='open'
  598. )
  599. status = forms.MultipleChoiceField(
  600. choices=PrefixStatusChoices,
  601. required=False,
  602. widget=StaticSelectMultiple()
  603. )
  604. region_id = DynamicModelMultipleChoiceField(
  605. queryset=Region.objects.all(),
  606. required=False,
  607. label=_('Region'),
  608. fetch_trigger='open'
  609. )
  610. site_group_id = DynamicModelMultipleChoiceField(
  611. queryset=SiteGroup.objects.all(),
  612. required=False,
  613. label=_('Site group'),
  614. fetch_trigger='open'
  615. )
  616. site_id = DynamicModelMultipleChoiceField(
  617. queryset=Site.objects.all(),
  618. required=False,
  619. null_option='None',
  620. query_params={
  621. 'region_id': '$region_id'
  622. },
  623. label=_('Site'),
  624. fetch_trigger='open'
  625. )
  626. role_id = DynamicModelMultipleChoiceField(
  627. queryset=Role.objects.all(),
  628. required=False,
  629. null_option='None',
  630. label=_('Role'),
  631. fetch_trigger='open'
  632. )
  633. is_pool = forms.NullBooleanField(
  634. required=False,
  635. label=_('Is a pool'),
  636. widget=StaticSelect(
  637. choices=BOOLEAN_WITH_BLANK_CHOICES
  638. )
  639. )
  640. mark_utilized = forms.NullBooleanField(
  641. required=False,
  642. label=_('Marked as 100% utilized'),
  643. widget=StaticSelect(
  644. choices=BOOLEAN_WITH_BLANK_CHOICES
  645. )
  646. )
  647. tag = TagFilterField(model)
  648. #
  649. # IP ranges
  650. #
  651. class IPRangeForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  652. vrf = DynamicModelChoiceField(
  653. queryset=VRF.objects.all(),
  654. required=False,
  655. label='VRF'
  656. )
  657. role = DynamicModelChoiceField(
  658. queryset=Role.objects.all(),
  659. required=False
  660. )
  661. tags = DynamicModelMultipleChoiceField(
  662. queryset=Tag.objects.all(),
  663. required=False
  664. )
  665. class Meta:
  666. model = IPRange
  667. fields = [
  668. 'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  669. ]
  670. fieldsets = (
  671. ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
  672. ('Tenancy', ('tenant_group', 'tenant')),
  673. )
  674. widgets = {
  675. 'status': StaticSelect(),
  676. }
  677. class IPRangeCSVForm(CustomFieldModelCSVForm):
  678. vrf = CSVModelChoiceField(
  679. queryset=VRF.objects.all(),
  680. to_field_name='name',
  681. required=False,
  682. help_text='Assigned VRF'
  683. )
  684. tenant = CSVModelChoiceField(
  685. queryset=Tenant.objects.all(),
  686. required=False,
  687. to_field_name='name',
  688. help_text='Assigned tenant'
  689. )
  690. status = CSVChoiceField(
  691. choices=IPRangeStatusChoices,
  692. help_text='Operational status'
  693. )
  694. role = CSVModelChoiceField(
  695. queryset=Role.objects.all(),
  696. required=False,
  697. to_field_name='name',
  698. help_text='Functional role'
  699. )
  700. class Meta:
  701. model = IPRange
  702. fields = (
  703. 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description',
  704. )
  705. class IPRangeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  706. pk = forms.ModelMultipleChoiceField(
  707. queryset=IPRange.objects.all(),
  708. widget=forms.MultipleHiddenInput()
  709. )
  710. vrf = DynamicModelChoiceField(
  711. queryset=VRF.objects.all(),
  712. required=False,
  713. label='VRF'
  714. )
  715. tenant = DynamicModelChoiceField(
  716. queryset=Tenant.objects.all(),
  717. required=False
  718. )
  719. status = forms.ChoiceField(
  720. choices=add_blank_choice(IPRangeStatusChoices),
  721. required=False,
  722. widget=StaticSelect()
  723. )
  724. role = DynamicModelChoiceField(
  725. queryset=Role.objects.all(),
  726. required=False
  727. )
  728. description = forms.CharField(
  729. max_length=100,
  730. required=False
  731. )
  732. class Meta:
  733. nullable_fields = [
  734. 'vrf', 'tenant', 'role', 'description',
  735. ]
  736. class IPRangeFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  737. model = IPRange
  738. field_groups = [
  739. ['q', 'tag'],
  740. ['family', 'vrf_id', 'status', 'role_id'],
  741. ['tenant_group_id', 'tenant_id'],
  742. ]
  743. q = forms.CharField(
  744. required=False,
  745. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  746. label=_('Search')
  747. )
  748. family = forms.ChoiceField(
  749. required=False,
  750. choices=add_blank_choice(IPAddressFamilyChoices),
  751. label=_('Address family'),
  752. widget=StaticSelect()
  753. )
  754. vrf_id = DynamicModelMultipleChoiceField(
  755. queryset=VRF.objects.all(),
  756. required=False,
  757. label=_('Assigned VRF'),
  758. null_option='Global',
  759. fetch_trigger='open'
  760. )
  761. status = forms.MultipleChoiceField(
  762. choices=PrefixStatusChoices,
  763. required=False,
  764. widget=StaticSelectMultiple()
  765. )
  766. role_id = DynamicModelMultipleChoiceField(
  767. queryset=Role.objects.all(),
  768. required=False,
  769. null_option='None',
  770. label=_('Role'),
  771. fetch_trigger='open'
  772. )
  773. tag = TagFilterField(model)
  774. #
  775. # IP addresses
  776. #
  777. class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  778. device = DynamicModelChoiceField(
  779. queryset=Device.objects.all(),
  780. required=False,
  781. initial_params={
  782. 'interfaces': '$interface'
  783. }
  784. )
  785. interface = DynamicModelChoiceField(
  786. queryset=Interface.objects.all(),
  787. required=False,
  788. query_params={
  789. 'device_id': '$device'
  790. }
  791. )
  792. virtual_machine = DynamicModelChoiceField(
  793. queryset=VirtualMachine.objects.all(),
  794. required=False,
  795. initial_params={
  796. 'interfaces': '$vminterface'
  797. }
  798. )
  799. vminterface = DynamicModelChoiceField(
  800. queryset=VMInterface.objects.all(),
  801. required=False,
  802. label='Interface',
  803. query_params={
  804. 'virtual_machine_id': '$virtual_machine'
  805. }
  806. )
  807. vrf = DynamicModelChoiceField(
  808. queryset=VRF.objects.all(),
  809. required=False,
  810. label='VRF'
  811. )
  812. nat_region = DynamicModelChoiceField(
  813. queryset=Region.objects.all(),
  814. required=False,
  815. label='Region',
  816. initial_params={
  817. 'sites': '$nat_site'
  818. }
  819. )
  820. nat_site_group = DynamicModelChoiceField(
  821. queryset=SiteGroup.objects.all(),
  822. required=False,
  823. label='Site group',
  824. initial_params={
  825. 'sites': '$nat_site'
  826. }
  827. )
  828. nat_site = DynamicModelChoiceField(
  829. queryset=Site.objects.all(),
  830. required=False,
  831. label='Site',
  832. query_params={
  833. 'region_id': '$nat_region',
  834. 'group_id': '$nat_site_group',
  835. }
  836. )
  837. nat_rack = DynamicModelChoiceField(
  838. queryset=Rack.objects.all(),
  839. required=False,
  840. label='Rack',
  841. null_option='None',
  842. query_params={
  843. 'site_id': '$site'
  844. }
  845. )
  846. nat_device = DynamicModelChoiceField(
  847. queryset=Device.objects.all(),
  848. required=False,
  849. label='Device',
  850. query_params={
  851. 'site_id': '$site',
  852. 'rack_id': '$nat_rack',
  853. }
  854. )
  855. nat_cluster = DynamicModelChoiceField(
  856. queryset=Cluster.objects.all(),
  857. required=False,
  858. label='Cluster'
  859. )
  860. nat_virtual_machine = DynamicModelChoiceField(
  861. queryset=VirtualMachine.objects.all(),
  862. required=False,
  863. label='Virtual Machine',
  864. query_params={
  865. 'cluster_id': '$nat_cluster',
  866. }
  867. )
  868. nat_vrf = DynamicModelChoiceField(
  869. queryset=VRF.objects.all(),
  870. required=False,
  871. label='VRF'
  872. )
  873. nat_inside = DynamicModelChoiceField(
  874. queryset=IPAddress.objects.all(),
  875. required=False,
  876. label='IP Address',
  877. query_params={
  878. 'device_id': '$nat_device',
  879. 'virtual_machine_id': '$nat_virtual_machine',
  880. 'vrf_id': '$nat_vrf',
  881. }
  882. )
  883. primary_for_parent = forms.BooleanField(
  884. required=False,
  885. label='Make this the primary IP for the device/VM'
  886. )
  887. tags = DynamicModelMultipleChoiceField(
  888. queryset=Tag.objects.all(),
  889. required=False
  890. )
  891. class Meta:
  892. model = IPAddress
  893. fields = [
  894. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
  895. 'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
  896. 'tags',
  897. ]
  898. widgets = {
  899. 'status': StaticSelect(),
  900. 'role': StaticSelect(),
  901. }
  902. def __init__(self, *args, **kwargs):
  903. # Initialize helper selectors
  904. instance = kwargs.get('instance')
  905. initial = kwargs.get('initial', {}).copy()
  906. if instance:
  907. if type(instance.assigned_object) is Interface:
  908. initial['interface'] = instance.assigned_object
  909. elif type(instance.assigned_object) is VMInterface:
  910. initial['vminterface'] = instance.assigned_object
  911. if instance.nat_inside:
  912. nat_inside_parent = instance.nat_inside.assigned_object
  913. if type(nat_inside_parent) is Interface:
  914. initial['nat_site'] = nat_inside_parent.device.site.pk
  915. if nat_inside_parent.device.rack:
  916. initial['nat_rack'] = nat_inside_parent.device.rack.pk
  917. initial['nat_device'] = nat_inside_parent.device.pk
  918. elif type(nat_inside_parent) is VMInterface:
  919. initial['nat_cluster'] = nat_inside_parent.virtual_machine.cluster.pk
  920. initial['nat_virtual_machine'] = nat_inside_parent.virtual_machine.pk
  921. kwargs['initial'] = initial
  922. super().__init__(*args, **kwargs)
  923. # Initialize primary_for_parent if IP address is already assigned
  924. if self.instance.pk and self.instance.assigned_object:
  925. parent = self.instance.assigned_object.parent_object
  926. if (
  927. self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
  928. self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
  929. ):
  930. self.initial['primary_for_parent'] = True
  931. def clean(self):
  932. super().clean()
  933. # Cannot select both a device interface and a VM interface
  934. if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
  935. raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
  936. self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  937. # Primary IP assignment is only available if an interface has been assigned.
  938. interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  939. if self.cleaned_data.get('primary_for_parent') and not interface:
  940. self.add_error(
  941. 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
  942. )
  943. def save(self, *args, **kwargs):
  944. ipaddress = super().save(*args, **kwargs)
  945. # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
  946. interface = self.instance.assigned_object
  947. if interface:
  948. parent = interface.parent_object
  949. if self.cleaned_data['primary_for_parent']:
  950. if ipaddress.address.version == 4:
  951. parent.primary_ip4 = ipaddress
  952. else:
  953. parent.primary_ip6 = ipaddress
  954. parent.save()
  955. elif ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
  956. parent.primary_ip4 = None
  957. parent.save()
  958. elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
  959. parent.primary_ip6 = None
  960. parent.save()
  961. return ipaddress
  962. class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
  963. pattern = ExpandableIPAddressField(
  964. label='Address pattern'
  965. )
  966. class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  967. vrf = DynamicModelChoiceField(
  968. queryset=VRF.objects.all(),
  969. required=False,
  970. label='VRF'
  971. )
  972. tags = DynamicModelMultipleChoiceField(
  973. queryset=Tag.objects.all(),
  974. required=False
  975. )
  976. class Meta:
  977. model = IPAddress
  978. fields = [
  979. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
  980. ]
  981. widgets = {
  982. 'status': StaticSelect(),
  983. 'role': StaticSelect(),
  984. }
  985. class IPAddressCSVForm(CustomFieldModelCSVForm):
  986. vrf = CSVModelChoiceField(
  987. queryset=VRF.objects.all(),
  988. to_field_name='name',
  989. required=False,
  990. help_text='Assigned VRF'
  991. )
  992. tenant = CSVModelChoiceField(
  993. queryset=Tenant.objects.all(),
  994. to_field_name='name',
  995. required=False,
  996. help_text='Assigned tenant'
  997. )
  998. status = CSVChoiceField(
  999. choices=IPAddressStatusChoices,
  1000. required=False,
  1001. help_text='Operational status'
  1002. )
  1003. role = CSVChoiceField(
  1004. choices=IPAddressRoleChoices,
  1005. required=False,
  1006. help_text='Functional role'
  1007. )
  1008. device = CSVModelChoiceField(
  1009. queryset=Device.objects.all(),
  1010. required=False,
  1011. to_field_name='name',
  1012. help_text='Parent device of assigned interface (if any)'
  1013. )
  1014. virtual_machine = CSVModelChoiceField(
  1015. queryset=VirtualMachine.objects.all(),
  1016. required=False,
  1017. to_field_name='name',
  1018. help_text='Parent VM of assigned interface (if any)'
  1019. )
  1020. interface = CSVModelChoiceField(
  1021. queryset=Interface.objects.none(), # Can also refer to VMInterface
  1022. required=False,
  1023. to_field_name='name',
  1024. help_text='Assigned interface'
  1025. )
  1026. is_primary = forms.BooleanField(
  1027. help_text='Make this the primary IP for the assigned device',
  1028. required=False
  1029. )
  1030. class Meta:
  1031. model = IPAddress
  1032. fields = [
  1033. 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
  1034. 'dns_name', 'description',
  1035. ]
  1036. def __init__(self, data=None, *args, **kwargs):
  1037. super().__init__(data, *args, **kwargs)
  1038. if data:
  1039. # Limit interface queryset by assigned device
  1040. if data.get('device'):
  1041. self.fields['interface'].queryset = Interface.objects.filter(
  1042. **{f"device__{self.fields['device'].to_field_name}": data['device']}
  1043. )
  1044. # Limit interface queryset by assigned device
  1045. elif data.get('virtual_machine'):
  1046. self.fields['interface'].queryset = VMInterface.objects.filter(
  1047. **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
  1048. )
  1049. def clean(self):
  1050. super().clean()
  1051. device = self.cleaned_data.get('device')
  1052. virtual_machine = self.cleaned_data.get('virtual_machine')
  1053. is_primary = self.cleaned_data.get('is_primary')
  1054. # Validate is_primary
  1055. if is_primary and not device and not virtual_machine:
  1056. raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP")
  1057. def save(self, *args, **kwargs):
  1058. # Set interface assignment
  1059. if self.cleaned_data['interface']:
  1060. self.instance.assigned_object = self.cleaned_data['interface']
  1061. ipaddress = super().save(*args, **kwargs)
  1062. # Set as primary for device/VM
  1063. if self.cleaned_data['is_primary']:
  1064. parent = self.cleaned_data['device'] or self.cleaned_data['virtual_machine']
  1065. if self.instance.address.version == 4:
  1066. parent.primary_ip4 = ipaddress
  1067. elif self.instance.address.version == 6:
  1068. parent.primary_ip6 = ipaddress
  1069. parent.save()
  1070. return ipaddress
  1071. class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  1072. pk = forms.ModelMultipleChoiceField(
  1073. queryset=IPAddress.objects.all(),
  1074. widget=forms.MultipleHiddenInput()
  1075. )
  1076. vrf = DynamicModelChoiceField(
  1077. queryset=VRF.objects.all(),
  1078. required=False,
  1079. label='VRF'
  1080. )
  1081. mask_length = forms.IntegerField(
  1082. min_value=IPADDRESS_MASK_LENGTH_MIN,
  1083. max_value=IPADDRESS_MASK_LENGTH_MAX,
  1084. required=False
  1085. )
  1086. tenant = DynamicModelChoiceField(
  1087. queryset=Tenant.objects.all(),
  1088. required=False
  1089. )
  1090. status = forms.ChoiceField(
  1091. choices=add_blank_choice(IPAddressStatusChoices),
  1092. required=False,
  1093. widget=StaticSelect()
  1094. )
  1095. role = forms.ChoiceField(
  1096. choices=add_blank_choice(IPAddressRoleChoices),
  1097. required=False,
  1098. widget=StaticSelect()
  1099. )
  1100. dns_name = forms.CharField(
  1101. max_length=255,
  1102. required=False
  1103. )
  1104. description = forms.CharField(
  1105. max_length=100,
  1106. required=False
  1107. )
  1108. class Meta:
  1109. nullable_fields = [
  1110. 'vrf', 'role', 'tenant', 'dns_name', 'description',
  1111. ]
  1112. class IPAddressAssignForm(BootstrapMixin, forms.Form):
  1113. vrf_id = DynamicModelChoiceField(
  1114. queryset=VRF.objects.all(),
  1115. required=False,
  1116. label='VRF'
  1117. )
  1118. q = forms.CharField(
  1119. required=False,
  1120. label='Search',
  1121. )
  1122. class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  1123. model = IPAddress
  1124. field_order = [
  1125. 'q', 'parent', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'role',
  1126. 'assigned_to_interface', 'tenant_group_id', 'tenant_id',
  1127. ]
  1128. field_groups = [
  1129. ['q', 'tag'],
  1130. ['parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface'],
  1131. ['vrf_id', 'present_in_vrf_id'],
  1132. ['tenant_group_id', 'tenant_id'],
  1133. ]
  1134. q = forms.CharField(
  1135. required=False,
  1136. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  1137. label=_('Search')
  1138. )
  1139. parent = forms.CharField(
  1140. required=False,
  1141. widget=forms.TextInput(
  1142. attrs={
  1143. 'placeholder': 'Prefix',
  1144. }
  1145. ),
  1146. label='Parent Prefix'
  1147. )
  1148. family = forms.ChoiceField(
  1149. required=False,
  1150. choices=add_blank_choice(IPAddressFamilyChoices),
  1151. label=_('Address family'),
  1152. widget=StaticSelect()
  1153. )
  1154. mask_length = forms.ChoiceField(
  1155. required=False,
  1156. choices=IPADDRESS_MASK_LENGTH_CHOICES,
  1157. label=_('Mask length'),
  1158. widget=StaticSelect()
  1159. )
  1160. vrf_id = DynamicModelMultipleChoiceField(
  1161. queryset=VRF.objects.all(),
  1162. required=False,
  1163. label=_('Assigned VRF'),
  1164. null_option='Global',
  1165. fetch_trigger='open'
  1166. )
  1167. present_in_vrf_id = DynamicModelChoiceField(
  1168. queryset=VRF.objects.all(),
  1169. required=False,
  1170. label=_('Present in VRF'),
  1171. fetch_trigger='open'
  1172. )
  1173. status = forms.MultipleChoiceField(
  1174. choices=IPAddressStatusChoices,
  1175. required=False,
  1176. widget=StaticSelectMultiple()
  1177. )
  1178. role = forms.MultipleChoiceField(
  1179. choices=IPAddressRoleChoices,
  1180. required=False,
  1181. widget=StaticSelectMultiple()
  1182. )
  1183. assigned_to_interface = forms.NullBooleanField(
  1184. required=False,
  1185. label=_('Assigned to an interface'),
  1186. widget=StaticSelect(
  1187. choices=BOOLEAN_WITH_BLANK_CHOICES
  1188. )
  1189. )
  1190. tag = TagFilterField(model)
  1191. #
  1192. # VLAN groups
  1193. #
  1194. class VLANGroupForm(BootstrapMixin, CustomFieldModelForm):
  1195. scope_type = ContentTypeChoiceField(
  1196. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  1197. required=False,
  1198. widget=StaticSelect
  1199. )
  1200. region = DynamicModelChoiceField(
  1201. queryset=Region.objects.all(),
  1202. required=False,
  1203. initial_params={
  1204. 'sites': '$site'
  1205. }
  1206. )
  1207. sitegroup = DynamicModelChoiceField(
  1208. queryset=SiteGroup.objects.all(),
  1209. required=False,
  1210. initial_params={
  1211. 'sites': '$site'
  1212. },
  1213. label='Site group'
  1214. )
  1215. site = DynamicModelChoiceField(
  1216. queryset=Site.objects.all(),
  1217. required=False,
  1218. initial_params={
  1219. 'locations': '$location'
  1220. },
  1221. query_params={
  1222. 'region_id': '$region',
  1223. 'group_id': '$sitegroup',
  1224. }
  1225. )
  1226. location = DynamicModelChoiceField(
  1227. queryset=Location.objects.all(),
  1228. required=False,
  1229. initial_params={
  1230. 'racks': '$rack'
  1231. },
  1232. query_params={
  1233. 'site_id': '$site',
  1234. }
  1235. )
  1236. rack = DynamicModelChoiceField(
  1237. queryset=Rack.objects.all(),
  1238. required=False,
  1239. query_params={
  1240. 'site_id': '$site',
  1241. 'location_id': '$location',
  1242. }
  1243. )
  1244. clustergroup = DynamicModelChoiceField(
  1245. queryset=ClusterGroup.objects.all(),
  1246. required=False,
  1247. initial_params={
  1248. 'clusters': '$cluster'
  1249. },
  1250. label='Cluster group'
  1251. )
  1252. cluster = DynamicModelChoiceField(
  1253. queryset=Cluster.objects.all(),
  1254. required=False,
  1255. query_params={
  1256. 'group_id': '$clustergroup',
  1257. }
  1258. )
  1259. slug = SlugField()
  1260. class Meta:
  1261. model = VLANGroup
  1262. fields = [
  1263. 'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
  1264. 'clustergroup', 'cluster',
  1265. ]
  1266. fieldsets = (
  1267. ('VLAN Group', ('name', 'slug', 'description')),
  1268. ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
  1269. )
  1270. widgets = {
  1271. 'scope_type': StaticSelect,
  1272. }
  1273. def __init__(self, *args, **kwargs):
  1274. instance = kwargs.get('instance')
  1275. initial = kwargs.get('initial', {})
  1276. if instance is not None and instance.scope:
  1277. initial[instance.scope_type.model] = instance.scope
  1278. kwargs['initial'] = initial
  1279. super().__init__(*args, **kwargs)
  1280. def clean(self):
  1281. super().clean()
  1282. # Assign scope based on scope_type
  1283. if self.cleaned_data.get('scope_type'):
  1284. scope_field = self.cleaned_data['scope_type'].model
  1285. self.instance.scope = self.cleaned_data.get(scope_field)
  1286. else:
  1287. self.instance.scope_id = None
  1288. class VLANGroupCSVForm(CustomFieldModelCSVForm):
  1289. slug = SlugField()
  1290. scope_type = CSVContentTypeField(
  1291. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  1292. required=False,
  1293. label='Scope type (app & model)'
  1294. )
  1295. class Meta:
  1296. model = VLANGroup
  1297. fields = ('name', 'slug', 'scope_type', 'scope_id', 'description')
  1298. labels = {
  1299. 'scope_id': 'Scope ID',
  1300. }
  1301. class VLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
  1302. pk = forms.ModelMultipleChoiceField(
  1303. queryset=VLANGroup.objects.all(),
  1304. widget=forms.MultipleHiddenInput
  1305. )
  1306. site = DynamicModelChoiceField(
  1307. queryset=Site.objects.all(),
  1308. required=False
  1309. )
  1310. description = forms.CharField(
  1311. max_length=200,
  1312. required=False
  1313. )
  1314. class Meta:
  1315. nullable_fields = ['site', 'description']
  1316. class VLANGroupFilterForm(BootstrapMixin, forms.Form):
  1317. field_groups = [
  1318. ['q'],
  1319. ['region', 'sitegroup', 'site', 'location', 'rack']
  1320. ]
  1321. q = forms.CharField(
  1322. required=False,
  1323. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  1324. label=_('Search')
  1325. )
  1326. region = DynamicModelMultipleChoiceField(
  1327. queryset=Region.objects.all(),
  1328. required=False,
  1329. label=_('Region'),
  1330. fetch_trigger='open'
  1331. )
  1332. sitegroup = DynamicModelMultipleChoiceField(
  1333. queryset=SiteGroup.objects.all(),
  1334. required=False,
  1335. label=_('Site group'),
  1336. fetch_trigger='open'
  1337. )
  1338. site = DynamicModelMultipleChoiceField(
  1339. queryset=Site.objects.all(),
  1340. required=False,
  1341. label=_('Site'),
  1342. fetch_trigger='open'
  1343. )
  1344. location = DynamicModelMultipleChoiceField(
  1345. queryset=Location.objects.all(),
  1346. required=False,
  1347. label=_('Location'),
  1348. fetch_trigger='open'
  1349. )
  1350. rack = DynamicModelMultipleChoiceField(
  1351. queryset=Rack.objects.all(),
  1352. required=False,
  1353. label=_('Rack'),
  1354. fetch_trigger='open'
  1355. )
  1356. #
  1357. # VLANs
  1358. #
  1359. class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  1360. # VLANGroup assignment fields
  1361. scope_type = forms.ChoiceField(
  1362. choices=(
  1363. ('', ''),
  1364. ('dcim.region', 'Region'),
  1365. ('dcim.sitegroup', 'Site group'),
  1366. ('dcim.site', 'Site'),
  1367. ('dcim.location', 'Location'),
  1368. ('dcim.rack', 'Rack'),
  1369. ('virtualization.clustergroup', 'Cluster group'),
  1370. ('virtualization.cluster', 'Cluster'),
  1371. ),
  1372. required=False,
  1373. widget=StaticSelect,
  1374. label='Group scope'
  1375. )
  1376. group = DynamicModelChoiceField(
  1377. queryset=VLANGroup.objects.all(),
  1378. required=False,
  1379. query_params={
  1380. 'scope_type': '$scope_type',
  1381. },
  1382. label='VLAN Group'
  1383. )
  1384. # Site assignment fields
  1385. region = DynamicModelChoiceField(
  1386. queryset=Region.objects.all(),
  1387. required=False,
  1388. initial_params={
  1389. 'sites': '$site'
  1390. },
  1391. label='Region'
  1392. )
  1393. sitegroup = DynamicModelChoiceField(
  1394. queryset=SiteGroup.objects.all(),
  1395. required=False,
  1396. initial_params={
  1397. 'sites': '$site'
  1398. },
  1399. label='Site group'
  1400. )
  1401. site = DynamicModelChoiceField(
  1402. queryset=Site.objects.all(),
  1403. required=False,
  1404. null_option='None',
  1405. query_params={
  1406. 'region_id': '$region',
  1407. 'group_id': '$sitegroup',
  1408. }
  1409. )
  1410. # Other fields
  1411. role = DynamicModelChoiceField(
  1412. queryset=Role.objects.all(),
  1413. required=False
  1414. )
  1415. tags = DynamicModelMultipleChoiceField(
  1416. queryset=Tag.objects.all(),
  1417. required=False
  1418. )
  1419. class Meta:
  1420. model = VLAN
  1421. fields = [
  1422. 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  1423. ]
  1424. help_texts = {
  1425. 'site': "Leave blank if this VLAN spans multiple sites",
  1426. 'group': "VLAN group (optional)",
  1427. 'vid': "Configured VLAN ID",
  1428. 'name': "Configured VLAN name",
  1429. 'status': "Operational status of this VLAN",
  1430. 'role': "The primary function of this VLAN",
  1431. }
  1432. widgets = {
  1433. 'status': StaticSelect(),
  1434. }
  1435. class VLANCSVForm(CustomFieldModelCSVForm):
  1436. site = CSVModelChoiceField(
  1437. queryset=Site.objects.all(),
  1438. required=False,
  1439. to_field_name='name',
  1440. help_text='Assigned site'
  1441. )
  1442. group = CSVModelChoiceField(
  1443. queryset=VLANGroup.objects.all(),
  1444. required=False,
  1445. to_field_name='name',
  1446. help_text='Assigned VLAN group'
  1447. )
  1448. tenant = CSVModelChoiceField(
  1449. queryset=Tenant.objects.all(),
  1450. to_field_name='name',
  1451. required=False,
  1452. help_text='Assigned tenant'
  1453. )
  1454. status = CSVChoiceField(
  1455. choices=VLANStatusChoices,
  1456. help_text='Operational status'
  1457. )
  1458. role = CSVModelChoiceField(
  1459. queryset=Role.objects.all(),
  1460. required=False,
  1461. to_field_name='name',
  1462. help_text='Functional role'
  1463. )
  1464. class Meta:
  1465. model = VLAN
  1466. fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description')
  1467. help_texts = {
  1468. 'vid': 'Numeric VLAN ID (1-4095)',
  1469. 'name': 'VLAN name',
  1470. }
  1471. class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  1472. pk = forms.ModelMultipleChoiceField(
  1473. queryset=VLAN.objects.all(),
  1474. widget=forms.MultipleHiddenInput()
  1475. )
  1476. region = DynamicModelChoiceField(
  1477. queryset=Region.objects.all(),
  1478. required=False
  1479. )
  1480. site_group = DynamicModelChoiceField(
  1481. queryset=SiteGroup.objects.all(),
  1482. required=False
  1483. )
  1484. site = DynamicModelChoiceField(
  1485. queryset=Site.objects.all(),
  1486. required=False,
  1487. query_params={
  1488. 'region_id': '$region',
  1489. 'group_id': '$site_group',
  1490. }
  1491. )
  1492. group = DynamicModelChoiceField(
  1493. queryset=VLANGroup.objects.all(),
  1494. required=False,
  1495. query_params={
  1496. 'site_id': '$site'
  1497. }
  1498. )
  1499. tenant = DynamicModelChoiceField(
  1500. queryset=Tenant.objects.all(),
  1501. required=False
  1502. )
  1503. status = forms.ChoiceField(
  1504. choices=add_blank_choice(VLANStatusChoices),
  1505. required=False,
  1506. widget=StaticSelect()
  1507. )
  1508. role = DynamicModelChoiceField(
  1509. queryset=Role.objects.all(),
  1510. required=False
  1511. )
  1512. description = forms.CharField(
  1513. max_length=100,
  1514. required=False
  1515. )
  1516. class Meta:
  1517. nullable_fields = [
  1518. 'site', 'group', 'tenant', 'role', 'description',
  1519. ]
  1520. class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
  1521. model = VLAN
  1522. field_groups = [
  1523. ['q', 'tag'],
  1524. ['region_id', 'site_group_id', 'site_id'],
  1525. ['group_id', 'status', 'role_id'],
  1526. ['tenant_group_id', 'tenant_id'],
  1527. ]
  1528. q = forms.CharField(
  1529. required=False,
  1530. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  1531. label=_('Search')
  1532. )
  1533. region_id = DynamicModelMultipleChoiceField(
  1534. queryset=Region.objects.all(),
  1535. required=False,
  1536. label=_('Region'),
  1537. fetch_trigger='open'
  1538. )
  1539. site_group_id = DynamicModelMultipleChoiceField(
  1540. queryset=SiteGroup.objects.all(),
  1541. required=False,
  1542. label=_('Site group'),
  1543. fetch_trigger='open'
  1544. )
  1545. site_id = DynamicModelMultipleChoiceField(
  1546. queryset=Site.objects.all(),
  1547. required=False,
  1548. null_option='None',
  1549. query_params={
  1550. 'region': '$region'
  1551. },
  1552. label=_('Site'),
  1553. fetch_trigger='open'
  1554. )
  1555. group_id = DynamicModelMultipleChoiceField(
  1556. queryset=VLANGroup.objects.all(),
  1557. required=False,
  1558. null_option='None',
  1559. query_params={
  1560. 'region': '$region'
  1561. },
  1562. label=_('VLAN group'),
  1563. fetch_trigger='open'
  1564. )
  1565. status = forms.MultipleChoiceField(
  1566. choices=VLANStatusChoices,
  1567. required=False,
  1568. widget=StaticSelectMultiple()
  1569. )
  1570. role_id = DynamicModelMultipleChoiceField(
  1571. queryset=Role.objects.all(),
  1572. required=False,
  1573. null_option='None',
  1574. label=_('Role'),
  1575. fetch_trigger='open'
  1576. )
  1577. tag = TagFilterField(model)
  1578. #
  1579. # Services
  1580. #
  1581. class ServiceForm(BootstrapMixin, CustomFieldModelForm):
  1582. ports = NumericArrayField(
  1583. base_field=forms.IntegerField(
  1584. min_value=SERVICE_PORT_MIN,
  1585. max_value=SERVICE_PORT_MAX
  1586. ),
  1587. help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
  1588. )
  1589. tags = DynamicModelMultipleChoiceField(
  1590. queryset=Tag.objects.all(),
  1591. required=False
  1592. )
  1593. class Meta:
  1594. model = Service
  1595. fields = [
  1596. 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
  1597. ]
  1598. help_texts = {
  1599. 'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
  1600. "reachable via all IPs assigned to the device.",
  1601. }
  1602. widgets = {
  1603. 'protocol': StaticSelect(),
  1604. 'ipaddresses': StaticSelectMultiple(),
  1605. }
  1606. def __init__(self, *args, **kwargs):
  1607. super().__init__(*args, **kwargs)
  1608. # Limit IP address choices to those assigned to interfaces of the parent device/VM
  1609. if self.instance.device:
  1610. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  1611. interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True)
  1612. )
  1613. elif self.instance.virtual_machine:
  1614. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  1615. vminterface__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
  1616. )
  1617. else:
  1618. self.fields['ipaddresses'].choices = []
  1619. class ServiceFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
  1620. model = Service
  1621. field_groups = (
  1622. ('q', 'tag'),
  1623. ('protocol', 'port'),
  1624. )
  1625. q = forms.CharField(
  1626. required=False,
  1627. widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
  1628. label=_('Search')
  1629. )
  1630. protocol = forms.ChoiceField(
  1631. choices=add_blank_choice(ServiceProtocolChoices),
  1632. required=False,
  1633. widget=StaticSelectMultiple()
  1634. )
  1635. port = forms.IntegerField(
  1636. required=False,
  1637. )
  1638. tag = TagFilterField(model)
  1639. class ServiceCSVForm(CustomFieldModelCSVForm):
  1640. device = CSVModelChoiceField(
  1641. queryset=Device.objects.all(),
  1642. required=False,
  1643. to_field_name='name',
  1644. help_text='Required if not assigned to a VM'
  1645. )
  1646. virtual_machine = CSVModelChoiceField(
  1647. queryset=VirtualMachine.objects.all(),
  1648. required=False,
  1649. to_field_name='name',
  1650. help_text='Required if not assigned to a device'
  1651. )
  1652. protocol = CSVChoiceField(
  1653. choices=ServiceProtocolChoices,
  1654. help_text='IP protocol'
  1655. )
  1656. class Meta:
  1657. model = Service
  1658. fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description')
  1659. class ServiceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
  1660. pk = forms.ModelMultipleChoiceField(
  1661. queryset=Service.objects.all(),
  1662. widget=forms.MultipleHiddenInput()
  1663. )
  1664. protocol = forms.ChoiceField(
  1665. choices=add_blank_choice(ServiceProtocolChoices),
  1666. required=False,
  1667. widget=StaticSelect()
  1668. )
  1669. ports = NumericArrayField(
  1670. base_field=forms.IntegerField(
  1671. min_value=SERVICE_PORT_MIN,
  1672. max_value=SERVICE_PORT_MAX
  1673. ),
  1674. required=False
  1675. )
  1676. description = forms.CharField(
  1677. max_length=100,
  1678. required=False
  1679. )
  1680. class Meta:
  1681. nullable_fields = [
  1682. 'description',
  1683. ]