bulk_edit.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421
  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. speed = forms.IntegerField(
  1069. required=False,
  1070. widget=SelectSpeedWidget(),
  1071. label=_('Speed')
  1072. )
  1073. mgmt_only = forms.NullBooleanField(
  1074. required=False,
  1075. widget=BulkEditNullBooleanSelect,
  1076. label=_('Management only')
  1077. )
  1078. poe_mode = forms.ChoiceField(
  1079. choices=add_blank_choice(InterfacePoEModeChoices),
  1080. required=False,
  1081. initial='',
  1082. widget=StaticSelect(),
  1083. label=_('PoE mode')
  1084. )
  1085. poe_type = forms.ChoiceField(
  1086. choices=add_blank_choice(InterfacePoETypeChoices),
  1087. required=False,
  1088. initial='',
  1089. widget=StaticSelect(),
  1090. label=_('PoE type')
  1091. )
  1092. mark_connected = forms.NullBooleanField(
  1093. required=False,
  1094. widget=BulkEditNullBooleanSelect
  1095. )
  1096. mode = forms.ChoiceField(
  1097. choices=add_blank_choice(InterfaceModeChoices),
  1098. required=False,
  1099. initial='',
  1100. widget=StaticSelect()
  1101. )
  1102. vlan_group = DynamicModelChoiceField(
  1103. queryset=VLANGroup.objects.all(),
  1104. required=False,
  1105. label=_('VLAN group')
  1106. )
  1107. untagged_vlan = DynamicModelChoiceField(
  1108. queryset=VLAN.objects.all(),
  1109. required=False,
  1110. query_params={
  1111. 'group_id': '$vlan_group',
  1112. },
  1113. label=_('Untagged VLAN')
  1114. )
  1115. tagged_vlans = DynamicModelMultipleChoiceField(
  1116. queryset=VLAN.objects.all(),
  1117. required=False,
  1118. query_params={
  1119. 'group_id': '$vlan_group',
  1120. },
  1121. label=_('Tagged VLANs')
  1122. )
  1123. vrf = DynamicModelChoiceField(
  1124. queryset=VRF.objects.all(),
  1125. required=False,
  1126. label=_('VRF')
  1127. )
  1128. model = Interface
  1129. fieldsets = (
  1130. (None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
  1131. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1132. ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1133. ('PoE', ('poe_mode', 'poe_type')),
  1134. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1135. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1136. ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')),
  1137. )
  1138. nullable_fields = (
  1139. 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description',
  1140. 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1141. 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf',
  1142. )
  1143. def __init__(self, *args, **kwargs):
  1144. super().__init__(*args, **kwargs)
  1145. if 'device' in self.initial:
  1146. device = Device.objects.filter(pk=self.initial['device']).first()
  1147. # Restrict parent/bridge/LAG interface assignment by device
  1148. self.fields['parent'].widget.add_query_param('device_id', device.pk)
  1149. self.fields['bridge'].widget.add_query_param('device_id', device.pk)
  1150. self.fields['lag'].widget.add_query_param('device_id', device.pk)
  1151. # Limit VLAN choices by device
  1152. self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
  1153. self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
  1154. else:
  1155. # See #4523
  1156. if 'pk' in self.initial:
  1157. site = None
  1158. interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
  1159. # Check interface sites. First interface should set site, further interfaces will either continue the
  1160. # loop or reset back to no site and break the loop.
  1161. for interface in interfaces:
  1162. if site is None:
  1163. site = interface.device.site
  1164. elif interface.device.site is not site:
  1165. site = None
  1166. break
  1167. if site is not None:
  1168. self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
  1169. self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
  1170. self.fields['parent'].choices = ()
  1171. self.fields['parent'].widget.attrs['disabled'] = True
  1172. self.fields['bridge'].choices = ()
  1173. self.fields['bridge'].widget.attrs['disabled'] = True
  1174. self.fields['lag'].choices = ()
  1175. self.fields['lag'].widget.attrs['disabled'] = True
  1176. def clean(self):
  1177. super().clean()
  1178. if not self.cleaned_data['mode']:
  1179. if self.cleaned_data['untagged_vlan']:
  1180. raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
  1181. elif self.cleaned_data['tagged_vlans']:
  1182. raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
  1183. # Untagged interfaces cannot be assigned tagged VLANs
  1184. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
  1185. raise forms.ValidationError({
  1186. 'mode': "An access interface cannot have tagged VLANs assigned."
  1187. })
  1188. # Remove all tagged VLAN assignments from "tagged all" interfaces
  1189. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
  1190. self.cleaned_data['tagged_vlans'] = []
  1191. class FrontPortBulkEditForm(
  1192. form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1193. ComponentBulkEditForm
  1194. ):
  1195. model = FrontPort
  1196. fieldsets = (
  1197. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1198. )
  1199. nullable_fields = ('module', 'label', 'description', 'color')
  1200. class RearPortBulkEditForm(
  1201. form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1202. ComponentBulkEditForm
  1203. ):
  1204. model = RearPort
  1205. fieldsets = (
  1206. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1207. )
  1208. nullable_fields = ('module', 'label', 'description', 'color')
  1209. class ModuleBayBulkEditForm(
  1210. form_from_model(ModuleBay, ['label', 'position', 'description']),
  1211. NetBoxModelBulkEditForm
  1212. ):
  1213. model = ModuleBay
  1214. fieldsets = (
  1215. (None, ('label', 'position', 'description')),
  1216. )
  1217. nullable_fields = ('label', 'position', 'description')
  1218. class DeviceBayBulkEditForm(
  1219. form_from_model(DeviceBay, ['label', 'description']),
  1220. NetBoxModelBulkEditForm
  1221. ):
  1222. model = DeviceBay
  1223. fieldsets = (
  1224. (None, ('label', 'description')),
  1225. )
  1226. nullable_fields = ('label', 'description')
  1227. class InventoryItemBulkEditForm(
  1228. form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
  1229. NetBoxModelBulkEditForm
  1230. ):
  1231. device = DynamicModelChoiceField(
  1232. queryset=Device.objects.all(),
  1233. required=False
  1234. )
  1235. role = DynamicModelChoiceField(
  1236. queryset=InventoryItemRole.objects.all(),
  1237. required=False
  1238. )
  1239. manufacturer = DynamicModelChoiceField(
  1240. queryset=Manufacturer.objects.all(),
  1241. required=False
  1242. )
  1243. model = InventoryItem
  1244. fieldsets = (
  1245. (None, ('device', 'label', 'role', 'manufacturer', 'part_id', 'description')),
  1246. )
  1247. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1248. #
  1249. # Device component roles
  1250. #
  1251. class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
  1252. color = ColorField(
  1253. required=False
  1254. )
  1255. description = forms.CharField(
  1256. max_length=200,
  1257. required=False
  1258. )
  1259. model = InventoryItemRole
  1260. fieldsets = (
  1261. (None, ('color', 'description')),
  1262. )
  1263. nullable_fields = ('color', 'description')
  1264. class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
  1265. device = DynamicModelChoiceField(
  1266. queryset=Device.objects.all(),
  1267. required=False
  1268. )
  1269. status = forms.ChoiceField(
  1270. required=False,
  1271. choices=add_blank_choice(VirtualDeviceContextStatusChoices),
  1272. widget=StaticSelect()
  1273. )
  1274. tenant = DynamicModelChoiceField(
  1275. queryset=Tenant.objects.all(),
  1276. required=False
  1277. )
  1278. model = VirtualDeviceContext
  1279. fieldsets = (
  1280. (None, ('device', 'status', 'tenant')),
  1281. )
  1282. nullable_fields = ('device', 'tenant', )