bulk_edit.py 51 KB

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