bulk_edit.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409
  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, SelectSpeedWidget
  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. config_template = DynamicModelChoiceField(
  437. queryset=ConfigTemplate.objects.all(),
  438. required=False
  439. )
  440. description = forms.CharField(
  441. max_length=200,
  442. required=False
  443. )
  444. model = Platform
  445. fieldsets = (
  446. (None, ('manufacturer', 'config_template', 'description')),
  447. )
  448. nullable_fields = ('manufacturer', 'config_template', 'description')
  449. class DeviceBulkEditForm(NetBoxModelBulkEditForm):
  450. manufacturer = DynamicModelChoiceField(
  451. queryset=Manufacturer.objects.all(),
  452. required=False
  453. )
  454. device_type = DynamicModelChoiceField(
  455. queryset=DeviceType.objects.all(),
  456. required=False,
  457. query_params={
  458. 'manufacturer_id': '$manufacturer'
  459. }
  460. )
  461. device_role = DynamicModelChoiceField(
  462. queryset=DeviceRole.objects.all(),
  463. required=False
  464. )
  465. site = DynamicModelChoiceField(
  466. queryset=Site.objects.all(),
  467. required=False
  468. )
  469. location = DynamicModelChoiceField(
  470. queryset=Location.objects.all(),
  471. required=False,
  472. query_params={
  473. 'site_id': '$site'
  474. }
  475. )
  476. tenant = DynamicModelChoiceField(
  477. queryset=Tenant.objects.all(),
  478. required=False
  479. )
  480. platform = DynamicModelChoiceField(
  481. queryset=Platform.objects.all(),
  482. required=False
  483. )
  484. status = forms.ChoiceField(
  485. choices=add_blank_choice(DeviceStatusChoices),
  486. required=False
  487. )
  488. airflow = forms.ChoiceField(
  489. choices=add_blank_choice(DeviceAirflowChoices),
  490. required=False
  491. )
  492. serial = forms.CharField(
  493. max_length=50,
  494. required=False,
  495. label=_('Serial Number')
  496. )
  497. description = forms.CharField(
  498. max_length=200,
  499. required=False
  500. )
  501. config_template = DynamicModelChoiceField(
  502. queryset=ConfigTemplate.objects.all(),
  503. required=False
  504. )
  505. comments = CommentField(
  506. label='Comments'
  507. )
  508. model = Device
  509. fieldsets = (
  510. ('Device', ('device_role', 'status', 'tenant', 'platform', 'description')),
  511. ('Location', ('site', 'location')),
  512. ('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
  513. ('Configuration', ('config_template',)),
  514. )
  515. nullable_fields = (
  516. 'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
  517. )
  518. class ModuleBulkEditForm(NetBoxModelBulkEditForm):
  519. manufacturer = DynamicModelChoiceField(
  520. queryset=Manufacturer.objects.all(),
  521. required=False
  522. )
  523. module_type = DynamicModelChoiceField(
  524. queryset=ModuleType.objects.all(),
  525. required=False,
  526. query_params={
  527. 'manufacturer_id': '$manufacturer'
  528. }
  529. )
  530. status = forms.ChoiceField(
  531. choices=add_blank_choice(ModuleStatusChoices),
  532. required=False,
  533. initial=''
  534. )
  535. serial = forms.CharField(
  536. max_length=50,
  537. required=False,
  538. label=_('Serial Number')
  539. )
  540. description = forms.CharField(
  541. max_length=200,
  542. required=False
  543. )
  544. comments = CommentField(
  545. label='Comments'
  546. )
  547. model = Module
  548. fieldsets = (
  549. (None, ('manufacturer', 'module_type', 'status', 'serial', 'description')),
  550. )
  551. nullable_fields = ('serial', 'description', 'comments')
  552. class CableBulkEditForm(NetBoxModelBulkEditForm):
  553. type = forms.ChoiceField(
  554. choices=add_blank_choice(CableTypeChoices),
  555. required=False,
  556. initial=''
  557. )
  558. status = forms.ChoiceField(
  559. choices=add_blank_choice(LinkStatusChoices),
  560. required=False,
  561. initial=''
  562. )
  563. tenant = DynamicModelChoiceField(
  564. queryset=Tenant.objects.all(),
  565. required=False
  566. )
  567. label = forms.CharField(
  568. max_length=100,
  569. required=False
  570. )
  571. color = ColorField(
  572. required=False
  573. )
  574. length = forms.DecimalField(
  575. min_value=0,
  576. required=False
  577. )
  578. length_unit = forms.ChoiceField(
  579. choices=add_blank_choice(CableLengthUnitChoices),
  580. required=False,
  581. initial=''
  582. )
  583. description = forms.CharField(
  584. max_length=200,
  585. required=False
  586. )
  587. comments = CommentField(
  588. label='Comments'
  589. )
  590. model = Cable
  591. fieldsets = (
  592. (None, ('type', 'status', 'tenant', 'label', 'description')),
  593. ('Attributes', ('color', 'length', 'length_unit')),
  594. )
  595. nullable_fields = (
  596. 'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
  597. )
  598. class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
  599. domain = forms.CharField(
  600. max_length=30,
  601. required=False
  602. )
  603. description = forms.CharField(
  604. max_length=200,
  605. required=False
  606. )
  607. comments = CommentField(
  608. label='Comments'
  609. )
  610. model = VirtualChassis
  611. fieldsets = (
  612. (None, ('domain', 'description')),
  613. )
  614. nullable_fields = ('domain', 'description', 'comments')
  615. class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
  616. region = DynamicModelChoiceField(
  617. queryset=Region.objects.all(),
  618. required=False,
  619. initial_params={
  620. 'sites': '$site'
  621. }
  622. )
  623. site_group = DynamicModelChoiceField(
  624. queryset=SiteGroup.objects.all(),
  625. required=False,
  626. initial_params={
  627. 'sites': '$site'
  628. }
  629. )
  630. site = DynamicModelChoiceField(
  631. queryset=Site.objects.all(),
  632. required=False,
  633. query_params={
  634. 'region_id': '$region',
  635. 'group_id': '$site_group',
  636. }
  637. )
  638. location = DynamicModelChoiceField(
  639. queryset=Location.objects.all(),
  640. required=False,
  641. query_params={
  642. 'site_id': '$site'
  643. }
  644. )
  645. description = forms.CharField(
  646. max_length=200,
  647. required=False
  648. )
  649. comments = CommentField(
  650. label='Comments'
  651. )
  652. model = PowerPanel
  653. fieldsets = (
  654. (None, ('region', 'site_group', 'site', 'location', 'description')),
  655. )
  656. nullable_fields = ('location', 'description', 'comments')
  657. class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
  658. power_panel = DynamicModelChoiceField(
  659. queryset=PowerPanel.objects.all(),
  660. required=False
  661. )
  662. rack = DynamicModelChoiceField(
  663. queryset=Rack.objects.all(),
  664. required=False,
  665. )
  666. status = forms.ChoiceField(
  667. choices=add_blank_choice(PowerFeedStatusChoices),
  668. required=False,
  669. initial=''
  670. )
  671. type = forms.ChoiceField(
  672. choices=add_blank_choice(PowerFeedTypeChoices),
  673. required=False,
  674. initial=''
  675. )
  676. supply = forms.ChoiceField(
  677. choices=add_blank_choice(PowerFeedSupplyChoices),
  678. required=False,
  679. initial=''
  680. )
  681. phase = forms.ChoiceField(
  682. choices=add_blank_choice(PowerFeedPhaseChoices),
  683. required=False,
  684. initial=''
  685. )
  686. voltage = forms.IntegerField(
  687. required=False
  688. )
  689. amperage = forms.IntegerField(
  690. required=False
  691. )
  692. max_utilization = forms.IntegerField(
  693. required=False
  694. )
  695. mark_connected = forms.NullBooleanField(
  696. required=False,
  697. widget=BulkEditNullBooleanSelect
  698. )
  699. description = forms.CharField(
  700. max_length=200,
  701. required=False
  702. )
  703. comments = CommentField(
  704. label=_('Comments')
  705. )
  706. model = PowerFeed
  707. fieldsets = (
  708. (None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description')),
  709. ('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
  710. )
  711. nullable_fields = ('location', 'description', 'comments')
  712. #
  713. # Device component templates
  714. #
  715. class ConsolePortTemplateBulkEditForm(BulkEditForm):
  716. pk = forms.ModelMultipleChoiceField(
  717. queryset=ConsolePortTemplate.objects.all(),
  718. widget=forms.MultipleHiddenInput()
  719. )
  720. label = forms.CharField(
  721. max_length=64,
  722. required=False
  723. )
  724. type = forms.ChoiceField(
  725. choices=add_blank_choice(ConsolePortTypeChoices),
  726. required=False
  727. )
  728. nullable_fields = ('label', 'type', 'description')
  729. class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
  730. pk = forms.ModelMultipleChoiceField(
  731. queryset=ConsoleServerPortTemplate.objects.all(),
  732. widget=forms.MultipleHiddenInput()
  733. )
  734. label = forms.CharField(
  735. max_length=64,
  736. required=False
  737. )
  738. type = forms.ChoiceField(
  739. choices=add_blank_choice(ConsolePortTypeChoices),
  740. required=False
  741. )
  742. description = forms.CharField(
  743. required=False
  744. )
  745. nullable_fields = ('label', 'type', 'description')
  746. class PowerPortTemplateBulkEditForm(BulkEditForm):
  747. pk = forms.ModelMultipleChoiceField(
  748. queryset=PowerPortTemplate.objects.all(),
  749. widget=forms.MultipleHiddenInput()
  750. )
  751. label = forms.CharField(
  752. max_length=64,
  753. required=False
  754. )
  755. type = forms.ChoiceField(
  756. choices=add_blank_choice(PowerPortTypeChoices),
  757. required=False
  758. )
  759. maximum_draw = forms.IntegerField(
  760. min_value=1,
  761. required=False,
  762. help_text=_("Maximum power draw (watts)")
  763. )
  764. allocated_draw = forms.IntegerField(
  765. min_value=1,
  766. required=False,
  767. help_text=_("Allocated power draw (watts)")
  768. )
  769. description = forms.CharField(
  770. required=False
  771. )
  772. nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
  773. class PowerOutletTemplateBulkEditForm(BulkEditForm):
  774. pk = forms.ModelMultipleChoiceField(
  775. queryset=PowerOutletTemplate.objects.all(),
  776. widget=forms.MultipleHiddenInput()
  777. )
  778. device_type = forms.ModelChoiceField(
  779. queryset=DeviceType.objects.all(),
  780. required=False,
  781. disabled=True,
  782. widget=forms.HiddenInput()
  783. )
  784. label = forms.CharField(
  785. max_length=64,
  786. required=False
  787. )
  788. type = forms.ChoiceField(
  789. choices=add_blank_choice(PowerOutletTypeChoices),
  790. required=False
  791. )
  792. power_port = forms.ModelChoiceField(
  793. queryset=PowerPortTemplate.objects.all(),
  794. required=False
  795. )
  796. feed_leg = forms.ChoiceField(
  797. choices=add_blank_choice(PowerOutletFeedLegChoices),
  798. required=False
  799. )
  800. description = forms.CharField(
  801. required=False
  802. )
  803. nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
  804. def __init__(self, *args, **kwargs):
  805. super().__init__(*args, **kwargs)
  806. # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
  807. if 'device_type' in self.initial:
  808. device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
  809. self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
  810. else:
  811. self.fields['power_port'].choices = ()
  812. self.fields['power_port'].widget.attrs['disabled'] = True
  813. class InterfaceTemplateBulkEditForm(BulkEditForm):
  814. pk = forms.ModelMultipleChoiceField(
  815. queryset=InterfaceTemplate.objects.all(),
  816. widget=forms.MultipleHiddenInput()
  817. )
  818. label = forms.CharField(
  819. max_length=64,
  820. required=False
  821. )
  822. type = forms.ChoiceField(
  823. choices=add_blank_choice(InterfaceTypeChoices),
  824. required=False
  825. )
  826. enabled = forms.NullBooleanField(
  827. required=False,
  828. widget=BulkEditNullBooleanSelect
  829. )
  830. mgmt_only = forms.NullBooleanField(
  831. required=False,
  832. widget=BulkEditNullBooleanSelect,
  833. label=_('Management only')
  834. )
  835. description = forms.CharField(
  836. required=False
  837. )
  838. poe_mode = forms.ChoiceField(
  839. choices=add_blank_choice(InterfacePoEModeChoices),
  840. required=False,
  841. initial='',
  842. label=_('PoE mode')
  843. )
  844. poe_type = forms.ChoiceField(
  845. choices=add_blank_choice(InterfacePoETypeChoices),
  846. required=False,
  847. initial='',
  848. label=_('PoE type')
  849. )
  850. nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
  851. class FrontPortTemplateBulkEditForm(BulkEditForm):
  852. pk = forms.ModelMultipleChoiceField(
  853. queryset=FrontPortTemplate.objects.all(),
  854. widget=forms.MultipleHiddenInput()
  855. )
  856. label = forms.CharField(
  857. max_length=64,
  858. required=False
  859. )
  860. type = forms.ChoiceField(
  861. choices=add_blank_choice(PortTypeChoices),
  862. required=False
  863. )
  864. color = ColorField(
  865. required=False
  866. )
  867. description = forms.CharField(
  868. required=False
  869. )
  870. nullable_fields = ('description',)
  871. class RearPortTemplateBulkEditForm(BulkEditForm):
  872. pk = forms.ModelMultipleChoiceField(
  873. queryset=RearPortTemplate.objects.all(),
  874. widget=forms.MultipleHiddenInput()
  875. )
  876. label = forms.CharField(
  877. max_length=64,
  878. required=False
  879. )
  880. type = forms.ChoiceField(
  881. choices=add_blank_choice(PortTypeChoices),
  882. required=False
  883. )
  884. color = ColorField(
  885. required=False
  886. )
  887. description = forms.CharField(
  888. required=False
  889. )
  890. nullable_fields = ('description',)
  891. class ModuleBayTemplateBulkEditForm(BulkEditForm):
  892. pk = forms.ModelMultipleChoiceField(
  893. queryset=ModuleBayTemplate.objects.all(),
  894. widget=forms.MultipleHiddenInput()
  895. )
  896. label = forms.CharField(
  897. max_length=64,
  898. required=False
  899. )
  900. description = forms.CharField(
  901. required=False
  902. )
  903. nullable_fields = ('label', 'position', 'description')
  904. class DeviceBayTemplateBulkEditForm(BulkEditForm):
  905. pk = forms.ModelMultipleChoiceField(
  906. queryset=DeviceBayTemplate.objects.all(),
  907. widget=forms.MultipleHiddenInput()
  908. )
  909. label = forms.CharField(
  910. max_length=64,
  911. required=False
  912. )
  913. description = forms.CharField(
  914. required=False
  915. )
  916. nullable_fields = ('label', 'description')
  917. class InventoryItemTemplateBulkEditForm(BulkEditForm):
  918. pk = forms.ModelMultipleChoiceField(
  919. queryset=InventoryItemTemplate.objects.all(),
  920. widget=forms.MultipleHiddenInput()
  921. )
  922. label = forms.CharField(
  923. max_length=64,
  924. required=False
  925. )
  926. description = forms.CharField(
  927. required=False
  928. )
  929. role = DynamicModelChoiceField(
  930. queryset=InventoryItemRole.objects.all(),
  931. required=False
  932. )
  933. manufacturer = DynamicModelChoiceField(
  934. queryset=Manufacturer.objects.all(),
  935. required=False
  936. )
  937. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  938. #
  939. # Device components
  940. #
  941. class ComponentBulkEditForm(NetBoxModelBulkEditForm):
  942. device = forms.ModelChoiceField(
  943. queryset=Device.objects.all(),
  944. required=False,
  945. disabled=True,
  946. widget=forms.HiddenInput()
  947. )
  948. module = forms.ModelChoiceField(
  949. queryset=Module.objects.all(),
  950. required=False
  951. )
  952. def __init__(self, *args, **kwargs):
  953. super().__init__(*args, **kwargs)
  954. # Limit module queryset to Modules which belong to the parent Device
  955. if 'device' in self.initial:
  956. device = Device.objects.filter(pk=self.initial['device']).first()
  957. self.fields['module'].queryset = Module.objects.filter(device=device)
  958. else:
  959. self.fields['module'].choices = ()
  960. self.fields['module'].widget.attrs['disabled'] = True
  961. class ConsolePortBulkEditForm(
  962. form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  963. ComponentBulkEditForm
  964. ):
  965. mark_connected = forms.NullBooleanField(
  966. required=False,
  967. widget=BulkEditNullBooleanSelect
  968. )
  969. model = ConsolePort
  970. fieldsets = (
  971. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  972. )
  973. nullable_fields = ('module', 'label', 'description')
  974. class ConsoleServerPortBulkEditForm(
  975. form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  976. ComponentBulkEditForm
  977. ):
  978. mark_connected = forms.NullBooleanField(
  979. required=False,
  980. widget=BulkEditNullBooleanSelect
  981. )
  982. model = ConsoleServerPort
  983. fieldsets = (
  984. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  985. )
  986. nullable_fields = ('module', 'label', 'description')
  987. class PowerPortBulkEditForm(
  988. form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
  989. ComponentBulkEditForm
  990. ):
  991. mark_connected = forms.NullBooleanField(
  992. required=False,
  993. widget=BulkEditNullBooleanSelect
  994. )
  995. model = PowerPort
  996. fieldsets = (
  997. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  998. ('Power', ('maximum_draw', 'allocated_draw')),
  999. )
  1000. nullable_fields = ('module', 'label', 'description')
  1001. class PowerOutletBulkEditForm(
  1002. form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
  1003. ComponentBulkEditForm
  1004. ):
  1005. mark_connected = forms.NullBooleanField(
  1006. required=False,
  1007. widget=BulkEditNullBooleanSelect
  1008. )
  1009. model = PowerOutlet
  1010. fieldsets = (
  1011. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  1012. ('Power', ('feed_leg', 'power_port')),
  1013. )
  1014. nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
  1015. def __init__(self, *args, **kwargs):
  1016. super().__init__(*args, **kwargs)
  1017. # Limit power_port queryset to PowerPorts which belong to the parent Device
  1018. if 'device' in self.initial:
  1019. device = Device.objects.filter(pk=self.initial['device']).first()
  1020. self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
  1021. else:
  1022. self.fields['power_port'].choices = ()
  1023. self.fields['power_port'].widget.attrs['disabled'] = True
  1024. class InterfaceBulkEditForm(
  1025. form_from_model(Interface, [
  1026. 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
  1027. 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
  1028. 'tx_power',
  1029. ]),
  1030. ComponentBulkEditForm
  1031. ):
  1032. enabled = forms.NullBooleanField(
  1033. required=False,
  1034. widget=BulkEditNullBooleanSelect
  1035. )
  1036. parent = DynamicModelChoiceField(
  1037. queryset=Interface.objects.all(),
  1038. required=False
  1039. )
  1040. bridge = DynamicModelChoiceField(
  1041. queryset=Interface.objects.all(),
  1042. required=False
  1043. )
  1044. lag = DynamicModelChoiceField(
  1045. queryset=Interface.objects.all(),
  1046. required=False,
  1047. query_params={
  1048. 'type': 'lag',
  1049. },
  1050. label=_('LAG')
  1051. )
  1052. vdcs = DynamicModelMultipleChoiceField(
  1053. queryset=VirtualDeviceContext.objects.all(),
  1054. required=False,
  1055. label='Virtual Device Contexts',
  1056. query_params={
  1057. 'device_id': '$device',
  1058. }
  1059. )
  1060. speed = forms.IntegerField(
  1061. required=False,
  1062. widget=SelectSpeedWidget(),
  1063. label=_('Speed')
  1064. )
  1065. mgmt_only = forms.NullBooleanField(
  1066. required=False,
  1067. widget=BulkEditNullBooleanSelect,
  1068. label=_('Management only')
  1069. )
  1070. poe_mode = forms.ChoiceField(
  1071. choices=add_blank_choice(InterfacePoEModeChoices),
  1072. required=False,
  1073. initial='',
  1074. label=_('PoE mode')
  1075. )
  1076. poe_type = forms.ChoiceField(
  1077. choices=add_blank_choice(InterfacePoETypeChoices),
  1078. required=False,
  1079. initial='',
  1080. label=_('PoE type')
  1081. )
  1082. mark_connected = forms.NullBooleanField(
  1083. required=False,
  1084. widget=BulkEditNullBooleanSelect
  1085. )
  1086. mode = forms.ChoiceField(
  1087. choices=add_blank_choice(InterfaceModeChoices),
  1088. required=False,
  1089. initial=''
  1090. )
  1091. vlan_group = DynamicModelChoiceField(
  1092. queryset=VLANGroup.objects.all(),
  1093. required=False,
  1094. label=_('VLAN group')
  1095. )
  1096. untagged_vlan = DynamicModelChoiceField(
  1097. queryset=VLAN.objects.all(),
  1098. required=False,
  1099. query_params={
  1100. 'group_id': '$vlan_group',
  1101. },
  1102. label=_('Untagged VLAN')
  1103. )
  1104. tagged_vlans = DynamicModelMultipleChoiceField(
  1105. queryset=VLAN.objects.all(),
  1106. required=False,
  1107. query_params={
  1108. 'group_id': '$vlan_group',
  1109. },
  1110. label=_('Tagged VLANs')
  1111. )
  1112. vrf = DynamicModelChoiceField(
  1113. queryset=VRF.objects.all(),
  1114. required=False,
  1115. label=_('VRF')
  1116. )
  1117. model = Interface
  1118. fieldsets = (
  1119. (None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
  1120. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1121. ('Operation', ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1122. ('PoE', ('poe_mode', 'poe_type')),
  1123. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1124. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1125. ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')),
  1126. )
  1127. nullable_fields = (
  1128. 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description',
  1129. 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1130. 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf',
  1131. )
  1132. def __init__(self, *args, **kwargs):
  1133. super().__init__(*args, **kwargs)
  1134. if 'device' in self.initial:
  1135. device = Device.objects.filter(pk=self.initial['device']).first()
  1136. # Restrict parent/bridge/LAG interface assignment by device
  1137. self.fields['parent'].widget.add_query_param('device_id', device.pk)
  1138. self.fields['bridge'].widget.add_query_param('device_id', device.pk)
  1139. self.fields['lag'].widget.add_query_param('device_id', device.pk)
  1140. # Limit VLAN choices by device
  1141. self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
  1142. self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
  1143. else:
  1144. # See #4523
  1145. if 'pk' in self.initial:
  1146. site = None
  1147. interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
  1148. # Check interface sites. First interface should set site, further interfaces will either continue the
  1149. # loop or reset back to no site and break the loop.
  1150. for interface in interfaces:
  1151. if site is None:
  1152. site = interface.device.site
  1153. elif interface.device.site is not site:
  1154. site = None
  1155. break
  1156. if site is not None:
  1157. self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
  1158. self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
  1159. self.fields['parent'].choices = ()
  1160. self.fields['parent'].widget.attrs['disabled'] = True
  1161. self.fields['bridge'].choices = ()
  1162. self.fields['bridge'].widget.attrs['disabled'] = True
  1163. self.fields['lag'].choices = ()
  1164. self.fields['lag'].widget.attrs['disabled'] = True
  1165. def clean(self):
  1166. super().clean()
  1167. if not self.cleaned_data['mode']:
  1168. if self.cleaned_data['untagged_vlan']:
  1169. raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
  1170. elif self.cleaned_data['tagged_vlans']:
  1171. raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
  1172. # Untagged interfaces cannot be assigned tagged VLANs
  1173. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
  1174. raise forms.ValidationError({
  1175. 'mode': "An access interface cannot have tagged VLANs assigned."
  1176. })
  1177. # Remove all tagged VLAN assignments from "tagged all" interfaces
  1178. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
  1179. self.cleaned_data['tagged_vlans'] = []
  1180. class FrontPortBulkEditForm(
  1181. form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1182. ComponentBulkEditForm
  1183. ):
  1184. model = FrontPort
  1185. fieldsets = (
  1186. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1187. )
  1188. nullable_fields = ('module', 'label', 'description', 'color')
  1189. class RearPortBulkEditForm(
  1190. form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1191. ComponentBulkEditForm
  1192. ):
  1193. model = RearPort
  1194. fieldsets = (
  1195. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1196. )
  1197. nullable_fields = ('module', 'label', 'description', 'color')
  1198. class ModuleBayBulkEditForm(
  1199. form_from_model(ModuleBay, ['label', 'position', 'description']),
  1200. NetBoxModelBulkEditForm
  1201. ):
  1202. model = ModuleBay
  1203. fieldsets = (
  1204. (None, ('label', 'position', 'description')),
  1205. )
  1206. nullable_fields = ('label', 'position', 'description')
  1207. class DeviceBayBulkEditForm(
  1208. form_from_model(DeviceBay, ['label', 'description']),
  1209. NetBoxModelBulkEditForm
  1210. ):
  1211. model = DeviceBay
  1212. fieldsets = (
  1213. (None, ('label', 'description')),
  1214. )
  1215. nullable_fields = ('label', 'description')
  1216. class InventoryItemBulkEditForm(
  1217. form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
  1218. NetBoxModelBulkEditForm
  1219. ):
  1220. device = DynamicModelChoiceField(
  1221. queryset=Device.objects.all(),
  1222. required=False
  1223. )
  1224. role = DynamicModelChoiceField(
  1225. queryset=InventoryItemRole.objects.all(),
  1226. required=False
  1227. )
  1228. manufacturer = DynamicModelChoiceField(
  1229. queryset=Manufacturer.objects.all(),
  1230. required=False
  1231. )
  1232. model = InventoryItem
  1233. fieldsets = (
  1234. (None, ('device', 'label', 'role', 'manufacturer', 'part_id', 'description')),
  1235. )
  1236. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1237. #
  1238. # Device component roles
  1239. #
  1240. class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
  1241. color = ColorField(
  1242. required=False
  1243. )
  1244. description = forms.CharField(
  1245. max_length=200,
  1246. required=False
  1247. )
  1248. model = InventoryItemRole
  1249. fieldsets = (
  1250. (None, ('color', 'description')),
  1251. )
  1252. nullable_fields = ('color', 'description')
  1253. class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
  1254. device = DynamicModelChoiceField(
  1255. queryset=Device.objects.all(),
  1256. required=False
  1257. )
  1258. status = forms.ChoiceField(
  1259. required=False,
  1260. choices=add_blank_choice(VirtualDeviceContextStatusChoices)
  1261. )
  1262. tenant = DynamicModelChoiceField(
  1263. queryset=Tenant.objects.all(),
  1264. required=False
  1265. )
  1266. model = VirtualDeviceContext
  1267. fieldsets = (
  1268. (None, ('device', 'status', 'tenant')),
  1269. )
  1270. nullable_fields = ('device', 'tenant', )