bulk_edit.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825
  1. from django import forms
  2. from django.conf import settings
  3. from django.utils.translation import gettext_lazy as _
  4. from timezone_field import TimeZoneFormField
  5. from dcim.choices import *
  6. from dcim.constants import *
  7. from dcim.models import *
  8. from extras.models import ConfigTemplate
  9. from ipam.choices import VLANQinQRoleChoices
  10. from ipam.models import ASN, VLAN, VRF, VLANGroup
  11. from netbox.choices import *
  12. from netbox.forms import (
  13. NestedGroupModelBulkEditForm,
  14. NetBoxModelBulkEditForm,
  15. OrganizationalModelBulkEditForm,
  16. PrimaryModelBulkEditForm,
  17. )
  18. from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
  19. from tenancy.models import Tenant
  20. from users.models import User
  21. from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
  22. from utilities.forms.fields import (
  23. ChoiceField,
  24. ColorField,
  25. DynamicModelChoiceField,
  26. DynamicModelMultipleChoiceField,
  27. JSONField,
  28. PositiveBigIntegerField,
  29. )
  30. from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
  31. from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
  32. from virtualization.models import Cluster
  33. from wireless.choices import WirelessRoleChoices
  34. from wireless.models import WirelessLAN, WirelessLANGroup
  35. __all__ = (
  36. 'CableBulkEditForm',
  37. 'CableBundleBulkEditForm',
  38. 'ConsolePortBulkEditForm',
  39. 'ConsolePortTemplateBulkEditForm',
  40. 'ConsoleServerPortBulkEditForm',
  41. 'ConsoleServerPortTemplateBulkEditForm',
  42. 'DeviceBayBulkEditForm',
  43. 'DeviceBayTemplateBulkEditForm',
  44. 'DeviceBulkEditForm',
  45. 'DeviceRoleBulkEditForm',
  46. 'DeviceTypeBulkEditForm',
  47. 'FrontPortBulkEditForm',
  48. 'FrontPortTemplateBulkEditForm',
  49. 'InterfaceBulkEditForm',
  50. 'InterfaceTemplateBulkEditForm',
  51. 'InventoryItemBulkEditForm',
  52. 'InventoryItemRoleBulkEditForm',
  53. 'InventoryItemTemplateBulkEditForm',
  54. 'LocationBulkEditForm',
  55. 'MACAddressBulkEditForm',
  56. 'ManufacturerBulkEditForm',
  57. 'ModuleBayBulkEditForm',
  58. 'ModuleBayTemplateBulkEditForm',
  59. 'ModuleBulkEditForm',
  60. 'ModuleTypeBulkEditForm',
  61. 'ModuleTypeProfileBulkEditForm',
  62. 'PlatformBulkEditForm',
  63. 'PowerFeedBulkEditForm',
  64. 'PowerOutletBulkEditForm',
  65. 'PowerOutletTemplateBulkEditForm',
  66. 'PowerPanelBulkEditForm',
  67. 'PowerPortBulkEditForm',
  68. 'PowerPortTemplateBulkEditForm',
  69. 'RackBulkEditForm',
  70. 'RackGroupBulkEditForm',
  71. 'RackReservationBulkEditForm',
  72. 'RackRoleBulkEditForm',
  73. 'RackTypeBulkEditForm',
  74. 'RearPortBulkEditForm',
  75. 'RearPortTemplateBulkEditForm',
  76. 'RegionBulkEditForm',
  77. 'SiteBulkEditForm',
  78. 'SiteGroupBulkEditForm',
  79. 'VirtualChassisBulkEditForm',
  80. 'VirtualDeviceContextBulkEditForm'
  81. )
  82. class RegionBulkEditForm(NestedGroupModelBulkEditForm):
  83. parent = DynamicModelChoiceField(
  84. label=_('Parent'),
  85. queryset=Region.objects.all(),
  86. required=False
  87. )
  88. model = Region
  89. fieldsets = (
  90. FieldSet('parent', 'description'),
  91. )
  92. nullable_fields = ('parent', 'description', 'comments')
  93. class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm):
  94. parent = DynamicModelChoiceField(
  95. label=_('Parent'),
  96. queryset=SiteGroup.objects.all(),
  97. required=False
  98. )
  99. model = SiteGroup
  100. fieldsets = (
  101. FieldSet('parent', 'description'),
  102. )
  103. nullable_fields = ('parent', 'description', 'comments')
  104. class SiteBulkEditForm(PrimaryModelBulkEditForm):
  105. status = ChoiceField(
  106. label=_('Status'),
  107. choices=add_blank_choice(SiteStatusChoices),
  108. required=False,
  109. initial=''
  110. )
  111. region = DynamicModelChoiceField(
  112. label=_('Region'),
  113. queryset=Region.objects.all(),
  114. required=False
  115. )
  116. group = DynamicModelChoiceField(
  117. label=_('Group'),
  118. queryset=SiteGroup.objects.all(),
  119. required=False
  120. )
  121. tenant = DynamicModelChoiceField(
  122. label=_('Tenant'),
  123. queryset=Tenant.objects.all(),
  124. required=False
  125. )
  126. facility = forms.CharField(
  127. label=_('Facility'),
  128. max_length=50,
  129. required=False
  130. )
  131. asns = DynamicModelMultipleChoiceField(
  132. queryset=ASN.objects.all(),
  133. label=_('ASNs'),
  134. required=False
  135. )
  136. contact_name = forms.CharField(
  137. label=_('Contact name'),
  138. max_length=50,
  139. required=False
  140. )
  141. contact_phone = forms.CharField(
  142. label=_('Contact phone'),
  143. max_length=20,
  144. required=False
  145. )
  146. contact_email = forms.EmailField(
  147. required=False,
  148. label=_('Contact E-mail')
  149. )
  150. time_zone = TimeZoneFormField(
  151. label=_('Time zone'),
  152. choices=add_blank_choice(TimeZoneFormField().choices),
  153. required=False
  154. )
  155. model = Site
  156. fieldsets = (
  157. FieldSet('status', 'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description'),
  158. )
  159. nullable_fields = (
  160. 'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description', 'comments',
  161. )
  162. class LocationBulkEditForm(NestedGroupModelBulkEditForm):
  163. site = DynamicModelChoiceField(
  164. label=_('Site'),
  165. queryset=Site.objects.all(),
  166. required=False
  167. )
  168. parent = DynamicModelChoiceField(
  169. label=_('Parent'),
  170. queryset=Location.objects.all(),
  171. required=False,
  172. query_params={
  173. 'site_id': '$site'
  174. }
  175. )
  176. status = ChoiceField(
  177. label=_('Status'),
  178. choices=add_blank_choice(LocationStatusChoices),
  179. required=False,
  180. initial=''
  181. )
  182. tenant = DynamicModelChoiceField(
  183. label=_('Tenant'),
  184. queryset=Tenant.objects.all(),
  185. required=False
  186. )
  187. facility = forms.CharField(
  188. label=_('Facility'),
  189. max_length=50,
  190. required=False
  191. )
  192. model = Location
  193. fieldsets = (
  194. FieldSet('site', 'parent', 'status', 'tenant', 'facility', 'description'),
  195. )
  196. nullable_fields = ('parent', 'tenant', 'facility', 'description', 'comments')
  197. class RackGroupBulkEditForm(OrganizationalModelBulkEditForm):
  198. model = RackGroup
  199. fieldsets = (
  200. FieldSet('description'),
  201. )
  202. nullable_fields = ('description', 'comments')
  203. class RackRoleBulkEditForm(OrganizationalModelBulkEditForm):
  204. color = ColorField(
  205. label=_('Color'),
  206. required=False
  207. )
  208. model = RackRole
  209. fieldsets = (
  210. FieldSet('color', 'description'),
  211. )
  212. nullable_fields = ('color', 'description', 'comments')
  213. class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
  214. manufacturer = DynamicModelChoiceField(
  215. label=_('Manufacturer'),
  216. queryset=Manufacturer.objects.all(),
  217. required=False
  218. )
  219. form_factor = ChoiceField(
  220. label=_('Form factor'),
  221. choices=add_blank_choice(RackFormFactorChoices),
  222. required=False
  223. )
  224. width = ChoiceField(
  225. label=_('Width'),
  226. choices=add_blank_choice(RackWidthChoices),
  227. required=False
  228. )
  229. u_height = forms.IntegerField(
  230. required=False,
  231. label=_('Height (U)')
  232. )
  233. starting_unit = forms.IntegerField(
  234. required=False,
  235. min_value=1
  236. )
  237. desc_units = forms.NullBooleanField(
  238. required=False,
  239. widget=BulkEditNullBooleanSelect,
  240. label=_('Descending units')
  241. )
  242. outer_width = forms.IntegerField(
  243. label=_('Outer width'),
  244. required=False,
  245. min_value=1
  246. )
  247. outer_height = forms.IntegerField(
  248. label=_('Outer height'),
  249. required=False,
  250. min_value=1
  251. )
  252. outer_depth = forms.IntegerField(
  253. label=_('Outer depth'),
  254. required=False,
  255. min_value=1
  256. )
  257. outer_unit = ChoiceField(
  258. label=_('Outer unit'),
  259. choices=add_blank_choice(RackDimensionUnitChoices),
  260. required=False
  261. )
  262. mounting_depth = forms.IntegerField(
  263. label=_('Mounting depth'),
  264. required=False,
  265. min_value=1
  266. )
  267. weight = forms.DecimalField(
  268. label=_('Weight'),
  269. min_value=0,
  270. required=False
  271. )
  272. max_weight = forms.IntegerField(
  273. label=_('Max weight'),
  274. min_value=0,
  275. required=False
  276. )
  277. weight_unit = ChoiceField(
  278. label=_('Weight unit'),
  279. choices=add_blank_choice(WeightUnitChoices),
  280. required=False,
  281. initial=''
  282. )
  283. model = RackType
  284. fieldsets = (
  285. FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
  286. FieldSet(
  287. InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
  288. InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
  289. 'mounting_depth',
  290. name=_('Dimensions')
  291. ),
  292. FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
  293. )
  294. nullable_fields = (
  295. 'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
  296. 'max_weight', 'weight_unit', 'description', 'comments',
  297. )
  298. class RackBulkEditForm(PrimaryModelBulkEditForm):
  299. region = DynamicModelChoiceField(
  300. label=_('Region'),
  301. queryset=Region.objects.all(),
  302. required=False,
  303. initial_params={
  304. 'sites': '$site'
  305. }
  306. )
  307. site_group = DynamicModelChoiceField(
  308. label=_('Site group'),
  309. queryset=SiteGroup.objects.all(),
  310. required=False,
  311. initial_params={
  312. 'sites': '$site'
  313. }
  314. )
  315. site = DynamicModelChoiceField(
  316. label=_('Site'),
  317. queryset=Site.objects.all(),
  318. required=False,
  319. query_params={
  320. 'region_id': '$region',
  321. 'group_id': '$site_group',
  322. }
  323. )
  324. location = DynamicModelChoiceField(
  325. label=_('Location'),
  326. queryset=Location.objects.all(),
  327. required=False,
  328. query_params={
  329. 'site_id': '$site'
  330. }
  331. )
  332. group = DynamicModelChoiceField(
  333. label=_('Group'),
  334. queryset=RackGroup.objects.all(),
  335. required=False
  336. )
  337. tenant = DynamicModelChoiceField(
  338. label=_('Tenant'),
  339. queryset=Tenant.objects.all(),
  340. required=False
  341. )
  342. status = ChoiceField(
  343. label=_('Status'),
  344. choices=add_blank_choice(RackStatusChoices),
  345. required=False,
  346. initial=''
  347. )
  348. role = DynamicModelChoiceField(
  349. label=_('Role'),
  350. queryset=RackRole.objects.all(),
  351. required=False
  352. )
  353. rack_type = DynamicModelChoiceField(
  354. label=_('Rack type'),
  355. queryset=RackType.objects.all(),
  356. required=False,
  357. )
  358. serial = forms.CharField(
  359. max_length=50,
  360. required=False,
  361. label=_('Serial Number')
  362. )
  363. asset_tag = forms.CharField(
  364. label=_('Asset tag'),
  365. max_length=50,
  366. required=False
  367. )
  368. form_factor = ChoiceField(
  369. label=_('Form factor'),
  370. choices=add_blank_choice(RackFormFactorChoices),
  371. required=False
  372. )
  373. width = ChoiceField(
  374. label=_('Width'),
  375. choices=add_blank_choice(RackWidthChoices),
  376. required=False
  377. )
  378. u_height = forms.IntegerField(
  379. required=False,
  380. label=_('Height (U)')
  381. )
  382. desc_units = forms.NullBooleanField(
  383. required=False,
  384. widget=BulkEditNullBooleanSelect,
  385. label=_('Descending units')
  386. )
  387. outer_width = forms.IntegerField(
  388. label=_('Outer width'),
  389. required=False,
  390. min_value=1
  391. )
  392. outer_height = forms.IntegerField(
  393. label=_('Outer height'),
  394. required=False,
  395. min_value=1
  396. )
  397. outer_depth = forms.IntegerField(
  398. label=_('Outer depth'),
  399. required=False,
  400. min_value=1
  401. )
  402. outer_unit = ChoiceField(
  403. label=_('Outer unit'),
  404. choices=add_blank_choice(RackDimensionUnitChoices),
  405. required=False
  406. )
  407. mounting_depth = forms.IntegerField(
  408. label=_('Mounting depth'),
  409. required=False,
  410. min_value=1
  411. )
  412. airflow = ChoiceField(
  413. label=_('Airflow'),
  414. choices=add_blank_choice(RackAirflowChoices),
  415. required=False
  416. )
  417. weight = forms.DecimalField(
  418. label=_('Weight'),
  419. min_value=0,
  420. required=False
  421. )
  422. max_weight = forms.IntegerField(
  423. label=_('Max weight'),
  424. min_value=0,
  425. required=False
  426. )
  427. weight_unit = ChoiceField(
  428. label=_('Weight unit'),
  429. choices=add_blank_choice(WeightUnitChoices),
  430. required=False,
  431. initial=''
  432. )
  433. model = Rack
  434. fieldsets = (
  435. FieldSet(
  436. 'status', 'group', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')
  437. ),
  438. FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
  439. FieldSet('outer_width', 'outer_height', 'outer_depth', 'outer_unit', name=_('Outer Dimensions')),
  440. FieldSet('form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'mounting_depth', name=_('Hardware')),
  441. FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
  442. )
  443. nullable_fields = (
  444. 'location', 'group', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_height', 'outer_depth',
  445. 'outer_unit', 'weight', 'max_weight', 'weight_unit', 'description', 'comments',
  446. )
  447. class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
  448. status = ChoiceField(
  449. label=_('Status'),
  450. choices=add_blank_choice(RackReservationStatusChoices),
  451. required=False,
  452. initial=''
  453. )
  454. user = forms.ModelChoiceField(
  455. label=_('User'),
  456. queryset=User.objects.order_by('username'),
  457. required=False
  458. )
  459. tenant = DynamicModelChoiceField(
  460. label=_('Tenant'),
  461. queryset=Tenant.objects.all(),
  462. required=False
  463. )
  464. model = RackReservation
  465. fieldsets = (
  466. FieldSet('status', 'user', 'tenant', 'description'),
  467. )
  468. nullable_fields = ('comments',)
  469. class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm):
  470. model = Manufacturer
  471. fieldsets = (
  472. FieldSet('description'),
  473. )
  474. nullable_fields = ('description', 'comments')
  475. class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
  476. manufacturer = DynamicModelChoiceField(
  477. label=_('Manufacturer'),
  478. queryset=Manufacturer.objects.all(),
  479. required=False
  480. )
  481. default_platform = DynamicModelChoiceField(
  482. label=_('Default platform'),
  483. queryset=Platform.objects.all(),
  484. required=False
  485. )
  486. part_number = forms.CharField(
  487. label=_('Part number'),
  488. required=False
  489. )
  490. u_height = forms.IntegerField(
  491. label=_('U height'),
  492. min_value=0,
  493. required=False
  494. )
  495. is_full_depth = forms.NullBooleanField(
  496. required=False,
  497. widget=BulkEditNullBooleanSelect(),
  498. label=_('Is full depth')
  499. )
  500. exclude_from_utilization = forms.NullBooleanField(
  501. required=False,
  502. widget=BulkEditNullBooleanSelect(),
  503. label=_('Exclude from utilization')
  504. )
  505. airflow = ChoiceField(
  506. label=_('Airflow'),
  507. choices=add_blank_choice(DeviceAirflowChoices),
  508. required=False
  509. )
  510. weight = forms.DecimalField(
  511. label=_('Weight'),
  512. min_value=0,
  513. required=False
  514. )
  515. weight_unit = ChoiceField(
  516. label=_('Weight unit'),
  517. choices=add_blank_choice(WeightUnitChoices),
  518. required=False,
  519. initial=''
  520. )
  521. model = DeviceType
  522. fieldsets = (
  523. FieldSet(
  524. 'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
  525. 'airflow', 'description', name=_('Device Type')
  526. ),
  527. FieldSet('weight', 'weight_unit', name=_('Weight')),
  528. )
  529. nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
  530. class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm):
  531. schema = JSONField(
  532. label=_('Schema'),
  533. required=False
  534. )
  535. model = ModuleTypeProfile
  536. fieldsets = (
  537. FieldSet('name', 'description', 'schema', name=_('Profile')),
  538. )
  539. nullable_fields = ('description', 'comments')
  540. class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
  541. profile = DynamicModelChoiceField(
  542. label=_('Profile'),
  543. queryset=ModuleTypeProfile.objects.all(),
  544. required=False
  545. )
  546. manufacturer = DynamicModelChoiceField(
  547. label=_('Manufacturer'),
  548. queryset=Manufacturer.objects.all(),
  549. required=False
  550. )
  551. part_number = forms.CharField(
  552. label=_('Part number'),
  553. required=False
  554. )
  555. airflow = ChoiceField(
  556. label=_('Airflow'),
  557. choices=add_blank_choice(ModuleAirflowChoices),
  558. required=False
  559. )
  560. weight = forms.DecimalField(
  561. label=_('Weight'),
  562. min_value=0,
  563. required=False
  564. )
  565. weight_unit = ChoiceField(
  566. label=_('Weight unit'),
  567. choices=add_blank_choice(WeightUnitChoices),
  568. required=False,
  569. initial=''
  570. )
  571. model = ModuleType
  572. fieldsets = (
  573. FieldSet('profile', 'manufacturer', 'part_number', 'description', name=_('Module Type')),
  574. FieldSet(
  575. 'airflow',
  576. InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
  577. name=_('Chassis')
  578. ),
  579. )
  580. nullable_fields = ('part_number', 'weight', 'weight_unit', 'profile', 'description', 'comments')
  581. class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
  582. parent = DynamicModelChoiceField(
  583. label=_('Parent'),
  584. queryset=DeviceRole.objects.all(),
  585. required=False,
  586. )
  587. color = ColorField(
  588. label=_('Color'),
  589. required=False
  590. )
  591. vm_role = forms.NullBooleanField(
  592. required=False,
  593. widget=BulkEditNullBooleanSelect,
  594. label=_('VM role')
  595. )
  596. config_template = DynamicModelChoiceField(
  597. label=_('Config template'),
  598. queryset=ConfigTemplate.objects.all(),
  599. required=False
  600. )
  601. model = DeviceRole
  602. fieldsets = (
  603. FieldSet('parent', 'color', 'vm_role', 'config_template', 'description'),
  604. )
  605. nullable_fields = ('parent', 'color', 'config_template', 'description', 'comments')
  606. class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
  607. parent = DynamicModelChoiceField(
  608. label=_('Parent'),
  609. queryset=Platform.objects.all(),
  610. required=False,
  611. )
  612. manufacturer = DynamicModelChoiceField(
  613. label=_('Manufacturer'),
  614. queryset=Manufacturer.objects.all(),
  615. required=False
  616. )
  617. config_template = DynamicModelChoiceField(
  618. label=_('Config template'),
  619. queryset=ConfigTemplate.objects.all(),
  620. required=False
  621. )
  622. model = Platform
  623. fieldsets = (
  624. FieldSet('parent', 'manufacturer', 'config_template', 'description'),
  625. )
  626. nullable_fields = ('parent', 'manufacturer', 'config_template', 'description', 'comments')
  627. class DeviceBulkEditForm(PrimaryModelBulkEditForm):
  628. manufacturer = DynamicModelChoiceField(
  629. label=_('Manufacturer'),
  630. queryset=Manufacturer.objects.all(),
  631. required=False
  632. )
  633. device_type = DynamicModelChoiceField(
  634. label=_('Device type'),
  635. queryset=DeviceType.objects.all(),
  636. required=False,
  637. context={
  638. 'parent': 'manufacturer',
  639. },
  640. query_params={
  641. 'manufacturer_id': '$manufacturer'
  642. }
  643. )
  644. role = DynamicModelChoiceField(
  645. label=_('Device role'),
  646. queryset=DeviceRole.objects.all(),
  647. required=False
  648. )
  649. site = DynamicModelChoiceField(
  650. label=_('Site'),
  651. queryset=Site.objects.all(),
  652. required=False
  653. )
  654. location = DynamicModelChoiceField(
  655. label=_('Location'),
  656. queryset=Location.objects.all(),
  657. required=False,
  658. query_params={
  659. 'site_id': '$site'
  660. }
  661. )
  662. tenant = DynamicModelChoiceField(
  663. label=_('Tenant'),
  664. queryset=Tenant.objects.all(),
  665. required=False
  666. )
  667. platform = DynamicModelChoiceField(
  668. label=_('Platform'),
  669. queryset=Platform.objects.all(),
  670. required=False
  671. )
  672. status = ChoiceField(
  673. label=_('Status'),
  674. choices=add_blank_choice(DeviceStatusChoices),
  675. required=False
  676. )
  677. airflow = ChoiceField(
  678. label=_('Airflow'),
  679. choices=add_blank_choice(DeviceAirflowChoices),
  680. required=False
  681. )
  682. serial = forms.CharField(
  683. max_length=50,
  684. required=False,
  685. label=_('Serial Number')
  686. )
  687. config_template = DynamicModelChoiceField(
  688. label=_('Config template'),
  689. queryset=ConfigTemplate.objects.all(),
  690. required=False
  691. )
  692. cluster = DynamicModelChoiceField(
  693. label=_('Cluster'),
  694. queryset=Cluster.objects.all(),
  695. required=False,
  696. query_params={
  697. 'site_id': ['$site', 'null']
  698. },
  699. )
  700. model = Device
  701. fieldsets = (
  702. FieldSet('role', 'status', 'tenant', 'platform', 'description', name=_('Device')),
  703. FieldSet('site', 'location', name=_('Location')),
  704. FieldSet('manufacturer', 'device_type', 'airflow', 'serial', name=_('Hardware')),
  705. FieldSet('config_template', name=_('Configuration')),
  706. FieldSet('cluster', name=_('Virtualization')),
  707. )
  708. nullable_fields = (
  709. 'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'cluster', 'comments',
  710. )
  711. class ModuleBulkEditForm(PrimaryModelBulkEditForm):
  712. manufacturer = DynamicModelChoiceField(
  713. label=_('Manufacturer'),
  714. queryset=Manufacturer.objects.all(),
  715. required=False
  716. )
  717. module_type = DynamicModelChoiceField(
  718. label=_('Module type'),
  719. queryset=ModuleType.objects.all(),
  720. required=False,
  721. query_params={
  722. 'manufacturer_id': '$manufacturer'
  723. },
  724. context={
  725. 'parent': 'manufacturer',
  726. }
  727. )
  728. status = ChoiceField(
  729. label=_('Status'),
  730. choices=add_blank_choice(ModuleStatusChoices),
  731. required=False,
  732. initial=''
  733. )
  734. serial = forms.CharField(
  735. max_length=50,
  736. required=False,
  737. label=_('Serial Number')
  738. )
  739. model = Module
  740. fieldsets = (
  741. FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'),
  742. )
  743. nullable_fields = ('serial', 'description', 'comments')
  744. class CableBundleBulkEditForm(PrimaryModelBulkEditForm):
  745. pk = forms.ModelMultipleChoiceField(
  746. queryset=CableBundle.objects.all(),
  747. widget=forms.MultipleHiddenInput
  748. )
  749. description = forms.CharField(
  750. label=_('Description'),
  751. max_length=200,
  752. required=False,
  753. )
  754. model = CableBundle
  755. fieldsets = (
  756. FieldSet('description',),
  757. )
  758. nullable_fields = ('description', 'comments')
  759. class CableBulkEditForm(PrimaryModelBulkEditForm):
  760. type = ChoiceField(
  761. label=_('Type'),
  762. choices=add_blank_choice(CableTypeChoices),
  763. required=False,
  764. initial=''
  765. )
  766. status = ChoiceField(
  767. label=_('Status'),
  768. choices=add_blank_choice(LinkStatusChoices),
  769. required=False,
  770. initial=''
  771. )
  772. profile = ChoiceField(
  773. label=_('Profile'),
  774. choices=add_blank_choice(CableProfileChoices),
  775. required=False,
  776. initial=''
  777. )
  778. tenant = DynamicModelChoiceField(
  779. label=_('Tenant'),
  780. queryset=Tenant.objects.all(),
  781. required=False
  782. )
  783. bundle = DynamicModelChoiceField(
  784. label=_('Bundle'),
  785. queryset=CableBundle.objects.all(),
  786. required=False,
  787. )
  788. label = forms.CharField(
  789. label=_('Label'),
  790. max_length=100,
  791. required=False
  792. )
  793. color = ColorField(
  794. label=_('Color'),
  795. required=False
  796. )
  797. length = forms.DecimalField(
  798. label=_('Length'),
  799. min_value=0,
  800. required=False
  801. )
  802. length_unit = ChoiceField(
  803. label=_('Length unit'),
  804. choices=add_blank_choice(CableLengthUnitChoices),
  805. required=False,
  806. initial=''
  807. )
  808. model = Cable
  809. fieldsets = (
  810. FieldSet('type', 'status', 'profile', 'tenant', 'bundle', 'label', 'description'),
  811. FieldSet('color', 'length', 'length_unit', name=_('Attributes')),
  812. )
  813. nullable_fields = (
  814. 'type', 'status', 'profile', 'tenant', 'bundle', 'label', 'color', 'length', 'description', 'comments',
  815. )
  816. class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm):
  817. domain = forms.CharField(
  818. label=_('Domain'),
  819. max_length=30,
  820. required=False
  821. )
  822. model = VirtualChassis
  823. fieldsets = (
  824. FieldSet('domain', 'description'),
  825. )
  826. nullable_fields = ('domain', 'description', 'comments')
  827. class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
  828. region = DynamicModelChoiceField(
  829. label=_('Region'),
  830. queryset=Region.objects.all(),
  831. required=False,
  832. initial_params={
  833. 'sites': '$site'
  834. }
  835. )
  836. site_group = DynamicModelChoiceField(
  837. label=_('Site group'),
  838. queryset=SiteGroup.objects.all(),
  839. required=False,
  840. initial_params={
  841. 'sites': '$site'
  842. }
  843. )
  844. site = DynamicModelChoiceField(
  845. label=_('Site'),
  846. queryset=Site.objects.all(),
  847. required=False,
  848. query_params={
  849. 'region_id': '$region',
  850. 'group_id': '$site_group',
  851. }
  852. )
  853. location = DynamicModelChoiceField(
  854. label=_('Location'),
  855. queryset=Location.objects.all(),
  856. required=False,
  857. query_params={
  858. 'site_id': '$site'
  859. }
  860. )
  861. model = PowerPanel
  862. fieldsets = (
  863. FieldSet('region', 'site_group', 'site', 'location', 'description'),
  864. )
  865. nullable_fields = ('location', 'description', 'comments')
  866. class PowerFeedBulkEditForm(PrimaryModelBulkEditForm):
  867. power_panel = DynamicModelChoiceField(
  868. label=_('Power panel'),
  869. queryset=PowerPanel.objects.all(),
  870. required=False
  871. )
  872. rack = DynamicModelChoiceField(
  873. label=_('Rack'),
  874. queryset=Rack.objects.all(),
  875. required=False,
  876. )
  877. status = ChoiceField(
  878. label=_('Status'),
  879. choices=add_blank_choice(PowerFeedStatusChoices),
  880. required=False,
  881. initial=''
  882. )
  883. type = ChoiceField(
  884. label=_('Type'),
  885. choices=add_blank_choice(PowerFeedTypeChoices),
  886. required=False,
  887. initial=''
  888. )
  889. supply = ChoiceField(
  890. label=_('Supply'),
  891. choices=add_blank_choice(PowerFeedSupplyChoices),
  892. required=False,
  893. initial=''
  894. )
  895. phase = ChoiceField(
  896. label=_('Phase'),
  897. choices=add_blank_choice(PowerFeedPhaseChoices),
  898. required=False,
  899. initial=''
  900. )
  901. voltage = forms.IntegerField(
  902. label=_('Voltage'),
  903. required=False
  904. )
  905. amperage = forms.IntegerField(
  906. label=_('Amperage'),
  907. required=False
  908. )
  909. max_utilization = forms.IntegerField(
  910. label=_('Max utilization'),
  911. required=False
  912. )
  913. mark_connected = forms.NullBooleanField(
  914. label=_('Mark connected'),
  915. required=False,
  916. widget=BulkEditNullBooleanSelect
  917. )
  918. tenant = DynamicModelChoiceField(
  919. queryset=Tenant.objects.all(),
  920. required=False
  921. )
  922. model = PowerFeed
  923. fieldsets = (
  924. FieldSet('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant'),
  925. FieldSet('supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Power'))
  926. )
  927. nullable_fields = ('location', 'tenant', 'description', 'comments')
  928. #
  929. # Device component templates
  930. #
  931. class ComponentTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
  932. pass
  933. class ConsolePortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  934. pk = forms.ModelMultipleChoiceField(
  935. queryset=ConsolePortTemplate.objects.all(),
  936. widget=forms.MultipleHiddenInput()
  937. )
  938. label = forms.CharField(
  939. label=_('Label'),
  940. max_length=64,
  941. required=False
  942. )
  943. type = ChoiceField(
  944. label=_('Type'),
  945. choices=add_blank_choice(ConsolePortTypeChoices),
  946. required=False
  947. )
  948. nullable_fields = ('label', 'type', 'description')
  949. class ConsoleServerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  950. pk = forms.ModelMultipleChoiceField(
  951. queryset=ConsoleServerPortTemplate.objects.all(),
  952. widget=forms.MultipleHiddenInput()
  953. )
  954. label = forms.CharField(
  955. label=_('Label'),
  956. max_length=64,
  957. required=False
  958. )
  959. type = ChoiceField(
  960. label=_('Type'),
  961. choices=add_blank_choice(ConsolePortTypeChoices),
  962. required=False
  963. )
  964. description = forms.CharField(
  965. label=_('Description'),
  966. required=False
  967. )
  968. nullable_fields = ('label', 'type', 'description')
  969. class PowerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  970. pk = forms.ModelMultipleChoiceField(
  971. queryset=PowerPortTemplate.objects.all(),
  972. widget=forms.MultipleHiddenInput()
  973. )
  974. label = forms.CharField(
  975. label=_('Label'),
  976. max_length=64,
  977. required=False
  978. )
  979. type = ChoiceField(
  980. label=_('Type'),
  981. choices=add_blank_choice(PowerPortTypeChoices),
  982. required=False
  983. )
  984. maximum_draw = forms.IntegerField(
  985. label=_('Maximum draw'),
  986. min_value=1,
  987. required=False,
  988. help_text=_("Maximum power draw (watts)")
  989. )
  990. allocated_draw = forms.IntegerField(
  991. label=_('Allocated draw'),
  992. min_value=1,
  993. required=False,
  994. help_text=_("Allocated power draw (watts)")
  995. )
  996. description = forms.CharField(
  997. label=_('Description'),
  998. required=False
  999. )
  1000. nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
  1001. class PowerOutletTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1002. pk = forms.ModelMultipleChoiceField(
  1003. queryset=PowerOutletTemplate.objects.all(),
  1004. widget=forms.MultipleHiddenInput()
  1005. )
  1006. device_type = forms.ModelChoiceField(
  1007. label=_('Device type'),
  1008. queryset=DeviceType.objects.all(),
  1009. required=False,
  1010. disabled=True,
  1011. widget=forms.HiddenInput()
  1012. )
  1013. label = forms.CharField(
  1014. label=_('Label'),
  1015. max_length=64,
  1016. required=False
  1017. )
  1018. type = ChoiceField(
  1019. label=_('Type'),
  1020. choices=add_blank_choice(PowerOutletTypeChoices),
  1021. required=False
  1022. )
  1023. color = ColorField(
  1024. label=_('Color'),
  1025. required=False
  1026. )
  1027. power_port = forms.ModelChoiceField(
  1028. label=_('Power port'),
  1029. queryset=PowerPortTemplate.objects.all(),
  1030. required=False
  1031. )
  1032. feed_leg = ChoiceField(
  1033. label=_('Feed leg'),
  1034. choices=add_blank_choice(PowerOutletFeedLegChoices),
  1035. required=False
  1036. )
  1037. description = forms.CharField(
  1038. label=_('Description'),
  1039. required=False
  1040. )
  1041. nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
  1042. def __init__(self, *args, **kwargs):
  1043. super().__init__(*args, **kwargs)
  1044. # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
  1045. if 'device_type' in self.initial:
  1046. device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
  1047. self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
  1048. else:
  1049. self.fields['power_port'].choices = ()
  1050. self.fields['power_port'].widget.attrs['disabled'] = True
  1051. class InterfaceTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1052. pk = forms.ModelMultipleChoiceField(
  1053. queryset=InterfaceTemplate.objects.all(),
  1054. widget=forms.MultipleHiddenInput()
  1055. )
  1056. label = forms.CharField(
  1057. label=_('Label'),
  1058. max_length=64,
  1059. required=False
  1060. )
  1061. type = ChoiceField(
  1062. label=_('Type'),
  1063. choices=add_blank_choice(InterfaceTypeChoices),
  1064. required=False
  1065. )
  1066. enabled = forms.NullBooleanField(
  1067. label=_('Enabled'),
  1068. required=False,
  1069. widget=BulkEditNullBooleanSelect
  1070. )
  1071. mgmt_only = forms.NullBooleanField(
  1072. required=False,
  1073. widget=BulkEditNullBooleanSelect,
  1074. label=_('Management only')
  1075. )
  1076. description = forms.CharField(
  1077. label=_('Description'),
  1078. required=False
  1079. )
  1080. poe_mode = ChoiceField(
  1081. choices=add_blank_choice(InterfacePoEModeChoices),
  1082. required=False,
  1083. initial='',
  1084. label=_('PoE mode')
  1085. )
  1086. poe_type = ChoiceField(
  1087. choices=add_blank_choice(InterfacePoETypeChoices),
  1088. required=False,
  1089. initial='',
  1090. label=_('PoE type')
  1091. )
  1092. rf_role = ChoiceField(
  1093. choices=add_blank_choice(WirelessRoleChoices),
  1094. required=False,
  1095. initial='',
  1096. label=_('Wireless role')
  1097. )
  1098. nullable_fields = ('label', 'description', 'poe_mode', 'poe_type', 'rf_role')
  1099. class FrontPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1100. pk = forms.ModelMultipleChoiceField(
  1101. queryset=FrontPortTemplate.objects.all(),
  1102. widget=forms.MultipleHiddenInput()
  1103. )
  1104. label = forms.CharField(
  1105. label=_('Label'),
  1106. max_length=64,
  1107. required=False
  1108. )
  1109. type = ChoiceField(
  1110. label=_('Type'),
  1111. choices=add_blank_choice(PortTypeChoices),
  1112. required=False
  1113. )
  1114. color = ColorField(
  1115. label=_('Color'),
  1116. required=False
  1117. )
  1118. description = forms.CharField(
  1119. label=_('Description'),
  1120. required=False
  1121. )
  1122. nullable_fields = ('description',)
  1123. class RearPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1124. pk = forms.ModelMultipleChoiceField(
  1125. queryset=RearPortTemplate.objects.all(),
  1126. widget=forms.MultipleHiddenInput()
  1127. )
  1128. label = forms.CharField(
  1129. label=_('Label'),
  1130. max_length=64,
  1131. required=False
  1132. )
  1133. type = ChoiceField(
  1134. label=_('Type'),
  1135. choices=add_blank_choice(PortTypeChoices),
  1136. required=False
  1137. )
  1138. color = ColorField(
  1139. label=_('Color'),
  1140. required=False
  1141. )
  1142. description = forms.CharField(
  1143. label=_('Description'),
  1144. required=False
  1145. )
  1146. nullable_fields = ('description',)
  1147. class ModuleBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1148. pk = forms.ModelMultipleChoiceField(
  1149. queryset=ModuleBayTemplate.objects.all(),
  1150. widget=forms.MultipleHiddenInput()
  1151. )
  1152. label = forms.CharField(
  1153. label=_('Label'),
  1154. max_length=64,
  1155. required=False
  1156. )
  1157. description = forms.CharField(
  1158. label=_('Description'),
  1159. required=False
  1160. )
  1161. enabled = forms.NullBooleanField(
  1162. label=_('Enabled'),
  1163. required=False,
  1164. widget=BulkEditNullBooleanSelect,
  1165. )
  1166. nullable_fields = ('label', 'position', 'description')
  1167. class DeviceBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1168. pk = forms.ModelMultipleChoiceField(
  1169. queryset=DeviceBayTemplate.objects.all(),
  1170. widget=forms.MultipleHiddenInput()
  1171. )
  1172. label = forms.CharField(
  1173. label=_('Label'),
  1174. max_length=64,
  1175. required=False
  1176. )
  1177. description = forms.CharField(
  1178. label=_('Description'),
  1179. required=False
  1180. )
  1181. enabled = forms.NullBooleanField(
  1182. label=_('Enabled'),
  1183. required=False,
  1184. widget=BulkEditNullBooleanSelect,
  1185. )
  1186. nullable_fields = ('label', 'description')
  1187. class InventoryItemTemplateBulkEditForm(ComponentTemplateBulkEditForm):
  1188. pk = forms.ModelMultipleChoiceField(
  1189. queryset=InventoryItemTemplate.objects.all(),
  1190. widget=forms.MultipleHiddenInput()
  1191. )
  1192. label = forms.CharField(
  1193. label=_('Label'),
  1194. max_length=64,
  1195. required=False
  1196. )
  1197. description = forms.CharField(
  1198. label=_('Description'),
  1199. required=False
  1200. )
  1201. role = DynamicModelChoiceField(
  1202. label=_('Role'),
  1203. queryset=InventoryItemRole.objects.all(),
  1204. required=False
  1205. )
  1206. manufacturer = DynamicModelChoiceField(
  1207. label=_('Manufacturer'),
  1208. queryset=Manufacturer.objects.all(),
  1209. required=False
  1210. )
  1211. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1212. #
  1213. # Device components
  1214. #
  1215. class ComponentBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
  1216. device = forms.ModelChoiceField(
  1217. label=_('Device'),
  1218. queryset=Device.objects.all(),
  1219. required=False,
  1220. disabled=True,
  1221. widget=forms.HiddenInput()
  1222. )
  1223. module = forms.ModelChoiceField(
  1224. label=_('Module'),
  1225. queryset=Module.objects.all(),
  1226. required=False
  1227. )
  1228. def __init__(self, *args, initial=None, **kwargs):
  1229. try:
  1230. self.device_id = int(initial.get('device'))
  1231. except (TypeError, ValueError):
  1232. self.device_id = None
  1233. super().__init__(*args, initial=initial, **kwargs)
  1234. # Limit module queryset to Modules which belong to the parent Device
  1235. if self.device_id:
  1236. device = Device.objects.filter(pk=self.device_id).first()
  1237. self.fields['module'].queryset = Module.objects.filter(device=device)
  1238. else:
  1239. self.fields['module'].choices = ()
  1240. self.fields['module'].widget.attrs['disabled'] = True
  1241. class ConsolePortBulkEditForm(
  1242. ComponentBulkEditForm,
  1243. form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description'])
  1244. ):
  1245. mark_connected = forms.NullBooleanField(
  1246. label=_('Mark connected'),
  1247. required=False,
  1248. widget=BulkEditNullBooleanSelect
  1249. )
  1250. model = ConsolePort
  1251. fieldsets = (
  1252. FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
  1253. )
  1254. nullable_fields = ('module', 'label', 'description')
  1255. class ConsoleServerPortBulkEditForm(
  1256. ComponentBulkEditForm,
  1257. form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description'])
  1258. ):
  1259. mark_connected = forms.NullBooleanField(
  1260. label=_('Mark connected'),
  1261. required=False,
  1262. widget=BulkEditNullBooleanSelect
  1263. )
  1264. model = ConsoleServerPort
  1265. fieldsets = (
  1266. FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
  1267. )
  1268. nullable_fields = ('module', 'label', 'description')
  1269. class PowerPortBulkEditForm(
  1270. ComponentBulkEditForm,
  1271. form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description'])
  1272. ):
  1273. mark_connected = forms.NullBooleanField(
  1274. label=_('Mark connected'),
  1275. required=False,
  1276. widget=BulkEditNullBooleanSelect
  1277. )
  1278. model = PowerPort
  1279. fieldsets = (
  1280. FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
  1281. FieldSet('maximum_draw', 'allocated_draw', name=_('Power')),
  1282. )
  1283. nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')
  1284. class PowerOutletBulkEditForm(
  1285. ComponentBulkEditForm,
  1286. form_from_model(
  1287. PowerOutlet,
  1288. ['label', 'type', 'status', 'color', 'feed_leg', 'power_port', 'mark_connected', 'description']
  1289. )
  1290. ):
  1291. mark_connected = forms.NullBooleanField(
  1292. label=_('Mark connected'),
  1293. required=False,
  1294. widget=BulkEditNullBooleanSelect
  1295. )
  1296. model = PowerOutlet
  1297. fieldsets = (
  1298. FieldSet('module', 'type', 'label', 'status', 'description', 'mark_connected', 'color'),
  1299. FieldSet('feed_leg', 'power_port', name=_('Power')),
  1300. )
  1301. nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
  1302. def __init__(self, *args, **kwargs):
  1303. super().__init__(*args, **kwargs)
  1304. # Limit power_port queryset to PowerPorts which belong to the parent Device
  1305. if self.device_id:
  1306. device = Device.objects.filter(pk=self.device_id).first()
  1307. self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
  1308. else:
  1309. self.fields['power_port'].choices = ()
  1310. self.fields['power_port'].widget.attrs['disabled'] = True
  1311. class InterfaceBulkEditForm(
  1312. ComponentBulkEditForm,
  1313. form_from_model(Interface, [
  1314. 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
  1315. 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1316. 'wireless_lans', 'vlan_translation_policy'
  1317. ])
  1318. ):
  1319. enabled = forms.NullBooleanField(
  1320. label=_('Enabled'),
  1321. required=False,
  1322. widget=BulkEditNullBooleanSelect
  1323. )
  1324. parent = DynamicModelChoiceField(
  1325. label=_('Parent'),
  1326. queryset=Interface.objects.all(),
  1327. required=False,
  1328. query_params={
  1329. 'virtual_chassis_member_id': '$device',
  1330. }
  1331. )
  1332. bridge = DynamicModelChoiceField(
  1333. label=_('Bridge'),
  1334. queryset=Interface.objects.all(),
  1335. required=False,
  1336. query_params={
  1337. 'virtual_chassis_member_id': '$device',
  1338. }
  1339. )
  1340. lag = DynamicModelChoiceField(
  1341. queryset=Interface.objects.all(),
  1342. required=False,
  1343. query_params={
  1344. 'type': 'lag',
  1345. 'virtual_chassis_member_id': '$device',
  1346. },
  1347. label=_('LAG')
  1348. )
  1349. vdcs = DynamicModelMultipleChoiceField(
  1350. queryset=VirtualDeviceContext.objects.all(),
  1351. required=False,
  1352. label=_('Virtual device contexts'),
  1353. query_params={
  1354. 'device_id': '$device',
  1355. }
  1356. )
  1357. speed = PositiveBigIntegerField(
  1358. label=_('Speed'),
  1359. required=False,
  1360. widget=NumberWithOptions(
  1361. options=InterfaceSpeedChoices
  1362. )
  1363. )
  1364. mgmt_only = forms.NullBooleanField(
  1365. required=False,
  1366. widget=BulkEditNullBooleanSelect,
  1367. label=_('Management only')
  1368. )
  1369. poe_mode = ChoiceField(
  1370. choices=add_blank_choice(InterfacePoEModeChoices),
  1371. required=False,
  1372. initial='',
  1373. label=_('PoE mode')
  1374. )
  1375. poe_type = ChoiceField(
  1376. choices=add_blank_choice(InterfacePoETypeChoices),
  1377. required=False,
  1378. initial='',
  1379. label=_('PoE type')
  1380. )
  1381. mark_connected = forms.NullBooleanField(
  1382. label=_('Mark connected'),
  1383. required=False,
  1384. widget=BulkEditNullBooleanSelect
  1385. )
  1386. mode = ChoiceField(
  1387. label=_('Mode'),
  1388. choices=add_blank_choice(InterfaceModeChoices),
  1389. required=False,
  1390. initial=''
  1391. )
  1392. vlan_group = DynamicModelChoiceField(
  1393. queryset=VLANGroup.objects.all(),
  1394. required=False,
  1395. label=_('VLAN group')
  1396. )
  1397. untagged_vlan = DynamicModelChoiceField(
  1398. queryset=VLAN.objects.all(),
  1399. required=False,
  1400. query_params={
  1401. 'group_id': '$vlan_group',
  1402. 'available_on_device': '$device',
  1403. },
  1404. label=_('Untagged VLAN')
  1405. )
  1406. tagged_vlans = DynamicModelMultipleChoiceField(
  1407. queryset=VLAN.objects.all(),
  1408. required=False,
  1409. query_params={
  1410. 'group_id': '$vlan_group',
  1411. 'available_on_device': '$device',
  1412. },
  1413. label=_('Tagged VLANs')
  1414. )
  1415. add_tagged_vlans = DynamicModelMultipleChoiceField(
  1416. label=_('Add tagged VLANs'),
  1417. queryset=VLAN.objects.all(),
  1418. required=False,
  1419. query_params={
  1420. 'group_id': '$vlan_group',
  1421. 'available_on_device': '$device',
  1422. },
  1423. )
  1424. remove_tagged_vlans = DynamicModelMultipleChoiceField(
  1425. label=_('Remove tagged VLANs'),
  1426. queryset=VLAN.objects.all(),
  1427. required=False,
  1428. query_params={
  1429. 'group_id': '$vlan_group',
  1430. 'available_on_device': '$device',
  1431. }
  1432. )
  1433. qinq_svlan = DynamicModelChoiceField(
  1434. queryset=VLAN.objects.all(),
  1435. required=False,
  1436. label=_('Q-in-Q Service VLAN'),
  1437. query_params={
  1438. 'group_id': '$vlan_group',
  1439. 'available_on_device': '$device',
  1440. 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
  1441. }
  1442. )
  1443. vrf = DynamicModelChoiceField(
  1444. queryset=VRF.objects.all(),
  1445. required=False,
  1446. label=_('VRF')
  1447. )
  1448. wireless_lan_group = DynamicModelChoiceField(
  1449. queryset=WirelessLANGroup.objects.all(),
  1450. required=False,
  1451. label=_('Wireless LAN group')
  1452. )
  1453. wireless_lans = DynamicModelMultipleChoiceField(
  1454. queryset=WirelessLAN.objects.all(),
  1455. required=False,
  1456. label=_('Wireless LANs'),
  1457. query_params={
  1458. 'group_id': '$wireless_lan_group',
  1459. }
  1460. )
  1461. model = Interface
  1462. fieldsets = (
  1463. FieldSet('module', 'type', 'label', 'speed', 'duplex', 'description'),
  1464. FieldSet('vrf', 'wwn', name=_('Addressing')),
  1465. FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
  1466. FieldSet('poe_mode', 'poe_type', name=_('PoE')),
  1467. FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
  1468. FieldSet(
  1469. 'mode', 'vlan_group', 'untagged_vlan', 'qinq_svlan', 'vlan_translation_policy', name=_('802.1Q Switching')
  1470. ),
  1471. FieldSet(
  1472. TabbedGroups(
  1473. FieldSet('tagged_vlans', name=_('Assignment')),
  1474. FieldSet('add_tagged_vlans', 'remove_tagged_vlans', name=_('Add/Remove')),
  1475. ),
  1476. ),
  1477. FieldSet(
  1478. 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
  1479. name=_('Wireless')
  1480. ),
  1481. )
  1482. nullable_fields = (
  1483. 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'vdcs', 'mtu', 'description',
  1484. 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
  1485. 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'wireless_lans', 'vlan_translation_policy',
  1486. )
  1487. def __init__(self, *args, **kwargs):
  1488. super().__init__(*args, **kwargs)
  1489. if not self.device_id:
  1490. # See #4523
  1491. if 'pk' in self.initial:
  1492. site = None
  1493. interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')
  1494. # Check interface sites. First interface should set site, further interfaces will either continue the
  1495. # loop or reset back to no site and break the loop.
  1496. for interface in interfaces:
  1497. if site is None:
  1498. site = interface.device.site
  1499. elif interface.device.site is not site:
  1500. site = None
  1501. break
  1502. if site is not None:
  1503. # Query for VLANs assigned to the same site and VLANs with no site assigned (null).
  1504. self.fields['untagged_vlan'].widget.add_query_param(
  1505. 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
  1506. )
  1507. self.fields['tagged_vlans'].widget.add_query_param(
  1508. 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
  1509. )
  1510. self.fields['add_tagged_vlans'].widget.add_query_param(
  1511. 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
  1512. )
  1513. self.fields['remove_tagged_vlans'].widget.add_query_param(
  1514. 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
  1515. )
  1516. self.fields['parent'].choices = ()
  1517. self.fields['parent'].widget.attrs['disabled'] = True
  1518. self.fields['bridge'].choices = ()
  1519. self.fields['bridge'].widget.attrs['disabled'] = True
  1520. self.fields['lag'].choices = ()
  1521. self.fields['lag'].widget.attrs['disabled'] = True
  1522. def clean(self):
  1523. super().clean()
  1524. if not self.cleaned_data['mode']:
  1525. if self.cleaned_data['untagged_vlan']:
  1526. raise forms.ValidationError({'untagged_vlan': _("Interface mode must be specified to assign VLANs")})
  1527. if self.cleaned_data['tagged_vlans']:
  1528. raise forms.ValidationError({'tagged_vlans': _("Interface mode must be specified to assign VLANs")})
  1529. # Untagged interfaces cannot be assigned tagged VLANs
  1530. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
  1531. raise forms.ValidationError({
  1532. 'mode': _("An access interface cannot have tagged VLANs assigned.")
  1533. })
  1534. # Remove all tagged VLAN assignments from "tagged all" interfaces
  1535. elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
  1536. self.cleaned_data['tagged_vlans'] = []
  1537. class FrontPortBulkEditForm(
  1538. ComponentBulkEditForm,
  1539. form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description'])
  1540. ):
  1541. mark_connected = forms.NullBooleanField(
  1542. label=_('Mark connected'),
  1543. required=False,
  1544. widget=BulkEditNullBooleanSelect
  1545. )
  1546. model = FrontPort
  1547. fieldsets = (
  1548. FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
  1549. )
  1550. nullable_fields = ('module', 'label', 'description', 'color')
  1551. class RearPortBulkEditForm(
  1552. ComponentBulkEditForm,
  1553. form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description'])
  1554. ):
  1555. mark_connected = forms.NullBooleanField(
  1556. label=_('Mark connected'),
  1557. required=False,
  1558. widget=BulkEditNullBooleanSelect
  1559. )
  1560. model = RearPort
  1561. fieldsets = (
  1562. FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
  1563. )
  1564. nullable_fields = ('module', 'label', 'description', 'color')
  1565. class ModuleBayBulkEditForm(
  1566. form_from_model(ModuleBay, ['label', 'position', 'enabled', 'description']),
  1567. NetBoxModelBulkEditForm
  1568. ):
  1569. model = ModuleBay
  1570. fieldsets = (
  1571. FieldSet('label', 'position', 'enabled', 'description'),
  1572. )
  1573. nullable_fields = ('label', 'position', 'description')
  1574. class DeviceBayBulkEditForm(
  1575. form_from_model(DeviceBay, ['label', 'enabled', 'description']),
  1576. NetBoxModelBulkEditForm
  1577. ):
  1578. model = DeviceBay
  1579. fieldsets = (
  1580. FieldSet('label', 'enabled', 'description'),
  1581. )
  1582. nullable_fields = ('label', 'description')
  1583. class InventoryItemBulkEditForm(
  1584. form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
  1585. NetBoxModelBulkEditForm
  1586. ):
  1587. device = DynamicModelChoiceField(
  1588. label=_('Device'),
  1589. queryset=Device.objects.all(),
  1590. required=False
  1591. )
  1592. role = DynamicModelChoiceField(
  1593. label=_('Role'),
  1594. queryset=InventoryItemRole.objects.all(),
  1595. required=False
  1596. )
  1597. manufacturer = DynamicModelChoiceField(
  1598. label=_('Manufacturer'),
  1599. queryset=Manufacturer.objects.all(),
  1600. required=False
  1601. )
  1602. status = ChoiceField(
  1603. label=_('Status'),
  1604. choices=add_blank_choice(InventoryItemStatusChoices),
  1605. required=False,
  1606. initial=''
  1607. )
  1608. model = InventoryItem
  1609. fieldsets = (
  1610. FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'status', 'description'),
  1611. )
  1612. nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
  1613. def __init__(self, *args, **kwargs):
  1614. super().__init__(*args, **kwargs)
  1615. # Remove parent device passed as context to avoid conflicts with the actual device field
  1616. # on this form (see bug #19464)
  1617. self.initial.pop('device', None)
  1618. #
  1619. # Device component roles
  1620. #
  1621. class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm):
  1622. color = ColorField(
  1623. label=_('Color'),
  1624. required=False
  1625. )
  1626. model = InventoryItemRole
  1627. fieldsets = (
  1628. FieldSet('color', 'description'),
  1629. )
  1630. nullable_fields = ('color', 'description', 'comments')
  1631. class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
  1632. device = DynamicModelChoiceField(
  1633. label=_('Device'),
  1634. queryset=Device.objects.all(),
  1635. required=False
  1636. )
  1637. status = ChoiceField(
  1638. label=_('Status'),
  1639. required=False,
  1640. choices=add_blank_choice(VirtualDeviceContextStatusChoices)
  1641. )
  1642. tenant = DynamicModelChoiceField(
  1643. label=_('Tenant'),
  1644. queryset=Tenant.objects.all(),
  1645. required=False
  1646. )
  1647. model = VirtualDeviceContext
  1648. fieldsets = (
  1649. FieldSet('device', 'status', 'tenant'),
  1650. )
  1651. nullable_fields = ('device', 'tenant', )
  1652. def __init__(self, *args, **kwargs):
  1653. super().__init__(*args, **kwargs)
  1654. # The ?device=<id> GET param is navigation context (filter), not an intent to change the
  1655. # device field — drop it from initial so Django's changed_data doesn't treat it as an edit.
  1656. self.initial.pop('device', None)
  1657. #
  1658. # Addressing
  1659. #
  1660. class MACAddressBulkEditForm(PrimaryModelBulkEditForm):
  1661. model = MACAddress
  1662. fieldsets = (
  1663. FieldSet('description'),
  1664. )
  1665. nullable_fields = ('description', 'comments')