bulk_edit.py 33 KB

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