forms.py 53 KB

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