bulk_edit.py 31 KB

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