forms.py 46 KB

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