bulk_edit.py 40 KB

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