bulk_edit.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  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. comments = CommentField(
  266. widget=SmallTextarea,
  267. label='Comments'
  268. )
  269. model = Rack
  270. fieldsets = (
  271. ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')),
  272. ('Location', ('region', 'site_group', 'site', 'location')),
  273. ('Hardware', ('type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit')),
  274. )
  275. nullable_fields = (
  276. 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
  277. )
  278. class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
  279. user = forms.ModelChoiceField(
  280. queryset=User.objects.order_by(
  281. 'username'
  282. ),
  283. required=False,
  284. widget=StaticSelect()
  285. )
  286. tenant = DynamicModelChoiceField(
  287. queryset=Tenant.objects.all(),
  288. required=False
  289. )
  290. description = forms.CharField(
  291. max_length=100,
  292. required=False
  293. )
  294. model = RackReservation
  295. fieldsets = (
  296. (None, ('user', 'tenant', 'description')),
  297. )
  298. class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
  299. description = forms.CharField(
  300. max_length=200,
  301. required=False
  302. )
  303. model = Manufacturer
  304. fieldsets = (
  305. (None, ('description',)),
  306. )
  307. nullable_fields = ('description',)
  308. class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
  309. manufacturer = DynamicModelChoiceField(
  310. queryset=Manufacturer.objects.all(),
  311. required=False
  312. )
  313. part_number = forms.CharField(
  314. required=False
  315. )
  316. u_height = forms.IntegerField(
  317. min_value=1,
  318. required=False
  319. )
  320. is_full_depth = forms.NullBooleanField(
  321. required=False,
  322. widget=BulkEditNullBooleanSelect(),
  323. label='Is full depth'
  324. )
  325. airflow = forms.ChoiceField(
  326. choices=add_blank_choice(DeviceAirflowChoices),
  327. required=False,
  328. widget=StaticSelect()
  329. )
  330. model = DeviceType
  331. fieldsets = (
  332. (None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')),
  333. )
  334. nullable_fields = ('part_number', 'airflow')
  335. class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
  336. manufacturer = DynamicModelChoiceField(
  337. queryset=Manufacturer.objects.all(),
  338. required=False
  339. )
  340. part_number = forms.CharField(
  341. required=False
  342. )
  343. model = ModuleType
  344. fieldsets = (
  345. (None, ('manufacturer', 'part_number')),
  346. )
  347. nullable_fields = ('part_number',)
  348. class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
  349. color = ColorField(
  350. required=False
  351. )
  352. vm_role = forms.NullBooleanField(
  353. required=False,
  354. widget=BulkEditNullBooleanSelect,
  355. label='VM role'
  356. )
  357. description = forms.CharField(
  358. max_length=200,
  359. required=False
  360. )
  361. model = DeviceRole
  362. fieldsets = (
  363. (None, ('color', 'vm_role', 'description')),
  364. )
  365. nullable_fields = ('color', 'description')
  366. class PlatformBulkEditForm(NetBoxModelBulkEditForm):
  367. manufacturer = DynamicModelChoiceField(
  368. queryset=Manufacturer.objects.all(),
  369. required=False
  370. )
  371. napalm_driver = forms.CharField(
  372. max_length=50,
  373. required=False
  374. )
  375. # TODO: Bulk edit support for napalm_args
  376. description = forms.CharField(
  377. max_length=200,
  378. required=False
  379. )
  380. model = Platform
  381. fieldsets = (
  382. (None, ('manufacturer', 'napalm_driver', 'description')),
  383. )
  384. nullable_fields = ('manufacturer', 'napalm_driver', 'description')
  385. class DeviceBulkEditForm(NetBoxModelBulkEditForm):
  386. manufacturer = DynamicModelChoiceField(
  387. queryset=Manufacturer.objects.all(),
  388. required=False
  389. )
  390. device_type = DynamicModelChoiceField(
  391. queryset=DeviceType.objects.all(),
  392. required=False,
  393. query_params={
  394. 'manufacturer_id': '$manufacturer'
  395. }
  396. )
  397. device_role = DynamicModelChoiceField(
  398. queryset=DeviceRole.objects.all(),
  399. required=False
  400. )
  401. site = DynamicModelChoiceField(
  402. queryset=Site.objects.all(),
  403. required=False
  404. )
  405. location = DynamicModelChoiceField(
  406. queryset=Location.objects.all(),
  407. required=False,
  408. query_params={
  409. 'site_id': '$site'
  410. }
  411. )
  412. tenant = DynamicModelChoiceField(
  413. queryset=Tenant.objects.all(),
  414. required=False
  415. )
  416. platform = DynamicModelChoiceField(
  417. queryset=Platform.objects.all(),
  418. required=False
  419. )
  420. status = forms.ChoiceField(
  421. choices=add_blank_choice(DeviceStatusChoices),
  422. required=False,
  423. widget=StaticSelect()
  424. )
  425. airflow = forms.ChoiceField(
  426. choices=add_blank_choice(DeviceAirflowChoices),
  427. required=False,
  428. widget=StaticSelect()
  429. )
  430. serial = forms.CharField(
  431. max_length=50,
  432. required=False,
  433. label='Serial Number'
  434. )
  435. model = Device
  436. fieldsets = (
  437. ('Device', ('device_role', 'status', 'tenant', 'platform')),
  438. ('Location', ('site', 'location')),
  439. ('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
  440. )
  441. nullable_fields = (
  442. 'tenant', 'platform', 'serial', 'airflow',
  443. )
  444. class ModuleBulkEditForm(NetBoxModelBulkEditForm):
  445. manufacturer = DynamicModelChoiceField(
  446. queryset=Manufacturer.objects.all(),
  447. required=False
  448. )
  449. module_type = DynamicModelChoiceField(
  450. queryset=ModuleType.objects.all(),
  451. required=False,
  452. query_params={
  453. 'manufacturer_id': '$manufacturer'
  454. }
  455. )
  456. serial = forms.CharField(
  457. max_length=50,
  458. required=False,
  459. label='Serial Number'
  460. )
  461. model = Module
  462. fieldsets = (
  463. (None, ('manufacturer', 'module_type', 'serial')),
  464. )
  465. nullable_fields = ('serial',)
  466. class CableBulkEditForm(NetBoxModelBulkEditForm):
  467. type = forms.ChoiceField(
  468. choices=add_blank_choice(CableTypeChoices),
  469. required=False,
  470. initial='',
  471. widget=StaticSelect()
  472. )
  473. status = forms.ChoiceField(
  474. choices=add_blank_choice(LinkStatusChoices),
  475. required=False,
  476. widget=StaticSelect(),
  477. initial=''
  478. )
  479. tenant = DynamicModelChoiceField(
  480. queryset=Tenant.objects.all(),
  481. required=False
  482. )
  483. label = forms.CharField(
  484. max_length=100,
  485. required=False
  486. )
  487. color = ColorField(
  488. required=False
  489. )
  490. length = forms.DecimalField(
  491. min_value=0,
  492. required=False
  493. )
  494. length_unit = forms.ChoiceField(
  495. choices=add_blank_choice(CableLengthUnitChoices),
  496. required=False,
  497. initial='',
  498. widget=StaticSelect()
  499. )
  500. model = Cable
  501. fieldsets = (
  502. (None, ('type', 'status', 'tenant', 'label')),
  503. ('Attributes', ('color', 'length', 'length_unit')),
  504. )
  505. nullable_fields = (
  506. 'type', 'status', 'tenant', 'label', 'color', 'length',
  507. )
  508. def clean(self):
  509. super().clean()
  510. # Validate length/unit
  511. length = self.cleaned_data.get('length')
  512. length_unit = self.cleaned_data.get('length_unit')
  513. if length and not length_unit:
  514. raise forms.ValidationError({
  515. 'length_unit': "Must specify a unit when setting length"
  516. })
  517. class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
  518. domain = forms.CharField(
  519. max_length=30,
  520. required=False
  521. )
  522. model = VirtualChassis
  523. fieldsets = (
  524. (None, ('domain',)),
  525. )
  526. nullable_fields = ('domain',)
  527. class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
  528. region = DynamicModelChoiceField(
  529. queryset=Region.objects.all(),
  530. required=False,
  531. initial_params={
  532. 'sites': '$site'
  533. }
  534. )
  535. site_group = DynamicModelChoiceField(
  536. queryset=SiteGroup.objects.all(),
  537. required=False,
  538. initial_params={
  539. 'sites': '$site'
  540. }
  541. )
  542. site = DynamicModelChoiceField(
  543. queryset=Site.objects.all(),
  544. required=False,
  545. query_params={
  546. 'region_id': '$region',
  547. 'group_id': '$site_group',
  548. }
  549. )
  550. location = DynamicModelChoiceField(
  551. queryset=Location.objects.all(),
  552. required=False,
  553. query_params={
  554. 'site_id': '$site'
  555. }
  556. )
  557. model = PowerPanel
  558. fieldsets = (
  559. (None, ('region', 'site_group', 'site', 'location')),
  560. )
  561. nullable_fields = ('location',)
  562. class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
  563. power_panel = DynamicModelChoiceField(
  564. queryset=PowerPanel.objects.all(),
  565. required=False
  566. )
  567. rack = DynamicModelChoiceField(
  568. queryset=Rack.objects.all(),
  569. required=False,
  570. )
  571. status = forms.ChoiceField(
  572. choices=add_blank_choice(PowerFeedStatusChoices),
  573. required=False,
  574. initial='',
  575. widget=StaticSelect()
  576. )
  577. type = forms.ChoiceField(
  578. choices=add_blank_choice(PowerFeedTypeChoices),
  579. required=False,
  580. initial='',
  581. widget=StaticSelect()
  582. )
  583. supply = forms.ChoiceField(
  584. choices=add_blank_choice(PowerFeedSupplyChoices),
  585. required=False,
  586. initial='',
  587. widget=StaticSelect()
  588. )
  589. phase = forms.ChoiceField(
  590. choices=add_blank_choice(PowerFeedPhaseChoices),
  591. required=False,
  592. initial='',
  593. widget=StaticSelect()
  594. )
  595. voltage = forms.IntegerField(
  596. required=False
  597. )
  598. amperage = forms.IntegerField(
  599. required=False
  600. )
  601. max_utilization = forms.IntegerField(
  602. required=False
  603. )
  604. mark_connected = forms.NullBooleanField(
  605. required=False,
  606. widget=BulkEditNullBooleanSelect
  607. )
  608. comments = CommentField(
  609. widget=SmallTextarea,
  610. label='Comments'
  611. )
  612. model = PowerFeed
  613. fieldsets = (
  614. (None, ('power_panel', 'rack', 'status', 'type', 'mark_connected')),
  615. ('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
  616. )
  617. nullable_fields = ('location', 'comments')
  618. #
  619. # Device component templates
  620. #
  621. class ConsolePortTemplateBulkEditForm(BulkEditForm):
  622. pk = forms.ModelMultipleChoiceField(
  623. queryset=ConsolePortTemplate.objects.all(),
  624. widget=forms.MultipleHiddenInput()
  625. )
  626. label = forms.CharField(
  627. max_length=64,
  628. required=False
  629. )
  630. type = forms.ChoiceField(
  631. choices=add_blank_choice(ConsolePortTypeChoices),
  632. required=False,
  633. widget=StaticSelect()
  634. )
  635. nullable_fields = ('label', 'type', 'description')
  636. class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
  637. pk = forms.ModelMultipleChoiceField(
  638. queryset=ConsoleServerPortTemplate.objects.all(),
  639. widget=forms.MultipleHiddenInput()
  640. )
  641. label = forms.CharField(
  642. max_length=64,
  643. required=False
  644. )
  645. type = forms.ChoiceField(
  646. choices=add_blank_choice(ConsolePortTypeChoices),
  647. required=False,
  648. widget=StaticSelect()
  649. )
  650. description = forms.CharField(
  651. required=False
  652. )
  653. nullable_fields = ('label', 'type', 'description')
  654. class PowerPortTemplateBulkEditForm(BulkEditForm):
  655. pk = forms.ModelMultipleChoiceField(
  656. queryset=PowerPortTemplate.objects.all(),
  657. widget=forms.MultipleHiddenInput()
  658. )
  659. label = forms.CharField(
  660. max_length=64,
  661. required=False
  662. )
  663. type = forms.ChoiceField(
  664. choices=add_blank_choice(PowerPortTypeChoices),
  665. required=False,
  666. widget=StaticSelect()
  667. )
  668. maximum_draw = forms.IntegerField(
  669. min_value=1,
  670. required=False,
  671. help_text="Maximum power draw (watts)"
  672. )
  673. allocated_draw = forms.IntegerField(
  674. min_value=1,
  675. required=False,
  676. help_text="Allocated power draw (watts)"
  677. )
  678. description = forms.CharField(
  679. required=False
  680. )
  681. nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
  682. class PowerOutletTemplateBulkEditForm(BulkEditForm):
  683. pk = forms.ModelMultipleChoiceField(
  684. queryset=PowerOutletTemplate.objects.all(),
  685. widget=forms.MultipleHiddenInput()
  686. )
  687. device_type = forms.ModelChoiceField(
  688. queryset=DeviceType.objects.all(),
  689. required=False,
  690. disabled=True,
  691. widget=forms.HiddenInput()
  692. )
  693. label = forms.CharField(
  694. max_length=64,
  695. required=False
  696. )
  697. type = forms.ChoiceField(
  698. choices=add_blank_choice(PowerOutletTypeChoices),
  699. required=False,
  700. widget=StaticSelect()
  701. )
  702. power_port = forms.ModelChoiceField(
  703. queryset=PowerPortTemplate.objects.all(),
  704. required=False
  705. )
  706. feed_leg = forms.ChoiceField(
  707. choices=add_blank_choice(PowerOutletFeedLegChoices),
  708. required=False,
  709. widget=StaticSelect()
  710. )
  711. description = forms.CharField(
  712. required=False
  713. )
  714. nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
  715. def __init__(self, *args, **kwargs):
  716. super().__init__(*args, **kwargs)
  717. # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
  718. if 'device_type' in self.initial:
  719. device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
  720. self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
  721. else:
  722. self.fields['power_port'].choices = ()
  723. self.fields['power_port'].widget.attrs['disabled'] = True
  724. class InterfaceTemplateBulkEditForm(BulkEditForm):
  725. pk = forms.ModelMultipleChoiceField(
  726. queryset=InterfaceTemplate.objects.all(),
  727. widget=forms.MultipleHiddenInput()
  728. )
  729. label = forms.CharField(
  730. max_length=64,
  731. required=False
  732. )
  733. type = forms.ChoiceField(
  734. choices=add_blank_choice(InterfaceTypeChoices),
  735. required=False,
  736. widget=StaticSelect()
  737. )
  738. mgmt_only = forms.NullBooleanField(
  739. required=False,
  740. widget=BulkEditNullBooleanSelect,
  741. label='Management only'
  742. )
  743. description = forms.CharField(
  744. required=False
  745. )
  746. nullable_fields = ('label', 'description')
  747. class FrontPortTemplateBulkEditForm(BulkEditForm):
  748. pk = forms.ModelMultipleChoiceField(
  749. queryset=FrontPortTemplate.objects.all(),
  750. widget=forms.MultipleHiddenInput()
  751. )
  752. label = forms.CharField(
  753. max_length=64,
  754. required=False
  755. )
  756. type = forms.ChoiceField(
  757. choices=add_blank_choice(PortTypeChoices),
  758. required=False,
  759. widget=StaticSelect()
  760. )
  761. color = ColorField(
  762. required=False
  763. )
  764. description = forms.CharField(
  765. required=False
  766. )
  767. nullable_fields = ('description',)
  768. class RearPortTemplateBulkEditForm(BulkEditForm):
  769. pk = forms.ModelMultipleChoiceField(
  770. queryset=RearPortTemplate.objects.all(),
  771. widget=forms.MultipleHiddenInput()
  772. )
  773. label = forms.CharField(
  774. max_length=64,
  775. required=False
  776. )
  777. type = forms.ChoiceField(
  778. choices=add_blank_choice(PortTypeChoices),
  779. required=False,
  780. widget=StaticSelect()
  781. )
  782. color = ColorField(
  783. required=False
  784. )
  785. description = forms.CharField(
  786. required=False
  787. )
  788. nullable_fields = ('description',)
  789. class ModuleBayTemplateBulkEditForm(BulkEditForm):
  790. pk = forms.ModelMultipleChoiceField(
  791. queryset=ModuleBayTemplate.objects.all(),
  792. widget=forms.MultipleHiddenInput()
  793. )
  794. label = forms.CharField(
  795. max_length=64,
  796. required=False
  797. )
  798. description = forms.CharField(
  799. required=False
  800. )
  801. nullable_fields = ('label', 'position', 'description')
  802. class DeviceBayTemplateBulkEditForm(BulkEditForm):
  803. pk = forms.ModelMultipleChoiceField(
  804. queryset=DeviceBayTemplate.objects.all(),
  805. widget=forms.MultipleHiddenInput()
  806. )
  807. label = forms.CharField(
  808. max_length=64,
  809. required=False
  810. )
  811. description = forms.CharField(
  812. required=False
  813. )
  814. nullable_fields = ('label', 'description')
  815. class InventoryItemTemplateBulkEditForm(BulkEditForm):
  816. pk = forms.ModelMultipleChoiceField(
  817. queryset=InventoryItemTemplate.objects.all(),
  818. widget=forms.MultipleHiddenInput()
  819. )
  820. label = forms.CharField(
  821. max_length=64,
  822. required=False
  823. )
  824. description = forms.CharField(
  825. required=False
  826. )
  827. role = DynamicModelChoiceField(
  828. queryset=InventoryItemRole.objects.all(),
  829. required=False
  830. )
  831. manufacturer = DynamicModelChoiceField(
  832. queryset=Manufacturer.objects.all(),
  833. required=False
  834. )
  835. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  836. #
  837. # Device components
  838. #
  839. class ComponentBulkEditForm(NetBoxModelBulkEditForm):
  840. device = forms.ModelChoiceField(
  841. queryset=Device.objects.all(),
  842. required=False,
  843. disabled=True,
  844. widget=forms.HiddenInput()
  845. )
  846. module = forms.ModelChoiceField(
  847. queryset=Module.objects.all(),
  848. required=False
  849. )
  850. def __init__(self, *args, **kwargs):
  851. super().__init__(*args, **kwargs)
  852. # Limit module queryset to Modules which belong to the parent Device
  853. if 'device' in self.initial:
  854. device = Device.objects.filter(pk=self.initial['device']).first()
  855. self.fields['module'].queryset = Module.objects.filter(device=device)
  856. else:
  857. self.fields['module'].choices = ()
  858. self.fields['module'].widget.attrs['disabled'] = True
  859. class ConsolePortBulkEditForm(
  860. form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  861. ComponentBulkEditForm
  862. ):
  863. mark_connected = forms.NullBooleanField(
  864. required=False,
  865. widget=BulkEditNullBooleanSelect
  866. )
  867. model = ConsolePort
  868. fieldsets = (
  869. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  870. )
  871. nullable_fields = ('module', 'label', 'description')
  872. class ConsoleServerPortBulkEditForm(
  873. form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
  874. ComponentBulkEditForm
  875. ):
  876. mark_connected = forms.NullBooleanField(
  877. required=False,
  878. widget=BulkEditNullBooleanSelect
  879. )
  880. model = ConsoleServerPort
  881. fieldsets = (
  882. (None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
  883. )
  884. nullable_fields = ('module', 'label', 'description')
  885. class PowerPortBulkEditForm(
  886. form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
  887. ComponentBulkEditForm
  888. ):
  889. mark_connected = forms.NullBooleanField(
  890. required=False,
  891. widget=BulkEditNullBooleanSelect
  892. )
  893. model = PowerPort
  894. fieldsets = (
  895. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  896. ('Power', ('maximum_draw', 'allocated_draw')),
  897. )
  898. nullable_fields = ('module', 'label', 'description')
  899. class PowerOutletBulkEditForm(
  900. form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
  901. ComponentBulkEditForm
  902. ):
  903. mark_connected = forms.NullBooleanField(
  904. required=False,
  905. widget=BulkEditNullBooleanSelect
  906. )
  907. model = PowerOutlet
  908. fieldsets = (
  909. (None, ('module', 'type', 'label', 'description', 'mark_connected')),
  910. ('Power', ('feed_leg', 'power_port')),
  911. )
  912. nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
  913. def __init__(self, *args, **kwargs):
  914. super().__init__(*args, **kwargs)
  915. # Limit power_port queryset to PowerPorts which belong to the parent Device
  916. if 'device' in self.initial:
  917. device = Device.objects.filter(pk=self.initial['device']).first()
  918. self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
  919. else:
  920. self.fields['power_port'].choices = ()
  921. self.fields['power_port'].widget.attrs['disabled'] = True
  922. class InterfaceBulkEditForm(
  923. form_from_model(Interface, [
  924. 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
  925. 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
  926. 'tx_power',
  927. ]),
  928. ComponentBulkEditForm
  929. ):
  930. enabled = forms.NullBooleanField(
  931. required=False,
  932. widget=BulkEditNullBooleanSelect
  933. )
  934. parent = DynamicModelChoiceField(
  935. queryset=Interface.objects.all(),
  936. required=False
  937. )
  938. bridge = DynamicModelChoiceField(
  939. queryset=Interface.objects.all(),
  940. required=False
  941. )
  942. lag = DynamicModelChoiceField(
  943. queryset=Interface.objects.all(),
  944. required=False,
  945. query_params={
  946. 'type': 'lag',
  947. },
  948. label='LAG'
  949. )
  950. speed = forms.IntegerField(
  951. required=False,
  952. widget=SelectSpeedWidget(),
  953. label='Speed'
  954. )
  955. mgmt_only = forms.NullBooleanField(
  956. required=False,
  957. widget=BulkEditNullBooleanSelect,
  958. label='Management only'
  959. )
  960. poe_mode = forms.ChoiceField(
  961. choices=add_blank_choice(InterfacePoEModeChoices),
  962. required=False,
  963. initial='',
  964. widget=StaticSelect()
  965. )
  966. poe_type = forms.ChoiceField(
  967. choices=add_blank_choice(InterfacePoETypeChoices),
  968. required=False,
  969. initial='',
  970. widget=StaticSelect()
  971. )
  972. mark_connected = forms.NullBooleanField(
  973. required=False,
  974. widget=BulkEditNullBooleanSelect
  975. )
  976. mode = forms.ChoiceField(
  977. choices=add_blank_choice(InterfaceModeChoices),
  978. required=False,
  979. initial='',
  980. widget=StaticSelect()
  981. )
  982. vlan_group = DynamicModelChoiceField(
  983. queryset=VLANGroup.objects.all(),
  984. required=False,
  985. label='VLAN group'
  986. )
  987. untagged_vlan = DynamicModelChoiceField(
  988. queryset=VLAN.objects.all(),
  989. required=False,
  990. query_params={
  991. 'group_id': '$vlan_group',
  992. },
  993. label='Untagged VLAN'
  994. )
  995. tagged_vlans = DynamicModelMultipleChoiceField(
  996. queryset=VLAN.objects.all(),
  997. required=False,
  998. query_params={
  999. 'group_id': '$vlan_group',
  1000. },
  1001. label='Tagged VLANs'
  1002. )
  1003. vrf = DynamicModelChoiceField(
  1004. queryset=VRF.objects.all(),
  1005. required=False,
  1006. label='VRF'
  1007. )
  1008. model = Interface
  1009. fieldsets = (
  1010. (None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
  1011. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1012. ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1013. ('PoE', ('poe_mode', 'poe_type')),
  1014. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1015. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1016. ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')),
  1017. )
  1018. nullable_fields = (
  1019. 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description',
  1020. 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1021. 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf',
  1022. )
  1023. def __init__(self, *args, **kwargs):
  1024. super().__init__(*args, **kwargs)
  1025. if 'device' in self.initial:
  1026. device = Device.objects.filter(pk=self.initial['device']).first()
  1027. # Restrict parent/bridge/LAG interface assignment by device
  1028. self.fields['parent'].widget.add_query_param('device_id', device.pk)
  1029. self.fields['bridge'].widget.add_query_param('device_id', device.pk)
  1030. self.fields['lag'].widget.add_query_param('device_id', device.pk)
  1031. # Limit VLAN choices by device
  1032. self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
  1033. self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
  1034. else:
  1035. # See #4523
  1036. if 'pk' in self.initial:
  1037. site = None
  1038. interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
  1039. # Check interface sites. First interface should set site, further interfaces will either continue the
  1040. # loop or reset back to no site and break the loop.
  1041. for interface in interfaces:
  1042. if site is None:
  1043. site = interface.device.site
  1044. elif interface.device.site is not site:
  1045. site = None
  1046. break
  1047. if site is not None:
  1048. self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
  1049. self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
  1050. self.fields['parent'].choices = ()
  1051. self.fields['parent'].widget.attrs['disabled'] = True
  1052. self.fields['bridge'].choices = ()
  1053. self.fields['bridge'].widget.attrs['disabled'] = True
  1054. self.fields['lag'].choices = ()
  1055. self.fields['lag'].widget.attrs['disabled'] = True
  1056. def clean(self):
  1057. super().clean()
  1058. if not self.cleaned_data['mode']:
  1059. if self.cleaned_data['untagged_vlan']:
  1060. raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
  1061. elif self.cleaned_data['tagged_vlans']:
  1062. raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
  1063. # Untagged interfaces cannot be assigned tagged VLANs
  1064. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
  1065. raise forms.ValidationError({
  1066. 'mode': "An access interface cannot have tagged VLANs assigned."
  1067. })
  1068. # Remove all tagged VLAN assignments from "tagged all" interfaces
  1069. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
  1070. self.cleaned_data['tagged_vlans'] = []
  1071. class FrontPortBulkEditForm(
  1072. form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1073. ComponentBulkEditForm
  1074. ):
  1075. model = FrontPort
  1076. fieldsets = (
  1077. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1078. )
  1079. nullable_fields = ('module', 'label', 'description')
  1080. class RearPortBulkEditForm(
  1081. form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
  1082. ComponentBulkEditForm
  1083. ):
  1084. model = RearPort
  1085. fieldsets = (
  1086. (None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
  1087. )
  1088. nullable_fields = ('module', 'label', 'description')
  1089. class ModuleBayBulkEditForm(
  1090. form_from_model(ModuleBay, ['label', 'position', 'description']),
  1091. NetBoxModelBulkEditForm
  1092. ):
  1093. model = ModuleBay
  1094. fieldsets = (
  1095. (None, ('label', 'position', 'description')),
  1096. )
  1097. nullable_fields = ('label', 'position', 'description')
  1098. class DeviceBayBulkEditForm(
  1099. form_from_model(DeviceBay, ['label', 'description']),
  1100. NetBoxModelBulkEditForm
  1101. ):
  1102. model = DeviceBay
  1103. fieldsets = (
  1104. (None, ('label', 'description')),
  1105. )
  1106. nullable_fields = ('label', 'description')
  1107. class InventoryItemBulkEditForm(
  1108. form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
  1109. NetBoxModelBulkEditForm
  1110. ):
  1111. device = DynamicModelChoiceField(
  1112. queryset=Device.objects.all(),
  1113. required=False
  1114. )
  1115. role = DynamicModelChoiceField(
  1116. queryset=InventoryItemRole.objects.all(),
  1117. required=False
  1118. )
  1119. manufacturer = DynamicModelChoiceField(
  1120. queryset=Manufacturer.objects.all(),
  1121. required=False
  1122. )
  1123. model = InventoryItem
  1124. fieldsets = (
  1125. (None, ('device', 'label', 'role', 'manufacturer', 'part_id', 'description')),
  1126. )
  1127. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1128. #
  1129. # Device component roles
  1130. #
  1131. class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
  1132. color = ColorField(
  1133. required=False
  1134. )
  1135. description = forms.CharField(
  1136. max_length=200,
  1137. required=False
  1138. )
  1139. model = InventoryItemRole
  1140. fieldsets = (
  1141. (None, ('color', 'description')),
  1142. )
  1143. nullable_fields = ('color', 'description')