forms.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643
  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. q = forms.CharField(
  1135. required=False,
  1136. label=_('Search')
  1137. )
  1138. region = DynamicModelMultipleChoiceField(
  1139. queryset=Region.objects.all(),
  1140. required=False,
  1141. label=_('Region')
  1142. )
  1143. sitegroup = DynamicModelMultipleChoiceField(
  1144. queryset=SiteGroup.objects.all(),
  1145. required=False,
  1146. label=_('Site group')
  1147. )
  1148. site = DynamicModelMultipleChoiceField(
  1149. queryset=Site.objects.all(),
  1150. required=False,
  1151. label=_('Site')
  1152. )
  1153. location = DynamicModelMultipleChoiceField(
  1154. queryset=Location.objects.all(),
  1155. required=False,
  1156. label=_('Location')
  1157. )
  1158. rack = DynamicModelMultipleChoiceField(
  1159. queryset=Rack.objects.all(),
  1160. required=False,
  1161. label=_('Rack')
  1162. )
  1163. #
  1164. # VLANs
  1165. #
  1166. class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  1167. # VLANGroup assignment fields
  1168. scope_type = forms.ChoiceField(
  1169. choices=(
  1170. ('', ''),
  1171. ('dcim.region', 'Region'),
  1172. ('dcim.sitegroup', 'Site group'),
  1173. ('dcim.site', 'Site'),
  1174. ('dcim.location', 'Location'),
  1175. ('dcim.rack', 'Rack'),
  1176. ('virtualization.clustergroup', 'Cluster group'),
  1177. ('virtualization.cluster', 'Cluster'),
  1178. ),
  1179. required=False,
  1180. widget=StaticSelect2,
  1181. label='Group scope'
  1182. )
  1183. group = DynamicModelChoiceField(
  1184. queryset=VLANGroup.objects.all(),
  1185. required=False,
  1186. query_params={
  1187. 'scope_type': '$scope_type',
  1188. },
  1189. label='VLAN Group'
  1190. )
  1191. # Site assignment fields
  1192. region = DynamicModelChoiceField(
  1193. queryset=Region.objects.all(),
  1194. required=False,
  1195. initial_params={
  1196. 'sites': '$site'
  1197. },
  1198. label='Region'
  1199. )
  1200. sitegroup = DynamicModelChoiceField(
  1201. queryset=SiteGroup.objects.all(),
  1202. required=False,
  1203. initial_params={
  1204. 'sites': '$site'
  1205. },
  1206. label='Site group'
  1207. )
  1208. site = DynamicModelChoiceField(
  1209. queryset=Site.objects.all(),
  1210. required=False,
  1211. null_option='None',
  1212. query_params={
  1213. 'region_id': '$region',
  1214. 'group_id': '$sitegroup',
  1215. }
  1216. )
  1217. # Other fields
  1218. role = DynamicModelChoiceField(
  1219. queryset=Role.objects.all(),
  1220. required=False
  1221. )
  1222. tags = DynamicModelMultipleChoiceField(
  1223. queryset=Tag.objects.all(),
  1224. required=False
  1225. )
  1226. class Meta:
  1227. model = VLAN
  1228. fields = [
  1229. 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  1230. ]
  1231. help_texts = {
  1232. 'site': "Leave blank if this VLAN spans multiple sites",
  1233. 'group': "VLAN group (optional)",
  1234. 'vid': "Configured VLAN ID",
  1235. 'name': "Configured VLAN name",
  1236. 'status': "Operational status of this VLAN",
  1237. 'role': "The primary function of this VLAN",
  1238. }
  1239. widgets = {
  1240. 'status': StaticSelect2(),
  1241. }
  1242. class VLANCSVForm(CustomFieldModelCSVForm):
  1243. site = CSVModelChoiceField(
  1244. queryset=Site.objects.all(),
  1245. required=False,
  1246. to_field_name='name',
  1247. help_text='Assigned site'
  1248. )
  1249. group = CSVModelChoiceField(
  1250. queryset=VLANGroup.objects.all(),
  1251. required=False,
  1252. to_field_name='name',
  1253. help_text='Assigned VLAN group'
  1254. )
  1255. tenant = CSVModelChoiceField(
  1256. queryset=Tenant.objects.all(),
  1257. to_field_name='name',
  1258. required=False,
  1259. help_text='Assigned tenant'
  1260. )
  1261. status = CSVChoiceField(
  1262. choices=VLANStatusChoices,
  1263. help_text='Operational status'
  1264. )
  1265. role = CSVModelChoiceField(
  1266. queryset=Role.objects.all(),
  1267. required=False,
  1268. to_field_name='name',
  1269. help_text='Functional role'
  1270. )
  1271. class Meta:
  1272. model = VLAN
  1273. fields = VLAN.csv_headers
  1274. help_texts = {
  1275. 'vid': 'Numeric VLAN ID (1-4095)',
  1276. 'name': 'VLAN name',
  1277. }
  1278. class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
  1279. pk = forms.ModelMultipleChoiceField(
  1280. queryset=VLAN.objects.all(),
  1281. widget=forms.MultipleHiddenInput()
  1282. )
  1283. region = DynamicModelChoiceField(
  1284. queryset=Region.objects.all(),
  1285. required=False
  1286. )
  1287. site_group = DynamicModelChoiceField(
  1288. queryset=SiteGroup.objects.all(),
  1289. required=False
  1290. )
  1291. site = DynamicModelChoiceField(
  1292. queryset=Site.objects.all(),
  1293. required=False,
  1294. query_params={
  1295. 'region_id': '$region',
  1296. 'group_id': '$site_group',
  1297. }
  1298. )
  1299. group = DynamicModelChoiceField(
  1300. queryset=VLANGroup.objects.all(),
  1301. required=False,
  1302. query_params={
  1303. 'site_id': '$site'
  1304. }
  1305. )
  1306. tenant = DynamicModelChoiceField(
  1307. queryset=Tenant.objects.all(),
  1308. required=False
  1309. )
  1310. status = forms.ChoiceField(
  1311. choices=add_blank_choice(VLANStatusChoices),
  1312. required=False,
  1313. widget=StaticSelect2()
  1314. )
  1315. role = DynamicModelChoiceField(
  1316. queryset=Role.objects.all(),
  1317. required=False
  1318. )
  1319. description = forms.CharField(
  1320. max_length=100,
  1321. required=False
  1322. )
  1323. class Meta:
  1324. nullable_fields = [
  1325. 'site', 'group', 'tenant', 'role', 'description',
  1326. ]
  1327. class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
  1328. model = VLAN
  1329. field_order = [
  1330. 'q', 'region_id', 'site_group_id', 'site_id', 'group_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
  1331. ]
  1332. q = forms.CharField(
  1333. required=False,
  1334. label='Search'
  1335. )
  1336. region_id = DynamicModelMultipleChoiceField(
  1337. queryset=Region.objects.all(),
  1338. required=False,
  1339. label=_('Region')
  1340. )
  1341. site_group_id = DynamicModelMultipleChoiceField(
  1342. queryset=SiteGroup.objects.all(),
  1343. required=False,
  1344. label=_('Site group')
  1345. )
  1346. site_id = DynamicModelMultipleChoiceField(
  1347. queryset=Site.objects.all(),
  1348. required=False,
  1349. null_option='None',
  1350. query_params={
  1351. 'region': '$region'
  1352. },
  1353. label=_('Site')
  1354. )
  1355. group_id = DynamicModelMultipleChoiceField(
  1356. queryset=VLANGroup.objects.all(),
  1357. required=False,
  1358. null_option='None',
  1359. query_params={
  1360. 'region': '$region'
  1361. },
  1362. label=_('VLAN group')
  1363. )
  1364. status = forms.MultipleChoiceField(
  1365. choices=VLANStatusChoices,
  1366. required=False,
  1367. widget=StaticSelect2Multiple()
  1368. )
  1369. role_id = DynamicModelMultipleChoiceField(
  1370. queryset=Role.objects.all(),
  1371. required=False,
  1372. null_option='None',
  1373. label=_('Role')
  1374. )
  1375. tag = TagFilterField(model)
  1376. #
  1377. # Services
  1378. #
  1379. class ServiceForm(BootstrapMixin, CustomFieldModelForm):
  1380. ports = NumericArrayField(
  1381. base_field=forms.IntegerField(
  1382. min_value=SERVICE_PORT_MIN,
  1383. max_value=SERVICE_PORT_MAX
  1384. ),
  1385. help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
  1386. )
  1387. tags = DynamicModelMultipleChoiceField(
  1388. queryset=Tag.objects.all(),
  1389. required=False
  1390. )
  1391. class Meta:
  1392. model = Service
  1393. fields = [
  1394. 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
  1395. ]
  1396. help_texts = {
  1397. 'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
  1398. "reachable via all IPs assigned to the device.",
  1399. }
  1400. widgets = {
  1401. 'protocol': StaticSelect2(),
  1402. 'ipaddresses': StaticSelect2Multiple(),
  1403. }
  1404. def __init__(self, *args, **kwargs):
  1405. super().__init__(*args, **kwargs)
  1406. # Limit IP address choices to those assigned to interfaces of the parent device/VM
  1407. if self.instance.device:
  1408. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  1409. interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True)
  1410. )
  1411. elif self.instance.virtual_machine:
  1412. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  1413. vminterface__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
  1414. )
  1415. else:
  1416. self.fields['ipaddresses'].choices = []
  1417. class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
  1418. model = Service
  1419. q = forms.CharField(
  1420. required=False,
  1421. label=_('Search')
  1422. )
  1423. protocol = forms.ChoiceField(
  1424. choices=add_blank_choice(ServiceProtocolChoices),
  1425. required=False,
  1426. widget=StaticSelect2Multiple()
  1427. )
  1428. port = forms.IntegerField(
  1429. required=False,
  1430. )
  1431. tag = TagFilterField(model)
  1432. class ServiceCSVForm(CustomFieldModelCSVForm):
  1433. device = CSVModelChoiceField(
  1434. queryset=Device.objects.all(),
  1435. required=False,
  1436. to_field_name='name',
  1437. help_text='Required if not assigned to a VM'
  1438. )
  1439. virtual_machine = CSVModelChoiceField(
  1440. queryset=VirtualMachine.objects.all(),
  1441. required=False,
  1442. to_field_name='name',
  1443. help_text='Required if not assigned to a device'
  1444. )
  1445. protocol = CSVChoiceField(
  1446. choices=ServiceProtocolChoices,
  1447. help_text='IP protocol'
  1448. )
  1449. class Meta:
  1450. model = Service
  1451. fields = Service.csv_headers
  1452. class ServiceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
  1453. pk = forms.ModelMultipleChoiceField(
  1454. queryset=Service.objects.all(),
  1455. widget=forms.MultipleHiddenInput()
  1456. )
  1457. protocol = forms.ChoiceField(
  1458. choices=add_blank_choice(ServiceProtocolChoices),
  1459. required=False,
  1460. widget=StaticSelect2()
  1461. )
  1462. ports = NumericArrayField(
  1463. base_field=forms.IntegerField(
  1464. min_value=SERVICE_PORT_MIN,
  1465. max_value=SERVICE_PORT_MAX
  1466. ),
  1467. required=False
  1468. )
  1469. description = forms.CharField(
  1470. max_length=100,
  1471. required=False
  1472. )
  1473. class Meta:
  1474. nullable_fields = [
  1475. 'description',
  1476. ]