bulk_edit.py 37 KB

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