forms.py 53 KB

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