bulk_edit.py 40 KB

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