bulk_edit.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422
  1. from django import forms
  2. from django.contrib.auth.models import User
  3. from django.utils.translation import gettext as _
  4. from timezone_field import TimeZoneFormField
  5. from dcim.choices import *
  6. from dcim.constants import *
  7. from dcim.models import *
  8. from ipam.models import ASN, VLAN, VLANGroup, VRF
  9. from netbox.forms import NetBoxModelBulkEditForm
  10. from tenancy.models import Tenant
  11. from utilities.forms import (
  12. add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
  13. DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, SelectSpeedWidget,
  14. )
  15. __all__ = (
  16. 'CableBulkEditForm',
  17. 'ConsolePortBulkEditForm',
  18. 'ConsolePortTemplateBulkEditForm',
  19. 'ConsoleServerPortBulkEditForm',
  20. 'ConsoleServerPortTemplateBulkEditForm',
  21. 'DeviceBayBulkEditForm',
  22. 'DeviceBayTemplateBulkEditForm',
  23. 'DeviceBulkEditForm',
  24. 'DeviceRoleBulkEditForm',
  25. 'DeviceTypeBulkEditForm',
  26. 'FrontPortBulkEditForm',
  27. 'FrontPortTemplateBulkEditForm',
  28. 'InterfaceBulkEditForm',
  29. 'InterfaceTemplateBulkEditForm',
  30. 'InventoryItemBulkEditForm',
  31. 'InventoryItemRoleBulkEditForm',
  32. 'InventoryItemTemplateBulkEditForm',
  33. 'LocationBulkEditForm',
  34. 'ManufacturerBulkEditForm',
  35. 'ModuleBulkEditForm',
  36. 'ModuleBayBulkEditForm',
  37. 'ModuleBayTemplateBulkEditForm',
  38. 'ModuleTypeBulkEditForm',
  39. 'PlatformBulkEditForm',
  40. 'PowerFeedBulkEditForm',
  41. 'PowerOutletBulkEditForm',
  42. 'PowerOutletTemplateBulkEditForm',
  43. 'PowerPanelBulkEditForm',
  44. 'PowerPortBulkEditForm',
  45. 'PowerPortTemplateBulkEditForm',
  46. 'RackBulkEditForm',
  47. 'RackReservationBulkEditForm',
  48. 'RackRoleBulkEditForm',
  49. 'RearPortBulkEditForm',
  50. 'RearPortTemplateBulkEditForm',
  51. 'RegionBulkEditForm',
  52. 'SiteBulkEditForm',
  53. 'SiteGroupBulkEditForm',
  54. 'VirtualChassisBulkEditForm',
  55. 'VirtualDeviceContextBulkEditForm'
  56. )
  57. class RegionBulkEditForm(NetBoxModelBulkEditForm):
  58. parent = DynamicModelChoiceField(
  59. queryset=Region.objects.all(),
  60. required=False
  61. )
  62. description = forms.CharField(
  63. max_length=200,
  64. required=False
  65. )
  66. model = Region
  67. fieldsets = (
  68. (None, ('parent', 'description')),
  69. )
  70. nullable_fields = ('parent', 'description')
  71. class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
  72. parent = DynamicModelChoiceField(
  73. queryset=SiteGroup.objects.all(),
  74. required=False
  75. )
  76. description = forms.CharField(
  77. max_length=200,
  78. required=False
  79. )
  80. model = SiteGroup
  81. fieldsets = (
  82. (None, ('parent', 'description')),
  83. )
  84. nullable_fields = ('parent', 'description')
  85. class SiteBulkEditForm(NetBoxModelBulkEditForm):
  86. status = forms.ChoiceField(
  87. choices=add_blank_choice(SiteStatusChoices),
  88. required=False,
  89. initial='',
  90. widget=StaticSelect()
  91. )
  92. region = DynamicModelChoiceField(
  93. queryset=Region.objects.all(),
  94. required=False
  95. )
  96. group = DynamicModelChoiceField(
  97. queryset=SiteGroup.objects.all(),
  98. required=False
  99. )
  100. tenant = DynamicModelChoiceField(
  101. queryset=Tenant.objects.all(),
  102. required=False
  103. )
  104. asns = DynamicModelMultipleChoiceField(
  105. queryset=ASN.objects.all(),
  106. label=_('ASNs'),
  107. required=False
  108. )
  109. contact_name = forms.CharField(
  110. max_length=50,
  111. required=False
  112. )
  113. contact_phone = forms.CharField(
  114. max_length=20,
  115. required=False
  116. )
  117. contact_email = forms.EmailField(
  118. required=False,
  119. label=_('Contact E-mail')
  120. )
  121. time_zone = TimeZoneFormField(
  122. choices=add_blank_choice(TimeZoneFormField().choices),
  123. required=False,
  124. widget=StaticSelect()
  125. )
  126. description = forms.CharField(
  127. max_length=200,
  128. required=False
  129. )
  130. comments = CommentField(
  131. widget=SmallTextarea,
  132. label='Comments'
  133. )
  134. model = Site
  135. fieldsets = (
  136. (None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
  137. )
  138. nullable_fields = (
  139. 'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
  140. )
  141. class LocationBulkEditForm(NetBoxModelBulkEditForm):
  142. site = DynamicModelChoiceField(
  143. queryset=Site.objects.all(),
  144. required=False
  145. )
  146. parent = DynamicModelChoiceField(
  147. queryset=Location.objects.all(),
  148. required=False,
  149. query_params={
  150. 'site_id': '$site'
  151. }
  152. )
  153. status = forms.ChoiceField(
  154. choices=add_blank_choice(LocationStatusChoices),
  155. required=False,
  156. initial='',
  157. widget=StaticSelect()
  158. )
  159. tenant = DynamicModelChoiceField(
  160. queryset=Tenant.objects.all(),
  161. required=False
  162. )
  163. description = forms.CharField(
  164. max_length=200,
  165. required=False
  166. )
  167. model = Location
  168. fieldsets = (
  169. (None, ('site', 'parent', 'status', 'tenant', 'description')),
  170. )
  171. nullable_fields = ('parent', 'tenant', 'description')
  172. class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
  173. color = ColorField(
  174. required=False
  175. )
  176. description = forms.CharField(
  177. max_length=200,
  178. required=False
  179. )
  180. model = RackRole
  181. fieldsets = (
  182. (None, ('color', 'description')),
  183. )
  184. nullable_fields = ('color', 'description')
  185. class RackBulkEditForm(NetBoxModelBulkEditForm):
  186. region = DynamicModelChoiceField(
  187. queryset=Region.objects.all(),
  188. required=False,
  189. initial_params={
  190. 'sites': '$site'
  191. }
  192. )
  193. site_group = DynamicModelChoiceField(
  194. queryset=SiteGroup.objects.all(),
  195. required=False,
  196. initial_params={
  197. 'sites': '$site'
  198. }
  199. )
  200. site = DynamicModelChoiceField(
  201. queryset=Site.objects.all(),
  202. required=False,
  203. query_params={
  204. 'region_id': '$region',
  205. 'group_id': '$site_group',
  206. }
  207. )
  208. location = DynamicModelChoiceField(
  209. queryset=Location.objects.all(),
  210. required=False,
  211. query_params={
  212. 'site_id': '$site'
  213. }
  214. )
  215. tenant = DynamicModelChoiceField(
  216. queryset=Tenant.objects.all(),
  217. required=False
  218. )
  219. status = forms.ChoiceField(
  220. choices=add_blank_choice(RackStatusChoices),
  221. required=False,
  222. initial='',
  223. widget=StaticSelect()
  224. )
  225. role = DynamicModelChoiceField(
  226. queryset=RackRole.objects.all(),
  227. required=False
  228. )
  229. serial = forms.CharField(
  230. max_length=50,
  231. required=False,
  232. label=_('Serial Number')
  233. )
  234. asset_tag = forms.CharField(
  235. max_length=50,
  236. required=False
  237. )
  238. type = forms.ChoiceField(
  239. choices=add_blank_choice(RackTypeChoices),
  240. required=False,
  241. widget=StaticSelect()
  242. )
  243. width = forms.ChoiceField(
  244. choices=add_blank_choice(RackWidthChoices),
  245. required=False,
  246. widget=StaticSelect()
  247. )
  248. u_height = forms.IntegerField(
  249. required=False,
  250. label=_('Height (U)')
  251. )
  252. desc_units = forms.NullBooleanField(
  253. required=False,
  254. widget=BulkEditNullBooleanSelect,
  255. label=_('Descending units')
  256. )
  257. outer_width = forms.IntegerField(
  258. required=False,
  259. min_value=1
  260. )
  261. outer_depth = forms.IntegerField(
  262. required=False,
  263. min_value=1
  264. )
  265. outer_unit = forms.ChoiceField(
  266. choices=add_blank_choice(RackDimensionUnitChoices),
  267. required=False,
  268. widget=StaticSelect()
  269. )
  270. mounting_depth = forms.IntegerField(
  271. required=False,
  272. min_value=1
  273. )
  274. weight = forms.DecimalField(
  275. min_value=0,
  276. required=False
  277. )
  278. weight_unit = forms.ChoiceField(
  279. choices=add_blank_choice(WeightUnitChoices),
  280. required=False,
  281. initial='',
  282. widget=StaticSelect()
  283. )
  284. description = forms.CharField(
  285. max_length=200,
  286. required=False
  287. )
  288. comments = CommentField(
  289. widget=SmallTextarea,
  290. label='Comments'
  291. )
  292. model = Rack
  293. fieldsets = (
  294. ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag', 'description')),
  295. ('Location', ('region', 'site_group', 'site', 'location')),
  296. ('Hardware', (
  297. 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
  298. )),
  299. ('Weight', ('weight', 'weight_unit')),
  300. )
  301. nullable_fields = (
  302. 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
  303. 'weight_unit', 'description', 'comments',
  304. )
  305. class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
  306. user = forms.ModelChoiceField(
  307. queryset=User.objects.order_by(
  308. 'username'
  309. ),
  310. required=False,
  311. widget=StaticSelect()
  312. )
  313. tenant = DynamicModelChoiceField(
  314. queryset=Tenant.objects.all(),
  315. required=False
  316. )
  317. description = forms.CharField(
  318. max_length=200,
  319. required=False
  320. )
  321. comments = CommentField(
  322. widget=SmallTextarea,
  323. label='Comments'
  324. )
  325. model = RackReservation
  326. fieldsets = (
  327. (None, ('user', 'tenant', 'description')),
  328. )
  329. nullable_fields = ('comments',)
  330. class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
  331. description = forms.CharField(
  332. max_length=200,
  333. required=False
  334. )
  335. model = Manufacturer
  336. fieldsets = (
  337. (None, ('description',)),
  338. )
  339. nullable_fields = ('description',)
  340. class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
  341. manufacturer = DynamicModelChoiceField(
  342. queryset=Manufacturer.objects.all(),
  343. required=False
  344. )
  345. part_number = forms.CharField(
  346. required=False
  347. )
  348. u_height = forms.IntegerField(
  349. min_value=1,
  350. required=False
  351. )
  352. is_full_depth = forms.NullBooleanField(
  353. required=False,
  354. widget=BulkEditNullBooleanSelect(),
  355. label=_('Is full depth')
  356. )
  357. airflow = forms.ChoiceField(
  358. choices=add_blank_choice(DeviceAirflowChoices),
  359. required=False,
  360. widget=StaticSelect()
  361. )
  362. weight = forms.DecimalField(
  363. min_value=0,
  364. required=False
  365. )
  366. weight_unit = forms.ChoiceField(
  367. choices=add_blank_choice(WeightUnitChoices),
  368. required=False,
  369. initial='',
  370. widget=StaticSelect()
  371. )
  372. description = forms.CharField(
  373. max_length=200,
  374. required=False
  375. )
  376. comments = CommentField(
  377. widget=SmallTextarea,
  378. label='Comments'
  379. )
  380. model = DeviceType
  381. fieldsets = (
  382. ('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
  383. ('Weight', ('weight', 'weight_unit')),
  384. )
  385. nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
  386. class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
  387. manufacturer = DynamicModelChoiceField(
  388. queryset=Manufacturer.objects.all(),
  389. required=False
  390. )
  391. part_number = forms.CharField(
  392. required=False
  393. )
  394. weight = forms.DecimalField(
  395. min_value=0,
  396. required=False
  397. )
  398. weight_unit = forms.ChoiceField(
  399. choices=add_blank_choice(WeightUnitChoices),
  400. required=False,
  401. initial='',
  402. widget=StaticSelect()
  403. )
  404. description = forms.CharField(
  405. max_length=200,
  406. required=False
  407. )
  408. comments = CommentField(
  409. widget=SmallTextarea,
  410. label='Comments'
  411. )
  412. model = ModuleType
  413. fieldsets = (
  414. ('Module Type', ('manufacturer', 'part_number', 'description')),
  415. ('Weight', ('weight', 'weight_unit')),
  416. )
  417. nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
  418. class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
  419. color = ColorField(
  420. required=False
  421. )
  422. vm_role = forms.NullBooleanField(
  423. required=False,
  424. widget=BulkEditNullBooleanSelect,
  425. label=_('VM role')
  426. )
  427. description = forms.CharField(
  428. max_length=200,
  429. required=False
  430. )
  431. model = DeviceRole
  432. fieldsets = (
  433. (None, ('color', 'vm_role', 'description')),
  434. )
  435. nullable_fields = ('color', 'description')
  436. class PlatformBulkEditForm(NetBoxModelBulkEditForm):
  437. manufacturer = DynamicModelChoiceField(
  438. queryset=Manufacturer.objects.all(),
  439. required=False
  440. )
  441. napalm_driver = forms.CharField(
  442. max_length=50,
  443. required=False
  444. )
  445. # TODO: Bulk edit support for napalm_args
  446. description = forms.CharField(
  447. max_length=200,
  448. required=False
  449. )
  450. model = Platform
  451. fieldsets = (
  452. (None, ('manufacturer', 'napalm_driver', 'description')),
  453. )
  454. nullable_fields = ('manufacturer', 'napalm_driver', 'description')
  455. class DeviceBulkEditForm(NetBoxModelBulkEditForm):
  456. manufacturer = DynamicModelChoiceField(
  457. queryset=Manufacturer.objects.all(),
  458. required=False
  459. )
  460. device_type = DynamicModelChoiceField(
  461. queryset=DeviceType.objects.all(),
  462. required=False,
  463. query_params={
  464. 'manufacturer_id': '$manufacturer'
  465. }
  466. )
  467. device_role = DynamicModelChoiceField(
  468. queryset=DeviceRole.objects.all(),
  469. required=False
  470. )
  471. site = DynamicModelChoiceField(
  472. queryset=Site.objects.all(),
  473. required=False
  474. )
  475. location = DynamicModelChoiceField(
  476. queryset=Location.objects.all(),
  477. required=False,
  478. query_params={
  479. 'site_id': '$site'
  480. }
  481. )
  482. tenant = DynamicModelChoiceField(
  483. queryset=Tenant.objects.all(),
  484. required=False
  485. )
  486. platform = DynamicModelChoiceField(
  487. queryset=Platform.objects.all(),
  488. required=False
  489. )
  490. status = forms.ChoiceField(
  491. choices=add_blank_choice(DeviceStatusChoices),
  492. required=False,
  493. widget=StaticSelect()
  494. )
  495. airflow = forms.ChoiceField(
  496. choices=add_blank_choice(DeviceAirflowChoices),
  497. required=False,
  498. widget=StaticSelect()
  499. )
  500. serial = forms.CharField(
  501. max_length=50,
  502. required=False,
  503. label=_('Serial Number')
  504. )
  505. description = forms.CharField(
  506. max_length=200,
  507. required=False
  508. )
  509. comments = CommentField(
  510. widget=SmallTextarea,
  511. label='Comments'
  512. )
  513. model = Device
  514. fieldsets = (
  515. ('Device', ('device_role', 'status', 'tenant', 'platform', 'description')),
  516. ('Location', ('site', 'location')),
  517. ('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
  518. )
  519. nullable_fields = (
  520. 'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
  521. )
  522. class ModuleBulkEditForm(NetBoxModelBulkEditForm):
  523. manufacturer = DynamicModelChoiceField(
  524. queryset=Manufacturer.objects.all(),
  525. required=False
  526. )
  527. module_type = DynamicModelChoiceField(
  528. queryset=ModuleType.objects.all(),
  529. required=False,
  530. query_params={
  531. 'manufacturer_id': '$manufacturer'
  532. }
  533. )
  534. serial = forms.CharField(
  535. max_length=50,
  536. required=False,
  537. label=_('Serial Number')
  538. )
  539. description = forms.CharField(
  540. max_length=200,
  541. required=False
  542. )
  543. comments = CommentField(
  544. widget=SmallTextarea,
  545. label='Comments'
  546. )
  547. model = Module
  548. fieldsets = (
  549. (None, ('manufacturer', 'module_type', 'serial', 'description')),
  550. )
  551. nullable_fields = ('serial', 'description', 'comments')
  552. class CableBulkEditForm(NetBoxModelBulkEditForm):
  553. type = forms.ChoiceField(
  554. choices=add_blank_choice(CableTypeChoices),
  555. required=False,
  556. initial='',
  557. widget=StaticSelect()
  558. )
  559. status = forms.ChoiceField(
  560. choices=add_blank_choice(LinkStatusChoices),
  561. required=False,
  562. widget=StaticSelect(),
  563. initial=''
  564. )
  565. tenant = DynamicModelChoiceField(
  566. queryset=Tenant.objects.all(),
  567. required=False
  568. )
  569. label = forms.CharField(
  570. max_length=100,
  571. required=False
  572. )
  573. color = ColorField(
  574. required=False
  575. )
  576. length = forms.DecimalField(
  577. min_value=0,
  578. required=False
  579. )
  580. length_unit = forms.ChoiceField(
  581. choices=add_blank_choice(CableLengthUnitChoices),
  582. required=False,
  583. initial='',
  584. widget=StaticSelect()
  585. )
  586. description = forms.CharField(
  587. max_length=200,
  588. required=False
  589. )
  590. comments = CommentField(
  591. widget=SmallTextarea,
  592. label='Comments'
  593. )
  594. model = Cable
  595. fieldsets = (
  596. (None, ('type', 'status', 'tenant', 'label', 'description')),
  597. ('Attributes', ('color', 'length', 'length_unit')),
  598. )
  599. nullable_fields = (
  600. 'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
  601. )
  602. class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
  603. domain = forms.CharField(
  604. max_length=30,
  605. required=False
  606. )
  607. description = forms.CharField(
  608. max_length=200,
  609. required=False
  610. )
  611. comments = CommentField(
  612. widget=SmallTextarea,
  613. label='Comments'
  614. )
  615. model = VirtualChassis
  616. fieldsets = (
  617. (None, ('domain', 'description')),
  618. )
  619. nullable_fields = ('domain', 'description', 'comments')
  620. class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
  621. region = DynamicModelChoiceField(
  622. queryset=Region.objects.all(),
  623. required=False,
  624. initial_params={
  625. 'sites': '$site'
  626. }
  627. )
  628. site_group = DynamicModelChoiceField(
  629. queryset=SiteGroup.objects.all(),
  630. required=False,
  631. initial_params={
  632. 'sites': '$site'
  633. }
  634. )
  635. site = DynamicModelChoiceField(
  636. queryset=Site.objects.all(),
  637. required=False,
  638. query_params={
  639. 'region_id': '$region',
  640. 'group_id': '$site_group',
  641. }
  642. )
  643. location = DynamicModelChoiceField(
  644. queryset=Location.objects.all(),
  645. required=False,
  646. query_params={
  647. 'site_id': '$site'
  648. }
  649. )
  650. description = forms.CharField(
  651. max_length=200,
  652. required=False
  653. )
  654. comments = CommentField(
  655. widget=SmallTextarea,
  656. label='Comments'
  657. )
  658. model = PowerPanel
  659. fieldsets = (
  660. (None, ('region', 'site_group', 'site', 'location', 'description')),
  661. )
  662. nullable_fields = ('location', 'description', 'comments')
  663. class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
  664. power_panel = DynamicModelChoiceField(
  665. queryset=PowerPanel.objects.all(),
  666. required=False
  667. )
  668. rack = DynamicModelChoiceField(
  669. queryset=Rack.objects.all(),
  670. required=False,
  671. )
  672. status = forms.ChoiceField(
  673. choices=add_blank_choice(PowerFeedStatusChoices),
  674. required=False,
  675. initial='',
  676. widget=StaticSelect()
  677. )
  678. type = forms.ChoiceField(
  679. choices=add_blank_choice(PowerFeedTypeChoices),
  680. required=False,
  681. initial='',
  682. widget=StaticSelect()
  683. )
  684. supply = forms.ChoiceField(
  685. choices=add_blank_choice(PowerFeedSupplyChoices),
  686. required=False,
  687. initial='',
  688. widget=StaticSelect()
  689. )
  690. phase = forms.ChoiceField(
  691. choices=add_blank_choice(PowerFeedPhaseChoices),
  692. required=False,
  693. initial='',
  694. widget=StaticSelect()
  695. )
  696. voltage = forms.IntegerField(
  697. required=False
  698. )
  699. amperage = forms.IntegerField(
  700. required=False
  701. )
  702. max_utilization = forms.IntegerField(
  703. required=False
  704. )
  705. mark_connected = forms.NullBooleanField(
  706. required=False,
  707. widget=BulkEditNullBooleanSelect
  708. )
  709. description = forms.CharField(
  710. max_length=200,
  711. required=False
  712. )
  713. comments = CommentField(
  714. widget=SmallTextarea,
  715. label=_('Comments')
  716. )
  717. model = PowerFeed
  718. fieldsets = (
  719. (None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description')),
  720. ('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
  721. )
  722. nullable_fields = ('location', 'description', 'comments')
  723. #
  724. # Device component templates
  725. #
  726. class ConsolePortTemplateBulkEditForm(BulkEditForm):
  727. pk = forms.ModelMultipleChoiceField(
  728. queryset=ConsolePortTemplate.objects.all(),
  729. widget=forms.MultipleHiddenInput()
  730. )
  731. label = forms.CharField(
  732. max_length=64,
  733. required=False
  734. )
  735. type = forms.ChoiceField(
  736. choices=add_blank_choice(ConsolePortTypeChoices),
  737. required=False,
  738. widget=StaticSelect()
  739. )
  740. nullable_fields = ('label', 'type', 'description')
  741. class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
  742. pk = forms.ModelMultipleChoiceField(
  743. queryset=ConsoleServerPortTemplate.objects.all(),
  744. widget=forms.MultipleHiddenInput()
  745. )
  746. label = forms.CharField(
  747. max_length=64,
  748. required=False
  749. )
  750. type = forms.ChoiceField(
  751. choices=add_blank_choice(ConsolePortTypeChoices),
  752. required=False,
  753. widget=StaticSelect()
  754. )
  755. description = forms.CharField(
  756. required=False
  757. )
  758. nullable_fields = ('label', 'type', 'description')
  759. class PowerPortTemplateBulkEditForm(BulkEditForm):
  760. pk = forms.ModelMultipleChoiceField(
  761. queryset=PowerPortTemplate.objects.all(),
  762. widget=forms.MultipleHiddenInput()
  763. )
  764. label = forms.CharField(
  765. max_length=64,
  766. required=False
  767. )
  768. type = forms.ChoiceField(
  769. choices=add_blank_choice(PowerPortTypeChoices),
  770. required=False,
  771. widget=StaticSelect()
  772. )
  773. maximum_draw = forms.IntegerField(
  774. min_value=1,
  775. required=False,
  776. help_text=_("Maximum power draw (watts)")
  777. )
  778. allocated_draw = forms.IntegerField(
  779. min_value=1,
  780. required=False,
  781. help_text=_("Allocated power draw (watts)")
  782. )
  783. description = forms.CharField(
  784. required=False
  785. )
  786. nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
  787. class PowerOutletTemplateBulkEditForm(BulkEditForm):
  788. pk = forms.ModelMultipleChoiceField(
  789. queryset=PowerOutletTemplate.objects.all(),
  790. widget=forms.MultipleHiddenInput()
  791. )
  792. device_type = forms.ModelChoiceField(
  793. queryset=DeviceType.objects.all(),
  794. required=False,
  795. disabled=True,
  796. widget=forms.HiddenInput()
  797. )
  798. label = forms.CharField(
  799. max_length=64,
  800. required=False
  801. )
  802. type = forms.ChoiceField(
  803. choices=add_blank_choice(PowerOutletTypeChoices),
  804. required=False,
  805. widget=StaticSelect()
  806. )
  807. power_port = forms.ModelChoiceField(
  808. queryset=PowerPortTemplate.objects.all(),
  809. required=False
  810. )
  811. feed_leg = forms.ChoiceField(
  812. choices=add_blank_choice(PowerOutletFeedLegChoices),
  813. required=False,
  814. widget=StaticSelect()
  815. )
  816. description = forms.CharField(
  817. required=False
  818. )
  819. nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
  820. def __init__(self, *args, **kwargs):
  821. super().__init__(*args, **kwargs)
  822. # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
  823. if 'device_type' in self.initial:
  824. device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
  825. self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
  826. else:
  827. self.fields['power_port'].choices = ()
  828. self.fields['power_port'].widget.attrs['disabled'] = True
  829. class InterfaceTemplateBulkEditForm(BulkEditForm):
  830. pk = forms.ModelMultipleChoiceField(
  831. queryset=InterfaceTemplate.objects.all(),
  832. widget=forms.MultipleHiddenInput()
  833. )
  834. label = forms.CharField(
  835. max_length=64,
  836. required=False
  837. )
  838. type = forms.ChoiceField(
  839. choices=add_blank_choice(InterfaceTypeChoices),
  840. required=False,
  841. widget=StaticSelect()
  842. )
  843. mgmt_only = forms.NullBooleanField(
  844. required=False,
  845. widget=BulkEditNullBooleanSelect,
  846. label=_('Management only')
  847. )
  848. description = forms.CharField(
  849. required=False
  850. )
  851. poe_mode = forms.ChoiceField(
  852. choices=add_blank_choice(InterfacePoEModeChoices),
  853. required=False,
  854. initial='',
  855. widget=StaticSelect(),
  856. label=_('PoE mode')
  857. )
  858. poe_type = forms.ChoiceField(
  859. choices=add_blank_choice(InterfacePoETypeChoices),
  860. required=False,
  861. initial='',
  862. widget=StaticSelect(),
  863. label=_('PoE type')
  864. )
  865. nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
  866. class FrontPortTemplateBulkEditForm(BulkEditForm):
  867. pk = forms.ModelMultipleChoiceField(
  868. queryset=FrontPortTemplate.objects.all(),
  869. widget=forms.MultipleHiddenInput()
  870. )
  871. label = forms.CharField(
  872. max_length=64,
  873. required=False
  874. )
  875. type = forms.ChoiceField(
  876. choices=add_blank_choice(PortTypeChoices),
  877. required=False,
  878. widget=StaticSelect()
  879. )
  880. color = ColorField(
  881. required=False
  882. )
  883. description = forms.CharField(
  884. required=False
  885. )
  886. nullable_fields = ('description',)
  887. class RearPortTemplateBulkEditForm(BulkEditForm):
  888. pk = forms.ModelMultipleChoiceField(
  889. queryset=RearPortTemplate.objects.all(),
  890. widget=forms.MultipleHiddenInput()
  891. )
  892. label = forms.CharField(
  893. max_length=64,
  894. required=False
  895. )
  896. type = forms.ChoiceField(
  897. choices=add_blank_choice(PortTypeChoices),
  898. required=False,
  899. widget=StaticSelect()
  900. )
  901. color = ColorField(
  902. required=False
  903. )
  904. description = forms.CharField(
  905. required=False
  906. )
  907. nullable_fields = ('description',)
  908. class ModuleBayTemplateBulkEditForm(BulkEditForm):
  909. pk = forms.ModelMultipleChoiceField(
  910. queryset=ModuleBayTemplate.objects.all(),
  911. widget=forms.MultipleHiddenInput()
  912. )
  913. label = forms.CharField(
  914. max_length=64,
  915. required=False
  916. )
  917. description = forms.CharField(
  918. required=False
  919. )
  920. nullable_fields = ('label', 'position', 'description')
  921. class DeviceBayTemplateBulkEditForm(BulkEditForm):
  922. pk = forms.ModelMultipleChoiceField(
  923. queryset=DeviceBayTemplate.objects.all(),
  924. widget=forms.MultipleHiddenInput()
  925. )
  926. label = forms.CharField(
  927. max_length=64,
  928. required=False
  929. )
  930. description = forms.CharField(
  931. required=False
  932. )
  933. nullable_fields = ('label', 'description')
  934. class InventoryItemTemplateBulkEditForm(BulkEditForm):
  935. pk = forms.ModelMultipleChoiceField(
  936. queryset=InventoryItemTemplate.objects.all(),
  937. widget=forms.MultipleHiddenInput()
  938. )
  939. label = forms.CharField(
  940. max_length=64,
  941. required=False
  942. )
  943. description = forms.CharField(
  944. required=False
  945. )
  946. role = DynamicModelChoiceField(
  947. queryset=InventoryItemRole.objects.all(),
  948. required=False
  949. )
  950. manufacturer = DynamicModelChoiceField(
  951. queryset=Manufacturer.objects.all(),
  952. required=False
  953. )
  954. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  955. #
  956. # Device components
  957. #
  958. class ComponentBulkEditForm(NetBoxModelBulkEditForm):
  959. device = forms.ModelChoiceField(
  960. queryset=Device.objects.all(),
  961. required=False,
  962. disabled=True,
  963. widget=forms.HiddenInput()
  964. )
  965. module = forms.ModelChoiceField(
  966. queryset=Module.objects.all(),
  967. required=False
  968. )
  969. def __init__(self, *args, **kwargs):
  970. super().__init__(*args, **kwargs)
  971. # Limit module queryset to Modules which belong to the parent Device
  972. if 'device' in self.initial:
  973. device = Device.objects.filter(pk=self.initial['device']).first()
  974. self.fields['module'].queryset = Module.objects.filter(device=device)
  975. else:
  976. self.fields['module'].choices = ()
  977. self.fields['module'].widget.attrs['disabled'] = True
  978. class ConsolePortBulkEditForm(
  979. form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  980. ComponentBulkEditForm
  981. ):
  982. mark_connected = forms.NullBooleanField(
  983. required=False,
  984. widget=BulkEditNullBooleanSelect
  985. )
  986. model = ConsolePort
  987. fieldsets = (
  988. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  989. )
  990. nullable_fields = ('module', 'label', 'description')
  991. class ConsoleServerPortBulkEditForm(
  992. form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  993. ComponentBulkEditForm
  994. ):
  995. mark_connected = forms.NullBooleanField(
  996. required=False,
  997. widget=BulkEditNullBooleanSelect
  998. )
  999. model = ConsoleServerPort
  1000. fieldsets = (
  1001. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  1002. )
  1003. nullable_fields = ('module', 'label', 'description')
  1004. class PowerPortBulkEditForm(
  1005. form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
  1006. ComponentBulkEditForm
  1007. ):
  1008. mark_connected = forms.NullBooleanField(
  1009. required=False,
  1010. widget=BulkEditNullBooleanSelect
  1011. )
  1012. model = PowerPort
  1013. fieldsets = (
  1014. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  1015. ('Power', ('maximum_draw', 'allocated_draw')),
  1016. )
  1017. nullable_fields = ('module', 'label', 'description')
  1018. class PowerOutletBulkEditForm(
  1019. form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
  1020. ComponentBulkEditForm
  1021. ):
  1022. mark_connected = forms.NullBooleanField(
  1023. required=False,
  1024. widget=BulkEditNullBooleanSelect
  1025. )
  1026. model = PowerOutlet
  1027. fieldsets = (
  1028. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  1029. ('Power', ('feed_leg', 'power_port')),
  1030. )
  1031. nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
  1032. def __init__(self, *args, **kwargs):
  1033. super().__init__(*args, **kwargs)
  1034. # Limit power_port queryset to PowerPorts which belong to the parent Device
  1035. if 'device' in self.initial:
  1036. device = Device.objects.filter(pk=self.initial['device']).first()
  1037. self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
  1038. else:
  1039. self.fields['power_port'].choices = ()
  1040. self.fields['power_port'].widget.attrs['disabled'] = True
  1041. class InterfaceBulkEditForm(
  1042. form_from_model(Interface, [
  1043. 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
  1044. 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
  1045. 'tx_power',
  1046. ]),
  1047. ComponentBulkEditForm
  1048. ):
  1049. enabled = forms.NullBooleanField(
  1050. required=False,
  1051. widget=BulkEditNullBooleanSelect
  1052. )
  1053. parent = DynamicModelChoiceField(
  1054. queryset=Interface.objects.all(),
  1055. required=False
  1056. )
  1057. bridge = DynamicModelChoiceField(
  1058. queryset=Interface.objects.all(),
  1059. required=False
  1060. )
  1061. lag = DynamicModelChoiceField(
  1062. queryset=Interface.objects.all(),
  1063. required=False,
  1064. query_params={
  1065. 'type': 'lag',
  1066. },
  1067. label=_('LAG')
  1068. )
  1069. speed = forms.IntegerField(
  1070. required=False,
  1071. widget=SelectSpeedWidget(),
  1072. label=_('Speed')
  1073. )
  1074. mgmt_only = forms.NullBooleanField(
  1075. required=False,
  1076. widget=BulkEditNullBooleanSelect,
  1077. label=_('Management only')
  1078. )
  1079. poe_mode = forms.ChoiceField(
  1080. choices=add_blank_choice(InterfacePoEModeChoices),
  1081. required=False,
  1082. initial='',
  1083. widget=StaticSelect(),
  1084. label=_('PoE mode')
  1085. )
  1086. poe_type = forms.ChoiceField(
  1087. choices=add_blank_choice(InterfacePoETypeChoices),
  1088. required=False,
  1089. initial='',
  1090. widget=StaticSelect(),
  1091. label=_('PoE type')
  1092. )
  1093. mark_connected = forms.NullBooleanField(
  1094. required=False,
  1095. widget=BulkEditNullBooleanSelect
  1096. )
  1097. mode = forms.ChoiceField(
  1098. choices=add_blank_choice(InterfaceModeChoices),
  1099. required=False,
  1100. initial='',
  1101. widget=StaticSelect()
  1102. )
  1103. vlan_group = DynamicModelChoiceField(
  1104. queryset=VLANGroup.objects.all(),
  1105. required=False,
  1106. label=_('VLAN group')
  1107. )
  1108. untagged_vlan = DynamicModelChoiceField(
  1109. queryset=VLAN.objects.all(),
  1110. required=False,
  1111. query_params={
  1112. 'group_id': '$vlan_group',
  1113. },
  1114. label=_('Untagged VLAN')
  1115. )
  1116. tagged_vlans = DynamicModelMultipleChoiceField(
  1117. queryset=VLAN.objects.all(),
  1118. required=False,
  1119. query_params={
  1120. 'group_id': '$vlan_group',
  1121. },
  1122. label=_('Tagged VLANs')
  1123. )
  1124. vrf = DynamicModelChoiceField(
  1125. queryset=VRF.objects.all(),
  1126. required=False,
  1127. label=_('VRF')
  1128. )
  1129. model = Interface
  1130. fieldsets = (
  1131. (None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
  1132. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1133. ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1134. ('PoE', ('poe_mode', 'poe_type')),
  1135. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1136. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1137. ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')),
  1138. )
  1139. nullable_fields = (
  1140. 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description',
  1141. 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1142. 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf',
  1143. )
  1144. def __init__(self, *args, **kwargs):
  1145. super().__init__(*args, **kwargs)
  1146. if 'device' in self.initial:
  1147. device = Device.objects.filter(pk=self.initial['device']).first()
  1148. # Restrict parent/bridge/LAG interface assignment by device
  1149. self.fields['parent'].widget.add_query_param('device_id', device.pk)
  1150. self.fields['bridge'].widget.add_query_param('device_id', device.pk)
  1151. self.fields['lag'].widget.add_query_param('device_id', device.pk)
  1152. # Limit VLAN choices by device
  1153. self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
  1154. self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
  1155. else:
  1156. # See #4523
  1157. if 'pk' in self.initial:
  1158. site = None
  1159. interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
  1160. # Check interface sites. First interface should set site, further interfaces will either continue the
  1161. # loop or reset back to no site and break the loop.
  1162. for interface in interfaces:
  1163. if site is None:
  1164. site = interface.device.site
  1165. elif interface.device.site is not site:
  1166. site = None
  1167. break
  1168. if site is not None:
  1169. self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
  1170. self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
  1171. self.fields['parent'].choices = ()
  1172. self.fields['parent'].widget.attrs['disabled'] = True
  1173. self.fields['bridge'].choices = ()
  1174. self.fields['bridge'].widget.attrs['disabled'] = True
  1175. self.fields['lag'].choices = ()
  1176. self.fields['lag'].widget.attrs['disabled'] = True
  1177. def clean(self):
  1178. super().clean()
  1179. if not self.cleaned_data['mode']:
  1180. if self.cleaned_data['untagged_vlan']:
  1181. raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
  1182. elif self.cleaned_data['tagged_vlans']:
  1183. raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
  1184. # Untagged interfaces cannot be assigned tagged VLANs
  1185. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
  1186. raise forms.ValidationError({
  1187. 'mode': "An access interface cannot have tagged VLANs assigned."
  1188. })
  1189. # Remove all tagged VLAN assignments from "tagged all" interfaces
  1190. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
  1191. self.cleaned_data['tagged_vlans'] = []
  1192. class FrontPortBulkEditForm(
  1193. form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1194. ComponentBulkEditForm
  1195. ):
  1196. model = FrontPort
  1197. fieldsets = (
  1198. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1199. )
  1200. nullable_fields = ('module', 'label', 'description')
  1201. class RearPortBulkEditForm(
  1202. form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1203. ComponentBulkEditForm
  1204. ):
  1205. model = RearPort
  1206. fieldsets = (
  1207. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1208. )
  1209. nullable_fields = ('module', 'label', 'description')
  1210. class ModuleBayBulkEditForm(
  1211. form_from_model(ModuleBay, ['label', 'position', 'description']),
  1212. NetBoxModelBulkEditForm
  1213. ):
  1214. model = ModuleBay
  1215. fieldsets = (
  1216. (None, ('label', 'position', 'description')),
  1217. )
  1218. nullable_fields = ('label', 'position', 'description')
  1219. class DeviceBayBulkEditForm(
  1220. form_from_model(DeviceBay, ['label', 'description']),
  1221. NetBoxModelBulkEditForm
  1222. ):
  1223. model = DeviceBay
  1224. fieldsets = (
  1225. (None, ('label', 'description')),
  1226. )
  1227. nullable_fields = ('label', 'description')
  1228. class InventoryItemBulkEditForm(
  1229. form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
  1230. NetBoxModelBulkEditForm
  1231. ):
  1232. device = DynamicModelChoiceField(
  1233. queryset=Device.objects.all(),
  1234. required=False
  1235. )
  1236. role = DynamicModelChoiceField(
  1237. queryset=InventoryItemRole.objects.all(),
  1238. required=False
  1239. )
  1240. manufacturer = DynamicModelChoiceField(
  1241. queryset=Manufacturer.objects.all(),
  1242. required=False
  1243. )
  1244. model = InventoryItem
  1245. fieldsets = (
  1246. (None, ('device', 'label', 'role', 'manufacturer', 'part_id', 'description')),
  1247. )
  1248. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1249. #
  1250. # Device component roles
  1251. #
  1252. class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
  1253. color = ColorField(
  1254. required=False
  1255. )
  1256. description = forms.CharField(
  1257. max_length=200,
  1258. required=False
  1259. )
  1260. model = InventoryItemRole
  1261. fieldsets = (
  1262. (None, ('color', 'description')),
  1263. )
  1264. nullable_fields = ('color', 'description')
  1265. class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
  1266. device = DynamicModelChoiceField(
  1267. queryset=Device.objects.all(),
  1268. required=False
  1269. )
  1270. status = forms.ChoiceField(
  1271. required=False,
  1272. choices=add_blank_choice(VirtualDeviceContextStatusChoices),
  1273. widget=StaticSelect()
  1274. )
  1275. tenant = DynamicModelChoiceField(
  1276. queryset=Tenant.objects.all(),
  1277. required=False
  1278. )
  1279. model = VirtualDeviceContext
  1280. fieldsets = (
  1281. (None, ('device', 'status', 'tenant')),
  1282. )
  1283. nullable_fields = ('device', 'tenant', )