bulk_edit.py 34 KB

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