bulk_edit.py 31 KB

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