bulk_edit.py 31 KB

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