forms.py 53 KB

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