bulk_edit.py 36 KB

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