bulk_import.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.contrib.postgres.forms.array import SimpleArrayField
  4. from django.core.exceptions import ObjectDoesNotExist
  5. from django.utils.safestring import mark_safe
  6. from django.utils.translation import gettext_lazy as _
  7. from dcim.choices import *
  8. from dcim.constants import *
  9. from dcim.models import *
  10. from extras.models import ConfigTemplate
  11. from ipam.models import VRF
  12. from netbox.forms import NetBoxModelImportForm
  13. from tenancy.models import Tenant
  14. from utilities.forms.fields import (
  15. CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
  16. SlugField,
  17. )
  18. from virtualization.models import Cluster
  19. from wireless.choices import WirelessRoleChoices
  20. from .common import ModuleCommonForm
  21. __all__ = (
  22. 'CableImportForm',
  23. 'ConsolePortImportForm',
  24. 'ConsoleServerPortImportForm',
  25. 'DeviceBayImportForm',
  26. 'DeviceImportForm',
  27. 'DeviceRoleImportForm',
  28. 'DeviceTypeImportForm',
  29. 'FrontPortImportForm',
  30. 'InterfaceImportForm',
  31. 'InventoryItemImportForm',
  32. 'InventoryItemRoleImportForm',
  33. 'LocationImportForm',
  34. 'ManufacturerImportForm',
  35. 'ModuleImportForm',
  36. 'ModuleBayImportForm',
  37. 'ModuleTypeImportForm',
  38. 'PlatformImportForm',
  39. 'PowerFeedImportForm',
  40. 'PowerOutletImportForm',
  41. 'PowerPanelImportForm',
  42. 'PowerPortImportForm',
  43. 'RackImportForm',
  44. 'RackReservationImportForm',
  45. 'RackRoleImportForm',
  46. 'RearPortImportForm',
  47. 'RegionImportForm',
  48. 'SiteImportForm',
  49. 'SiteGroupImportForm',
  50. 'VirtualChassisImportForm',
  51. 'VirtualDeviceContextImportForm'
  52. )
  53. class RegionImportForm(NetBoxModelImportForm):
  54. parent = CSVModelChoiceField(
  55. label=_('Parent'),
  56. queryset=Region.objects.all(),
  57. required=False,
  58. to_field_name='name',
  59. help_text=_('Name of parent region')
  60. )
  61. class Meta:
  62. model = Region
  63. fields = ('name', 'slug', 'parent', 'description', 'tags')
  64. class SiteGroupImportForm(NetBoxModelImportForm):
  65. parent = CSVModelChoiceField(
  66. label=_('Parent'),
  67. queryset=SiteGroup.objects.all(),
  68. required=False,
  69. to_field_name='name',
  70. help_text=_('Name of parent site group')
  71. )
  72. class Meta:
  73. model = SiteGroup
  74. fields = ('name', 'slug', 'parent', 'description')
  75. class SiteImportForm(NetBoxModelImportForm):
  76. status = CSVChoiceField(
  77. label=_('Status'),
  78. choices=SiteStatusChoices,
  79. help_text=_('Operational status')
  80. )
  81. region = CSVModelChoiceField(
  82. label=_('Region'),
  83. queryset=Region.objects.all(),
  84. required=False,
  85. to_field_name='name',
  86. help_text=_('Assigned region')
  87. )
  88. group = CSVModelChoiceField(
  89. label=_('Group'),
  90. queryset=SiteGroup.objects.all(),
  91. required=False,
  92. to_field_name='name',
  93. help_text=_('Assigned group')
  94. )
  95. tenant = CSVModelChoiceField(
  96. label=_('Tenant'),
  97. queryset=Tenant.objects.all(),
  98. required=False,
  99. to_field_name='name',
  100. help_text=_('Assigned tenant')
  101. )
  102. class Meta:
  103. model = Site
  104. fields = (
  105. 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description',
  106. 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags'
  107. )
  108. help_texts = {
  109. 'time_zone': mark_safe(
  110. '{} (<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">{}</a>)'.format(
  111. _('Time zone'), _('available options')
  112. )
  113. )
  114. }
  115. class LocationImportForm(NetBoxModelImportForm):
  116. site = CSVModelChoiceField(
  117. label=_('Site'),
  118. queryset=Site.objects.all(),
  119. to_field_name='name',
  120. help_text=_('Assigned site')
  121. )
  122. parent = CSVModelChoiceField(
  123. label=_('Parent'),
  124. queryset=Location.objects.all(),
  125. required=False,
  126. to_field_name='name',
  127. help_text=_('Parent location'),
  128. error_messages={
  129. 'invalid_choice': _('Location not found.'),
  130. }
  131. )
  132. status = CSVChoiceField(
  133. label=_('Status'),
  134. choices=LocationStatusChoices,
  135. help_text=_('Operational status')
  136. )
  137. tenant = CSVModelChoiceField(
  138. label=_('Tenant'),
  139. queryset=Tenant.objects.all(),
  140. required=False,
  141. to_field_name='name',
  142. help_text=_('Assigned tenant')
  143. )
  144. class Meta:
  145. model = Location
  146. fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description', 'tags')
  147. def __init__(self, data=None, *args, **kwargs):
  148. super().__init__(data, *args, **kwargs)
  149. if data:
  150. # Limit location queryset by assigned site
  151. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  152. self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
  153. class RackRoleImportForm(NetBoxModelImportForm):
  154. slug = SlugField()
  155. class Meta:
  156. model = RackRole
  157. fields = ('name', 'slug', 'color', 'description', 'tags')
  158. help_texts = {
  159. 'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
  160. }
  161. class RackImportForm(NetBoxModelImportForm):
  162. site = CSVModelChoiceField(
  163. label=_('Site'),
  164. queryset=Site.objects.all(),
  165. to_field_name='name'
  166. )
  167. location = CSVModelChoiceField(
  168. label=_('Location'),
  169. queryset=Location.objects.all(),
  170. required=False,
  171. to_field_name='name'
  172. )
  173. tenant = CSVModelChoiceField(
  174. label=_('Tenant'),
  175. queryset=Tenant.objects.all(),
  176. required=False,
  177. to_field_name='name',
  178. help_text=_('Name of assigned tenant')
  179. )
  180. status = CSVChoiceField(
  181. label=_('Status'),
  182. choices=RackStatusChoices,
  183. help_text=_('Operational status')
  184. )
  185. role = CSVModelChoiceField(
  186. label=_('Role'),
  187. queryset=RackRole.objects.all(),
  188. required=False,
  189. to_field_name='name',
  190. help_text=_('Name of assigned role')
  191. )
  192. type = CSVChoiceField(
  193. label=_('Type'),
  194. choices=RackTypeChoices,
  195. required=False,
  196. help_text=_('Rack type')
  197. )
  198. width = forms.ChoiceField(
  199. label=_('Width'),
  200. choices=RackWidthChoices,
  201. help_text=_('Rail-to-rail width (in inches)')
  202. )
  203. outer_unit = CSVChoiceField(
  204. label=_('Outer unit'),
  205. choices=RackDimensionUnitChoices,
  206. required=False,
  207. help_text=_('Unit for outer dimensions')
  208. )
  209. weight_unit = CSVChoiceField(
  210. label=_('Weight unit'),
  211. choices=WeightUnitChoices,
  212. required=False,
  213. help_text=_('Unit for rack weights')
  214. )
  215. class Meta:
  216. model = Rack
  217. fields = (
  218. 'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
  219. 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight',
  220. 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
  221. )
  222. def __init__(self, data=None, *args, **kwargs):
  223. super().__init__(data, *args, **kwargs)
  224. if data:
  225. # Limit location queryset by assigned site
  226. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  227. self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
  228. class RackReservationImportForm(NetBoxModelImportForm):
  229. site = CSVModelChoiceField(
  230. label=_('Site'),
  231. queryset=Site.objects.all(),
  232. to_field_name='name',
  233. help_text=_('Parent site')
  234. )
  235. location = CSVModelChoiceField(
  236. label=_('Location'),
  237. queryset=Location.objects.all(),
  238. to_field_name='name',
  239. required=False,
  240. help_text=_("Rack's location (if any)")
  241. )
  242. rack = CSVModelChoiceField(
  243. label=_('Rack'),
  244. queryset=Rack.objects.all(),
  245. to_field_name='name',
  246. help_text=_('Rack')
  247. )
  248. units = SimpleArrayField(
  249. label=_('Units'),
  250. base_field=forms.IntegerField(),
  251. required=True,
  252. help_text=_('Comma-separated list of individual unit numbers')
  253. )
  254. tenant = CSVModelChoiceField(
  255. label=_('Tenant'),
  256. queryset=Tenant.objects.all(),
  257. required=False,
  258. to_field_name='name',
  259. help_text=_('Assigned tenant')
  260. )
  261. class Meta:
  262. model = RackReservation
  263. fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments', 'tags')
  264. def __init__(self, data=None, *args, **kwargs):
  265. super().__init__(data, *args, **kwargs)
  266. if data:
  267. # Limit location queryset by assigned site
  268. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  269. self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
  270. # Limit rack queryset by assigned site and group
  271. params = {
  272. f"site__{self.fields['site'].to_field_name}": data.get('site'),
  273. f"location__{self.fields['location'].to_field_name}": data.get('location'),
  274. }
  275. self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
  276. class ManufacturerImportForm(NetBoxModelImportForm):
  277. class Meta:
  278. model = Manufacturer
  279. fields = ('name', 'slug', 'description', 'tags')
  280. class DeviceTypeImportForm(NetBoxModelImportForm):
  281. manufacturer = forms.ModelChoiceField(
  282. label=_('Manufacturer'),
  283. queryset=Manufacturer.objects.all(),
  284. to_field_name='name',
  285. help_text=_('The manufacturer which produces this device type')
  286. )
  287. default_platform = forms.ModelChoiceField(
  288. label=_('Default platform'),
  289. queryset=Platform.objects.all(),
  290. to_field_name='name',
  291. required=False,
  292. help_text=_('The default platform for devices of this type (optional)')
  293. )
  294. weight = forms.DecimalField(
  295. label=_('Weight'),
  296. required=False,
  297. help_text=_('Device weight'),
  298. )
  299. weight_unit = CSVChoiceField(
  300. label=_('Weight unit'),
  301. choices=WeightUnitChoices,
  302. required=False,
  303. help_text=_('Unit for device weight')
  304. )
  305. class Meta:
  306. model = DeviceType
  307. fields = [
  308. 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization',
  309. 'is_full_depth', 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', 'tags',
  310. ]
  311. class ModuleTypeImportForm(NetBoxModelImportForm):
  312. manufacturer = forms.ModelChoiceField(
  313. label=_('Manufacturer'),
  314. queryset=Manufacturer.objects.all(),
  315. to_field_name='name'
  316. )
  317. weight = forms.DecimalField(
  318. label=_('Weight'),
  319. required=False,
  320. help_text=_('Module weight'),
  321. )
  322. weight_unit = CSVChoiceField(
  323. label=_('Weight unit'),
  324. choices=WeightUnitChoices,
  325. required=False,
  326. help_text=_('Unit for module weight')
  327. )
  328. class Meta:
  329. model = ModuleType
  330. fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments', 'tags']
  331. class DeviceRoleImportForm(NetBoxModelImportForm):
  332. config_template = CSVModelChoiceField(
  333. label=_('Config template'),
  334. queryset=ConfigTemplate.objects.all(),
  335. to_field_name='name',
  336. required=False,
  337. help_text=_('Config template')
  338. )
  339. slug = SlugField()
  340. class Meta:
  341. model = DeviceRole
  342. fields = ('name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags')
  343. help_texts = {
  344. 'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
  345. }
  346. class PlatformImportForm(NetBoxModelImportForm):
  347. slug = SlugField()
  348. manufacturer = CSVModelChoiceField(
  349. label=_('Manufacturer'),
  350. queryset=Manufacturer.objects.all(),
  351. required=False,
  352. to_field_name='name',
  353. help_text=_('Limit platform assignments to this manufacturer')
  354. )
  355. config_template = CSVModelChoiceField(
  356. label=_('Config template'),
  357. queryset=ConfigTemplate.objects.all(),
  358. to_field_name='name',
  359. required=False,
  360. help_text=_('Config template')
  361. )
  362. class Meta:
  363. model = Platform
  364. fields = (
  365. 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
  366. )
  367. class BaseDeviceImportForm(NetBoxModelImportForm):
  368. role = CSVModelChoiceField(
  369. label=_('Device role'),
  370. queryset=DeviceRole.objects.all(),
  371. to_field_name='name',
  372. help_text=_('Assigned role')
  373. )
  374. tenant = CSVModelChoiceField(
  375. label=_('Tenant'),
  376. queryset=Tenant.objects.all(),
  377. required=False,
  378. to_field_name='name',
  379. help_text=_('Assigned tenant')
  380. )
  381. manufacturer = CSVModelChoiceField(
  382. label=_('Manufacturer'),
  383. queryset=Manufacturer.objects.all(),
  384. to_field_name='name',
  385. help_text=_('Device type manufacturer')
  386. )
  387. device_type = CSVModelChoiceField(
  388. label=_('Device type'),
  389. queryset=DeviceType.objects.all(),
  390. to_field_name='model',
  391. help_text=_('Device type model')
  392. )
  393. platform = CSVModelChoiceField(
  394. label=_('Platform'),
  395. queryset=Platform.objects.all(),
  396. required=False,
  397. to_field_name='name',
  398. help_text=_('Assigned platform')
  399. )
  400. status = CSVChoiceField(
  401. label=_('Status'),
  402. choices=DeviceStatusChoices,
  403. help_text=_('Operational status')
  404. )
  405. virtual_chassis = CSVModelChoiceField(
  406. label=_('Virtual chassis'),
  407. queryset=VirtualChassis.objects.all(),
  408. to_field_name='name',
  409. required=False,
  410. help_text=_('Virtual chassis')
  411. )
  412. cluster = CSVModelChoiceField(
  413. label=_('Cluster'),
  414. queryset=Cluster.objects.all(),
  415. to_field_name='name',
  416. required=False,
  417. help_text=_('Virtualization cluster')
  418. )
  419. class Meta:
  420. fields = []
  421. model = Device
  422. def __init__(self, data=None, *args, **kwargs):
  423. super().__init__(data, *args, **kwargs)
  424. if data:
  425. # Limit device type queryset by manufacturer
  426. params = {f"manufacturer__{self.fields['manufacturer'].to_field_name}": data.get('manufacturer')}
  427. self.fields['device_type'].queryset = self.fields['device_type'].queryset.filter(**params)
  428. class DeviceImportForm(BaseDeviceImportForm):
  429. site = CSVModelChoiceField(
  430. label=_('Site'),
  431. queryset=Site.objects.all(),
  432. to_field_name='name',
  433. help_text=_('Assigned site')
  434. )
  435. location = CSVModelChoiceField(
  436. label=_('Location'),
  437. queryset=Location.objects.all(),
  438. to_field_name='name',
  439. required=False,
  440. help_text=_("Assigned location (if any)")
  441. )
  442. rack = CSVModelChoiceField(
  443. label=_('Rack'),
  444. queryset=Rack.objects.all(),
  445. to_field_name='name',
  446. required=False,
  447. help_text=_("Assigned rack (if any)")
  448. )
  449. face = CSVChoiceField(
  450. label=_('Face'),
  451. choices=DeviceFaceChoices,
  452. required=False,
  453. help_text=_('Mounted rack face')
  454. )
  455. parent = CSVModelChoiceField(
  456. label=_('Parent'),
  457. queryset=Device.objects.all(),
  458. to_field_name='name',
  459. required=False,
  460. help_text=_('Parent device (for child devices)')
  461. )
  462. device_bay = CSVModelChoiceField(
  463. label=_('Device bay'),
  464. queryset=DeviceBay.objects.all(),
  465. to_field_name='name',
  466. required=False,
  467. help_text=_('Device bay in which this device is installed (for child devices)')
  468. )
  469. airflow = CSVChoiceField(
  470. label=_('Airflow'),
  471. choices=DeviceAirflowChoices,
  472. required=False,
  473. help_text=_('Airflow direction')
  474. )
  475. config_template = CSVModelChoiceField(
  476. label=_('Config template'),
  477. queryset=ConfigTemplate.objects.all(),
  478. to_field_name='name',
  479. required=False,
  480. help_text=_('Config template')
  481. )
  482. class Meta(BaseDeviceImportForm.Meta):
  483. fields = [
  484. 'name', 'role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
  485. 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent', 'device_bay', 'airflow',
  486. 'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments',
  487. 'tags',
  488. ]
  489. def __init__(self, data=None, *args, **kwargs):
  490. super().__init__(data, *args, **kwargs)
  491. if data:
  492. # Limit location queryset by assigned site
  493. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  494. self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
  495. self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
  496. # Limit rack queryset by assigned site and location
  497. params = {
  498. f"site__{self.fields['site'].to_field_name}": data.get('site'),
  499. }
  500. if location := data.get('location'):
  501. params.update({
  502. f"location__{self.fields['location'].to_field_name}": location,
  503. })
  504. self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
  505. # Limit device bay queryset by parent device
  506. if parent := data.get('parent'):
  507. params = {f"device__{self.fields['parent'].to_field_name}": parent}
  508. self.fields['device_bay'].queryset = self.fields['device_bay'].queryset.filter(**params)
  509. def clean(self):
  510. super().clean()
  511. # Inherit site and rack from parent device
  512. if parent := self.cleaned_data.get('parent'):
  513. self.instance.site = parent.site
  514. self.instance.rack = parent.rack
  515. # Set parent_bay reverse relationship
  516. if device_bay := self.cleaned_data.get('device_bay'):
  517. self.instance.parent_bay = device_bay
  518. class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
  519. device = CSVModelChoiceField(
  520. label=_('Device'),
  521. queryset=Device.objects.all(),
  522. to_field_name='name',
  523. help_text=_('The device in which this module is installed')
  524. )
  525. module_bay = CSVModelChoiceField(
  526. label=_('Module bay'),
  527. queryset=ModuleBay.objects.all(),
  528. to_field_name='name',
  529. help_text=_('The module bay in which this module is installed')
  530. )
  531. module_type = CSVModelChoiceField(
  532. label=_('Module type'),
  533. queryset=ModuleType.objects.all(),
  534. to_field_name='model',
  535. help_text=_('The type of module')
  536. )
  537. status = CSVChoiceField(
  538. label=_('Status'),
  539. choices=ModuleStatusChoices,
  540. help_text=_('Operational status')
  541. )
  542. replicate_components = forms.BooleanField(
  543. label=_('Replicate components'),
  544. required=False,
  545. help_text=_('Automatically populate components associated with this module type (enabled by default)')
  546. )
  547. adopt_components = forms.BooleanField(
  548. label=_('Adopt components'),
  549. required=False,
  550. help_text=_('Adopt already existing components')
  551. )
  552. class Meta:
  553. model = Module
  554. fields = (
  555. 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'status', 'description', 'comments',
  556. 'replicate_components', 'adopt_components', 'tags',
  557. )
  558. def __init__(self, data=None, *args, **kwargs):
  559. super().__init__(data, *args, **kwargs)
  560. if data:
  561. # Limit module_bay queryset by assigned device
  562. params = {f"device__{self.fields['device'].to_field_name}": data.get('device')}
  563. self.fields['module_bay'].queryset = self.fields['module_bay'].queryset.filter(**params)
  564. def clean_replicate_components(self):
  565. # Make sure replicate_components is True when it's not included in the uploaded data
  566. if 'replicate_components' not in self.data:
  567. return True
  568. else:
  569. return self.cleaned_data['replicate_components']
  570. #
  571. # Device components
  572. #
  573. class ConsolePortImportForm(NetBoxModelImportForm):
  574. device = CSVModelChoiceField(
  575. label=_('Device'),
  576. queryset=Device.objects.all(),
  577. to_field_name='name'
  578. )
  579. type = CSVChoiceField(
  580. label=_('Type'),
  581. choices=ConsolePortTypeChoices,
  582. required=False,
  583. help_text=_('Port type')
  584. )
  585. speed = CSVTypedChoiceField(
  586. label=_('Speed'),
  587. choices=ConsolePortSpeedChoices,
  588. coerce=int,
  589. empty_value=None,
  590. required=False,
  591. help_text=_('Port speed in bps')
  592. )
  593. class Meta:
  594. model = ConsolePort
  595. fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
  596. class ConsoleServerPortImportForm(NetBoxModelImportForm):
  597. device = CSVModelChoiceField(
  598. label=_('Device'),
  599. queryset=Device.objects.all(),
  600. to_field_name='name'
  601. )
  602. type = CSVChoiceField(
  603. label=_('Type'),
  604. choices=ConsolePortTypeChoices,
  605. required=False,
  606. help_text=_('Port type')
  607. )
  608. speed = CSVTypedChoiceField(
  609. label=_('Speed'),
  610. choices=ConsolePortSpeedChoices,
  611. coerce=int,
  612. empty_value=None,
  613. required=False,
  614. help_text=_('Port speed in bps')
  615. )
  616. class Meta:
  617. model = ConsoleServerPort
  618. fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
  619. class PowerPortImportForm(NetBoxModelImportForm):
  620. device = CSVModelChoiceField(
  621. label=_('Device'),
  622. queryset=Device.objects.all(),
  623. to_field_name='name'
  624. )
  625. type = CSVChoiceField(
  626. label=_('Type'),
  627. choices=PowerPortTypeChoices,
  628. required=False,
  629. help_text=_('Port type')
  630. )
  631. class Meta:
  632. model = PowerPort
  633. fields = (
  634. 'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', 'tags'
  635. )
  636. class PowerOutletImportForm(NetBoxModelImportForm):
  637. device = CSVModelChoiceField(
  638. label=_('Device'),
  639. queryset=Device.objects.all(),
  640. to_field_name='name'
  641. )
  642. type = CSVChoiceField(
  643. label=_('Type'),
  644. choices=PowerOutletTypeChoices,
  645. required=False,
  646. help_text=_('Outlet type')
  647. )
  648. power_port = CSVModelChoiceField(
  649. label=_('Power port'),
  650. queryset=PowerPort.objects.all(),
  651. required=False,
  652. to_field_name='name',
  653. help_text=_('Local power port which feeds this outlet')
  654. )
  655. feed_leg = CSVChoiceField(
  656. label=_('Feed leg'),
  657. choices=PowerOutletFeedLegChoices,
  658. required=False,
  659. help_text=_('Electrical phase (for three-phase circuits)')
  660. )
  661. class Meta:
  662. model = PowerOutlet
  663. fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags')
  664. def __init__(self, *args, **kwargs):
  665. super().__init__(*args, **kwargs)
  666. # Limit PowerPort choices to those belonging to this device (or VC master)
  667. if self.is_bound and 'device' in self.data:
  668. try:
  669. device = self.fields['device'].to_python(self.data['device'])
  670. except forms.ValidationError:
  671. device = None
  672. else:
  673. try:
  674. device = self.instance.device
  675. except Device.DoesNotExist:
  676. device = None
  677. if device:
  678. self.fields['power_port'].queryset = PowerPort.objects.filter(
  679. device__in=[device, device.get_vc_master()]
  680. )
  681. else:
  682. self.fields['power_port'].queryset = PowerPort.objects.none()
  683. class InterfaceImportForm(NetBoxModelImportForm):
  684. device = CSVModelChoiceField(
  685. label=_('Device'),
  686. queryset=Device.objects.all(),
  687. to_field_name='name'
  688. )
  689. parent = CSVModelChoiceField(
  690. label=_('Parent'),
  691. queryset=Interface.objects.all(),
  692. required=False,
  693. to_field_name='name',
  694. help_text=_('Parent interface')
  695. )
  696. bridge = CSVModelChoiceField(
  697. label=_('Bridge'),
  698. queryset=Interface.objects.all(),
  699. required=False,
  700. to_field_name='name',
  701. help_text=_('Bridged interface')
  702. )
  703. lag = CSVModelChoiceField(
  704. label=_('Lag'),
  705. queryset=Interface.objects.all(),
  706. required=False,
  707. to_field_name='name',
  708. help_text=_('Parent LAG interface')
  709. )
  710. vdcs = CSVModelMultipleChoiceField(
  711. label=_('Vdcs'),
  712. queryset=VirtualDeviceContext.objects.all(),
  713. required=False,
  714. to_field_name='name',
  715. help_text=mark_safe(
  716. _('VDC names separated by commas, encased with double quotes. Example:') + ' <code>vdc1,vdc2,vdc3</code>'
  717. )
  718. )
  719. type = CSVChoiceField(
  720. label=_('Type'),
  721. choices=InterfaceTypeChoices,
  722. help_text=_('Physical medium')
  723. )
  724. duplex = CSVChoiceField(
  725. label=_('Duplex'),
  726. choices=InterfaceDuplexChoices,
  727. required=False
  728. )
  729. poe_mode = CSVChoiceField(
  730. label=_('Poe mode'),
  731. choices=InterfacePoEModeChoices,
  732. required=False,
  733. help_text=_('PoE mode')
  734. )
  735. poe_type = CSVChoiceField(
  736. label=_('Poe type'),
  737. choices=InterfacePoETypeChoices,
  738. required=False,
  739. help_text=_('PoE type')
  740. )
  741. mode = CSVChoiceField(
  742. label=_('Mode'),
  743. choices=InterfaceModeChoices,
  744. required=False,
  745. help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
  746. )
  747. vrf = CSVModelChoiceField(
  748. label=_('VRF'),
  749. queryset=VRF.objects.all(),
  750. required=False,
  751. to_field_name='rd',
  752. help_text=_('Assigned VRF')
  753. )
  754. rf_role = CSVChoiceField(
  755. label=_('Rf role'),
  756. choices=WirelessRoleChoices,
  757. required=False,
  758. help_text=_('Wireless role (AP/station)')
  759. )
  760. class Meta:
  761. model = Interface
  762. fields = (
  763. 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
  764. 'mark_connected', 'mac_address', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
  765. 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
  766. )
  767. def __init__(self, data=None, *args, **kwargs):
  768. super().__init__(data, *args, **kwargs)
  769. if data:
  770. # Limit choices for parent, bridge, and LAG interfaces to the assigned device
  771. if device := data.get('device'):
  772. params = {
  773. f"device__{self.fields['device'].to_field_name}": device
  774. }
  775. self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
  776. self.fields['bridge'].queryset = self.fields['bridge'].queryset.filter(**params)
  777. self.fields['lag'].queryset = self.fields['lag'].queryset.filter(**params)
  778. self.fields['vdcs'].queryset = self.fields['vdcs'].queryset.filter(**params)
  779. def clean_enabled(self):
  780. # Make sure enabled is True when it's not included in the uploaded data
  781. if 'enabled' not in self.data:
  782. return True
  783. else:
  784. return self.cleaned_data['enabled']
  785. def clean_vdcs(self):
  786. for vdc in self.cleaned_data['vdcs']:
  787. if vdc.device != self.cleaned_data['device']:
  788. raise forms.ValidationError(
  789. _("VDC {vdc} is not assigned to device {device}").format(
  790. vdc=vdc, device=self.cleaned_data['device']
  791. )
  792. )
  793. return self.cleaned_data['vdcs']
  794. class FrontPortImportForm(NetBoxModelImportForm):
  795. device = CSVModelChoiceField(
  796. label=_('Device'),
  797. queryset=Device.objects.all(),
  798. to_field_name='name'
  799. )
  800. rear_port = CSVModelChoiceField(
  801. label=_('Rear port'),
  802. queryset=RearPort.objects.all(),
  803. to_field_name='name',
  804. help_text=_('Corresponding rear port')
  805. )
  806. type = CSVChoiceField(
  807. label=_('Type'),
  808. choices=PortTypeChoices,
  809. help_text=_('Physical medium classification')
  810. )
  811. class Meta:
  812. model = FrontPort
  813. fields = (
  814. 'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position',
  815. 'description', 'tags'
  816. )
  817. def __init__(self, *args, **kwargs):
  818. super().__init__(*args, **kwargs)
  819. # Limit RearPort choices to those belonging to this device (or VC master)
  820. if self.is_bound and 'device' in self.data:
  821. try:
  822. device = self.fields['device'].to_python(self.data['device'])
  823. except forms.ValidationError:
  824. device = None
  825. else:
  826. try:
  827. device = self.instance.device
  828. except Device.DoesNotExist:
  829. device = None
  830. if device:
  831. self.fields['rear_port'].queryset = RearPort.objects.filter(
  832. device__in=[device, device.get_vc_master()]
  833. )
  834. else:
  835. self.fields['rear_port'].queryset = RearPort.objects.none()
  836. class RearPortImportForm(NetBoxModelImportForm):
  837. device = CSVModelChoiceField(
  838. label=_('Device'),
  839. queryset=Device.objects.all(),
  840. to_field_name='name'
  841. )
  842. type = CSVChoiceField(
  843. label=_('Type'),
  844. help_text=_('Physical medium classification'),
  845. choices=PortTypeChoices,
  846. )
  847. class Meta:
  848. model = RearPort
  849. fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
  850. class ModuleBayImportForm(NetBoxModelImportForm):
  851. device = CSVModelChoiceField(
  852. label=_('Device'),
  853. queryset=Device.objects.all(),
  854. to_field_name='name'
  855. )
  856. class Meta:
  857. model = ModuleBay
  858. fields = ('device', 'name', 'label', 'position', 'description', 'tags')
  859. class DeviceBayImportForm(NetBoxModelImportForm):
  860. device = CSVModelChoiceField(
  861. label=_('Device'),
  862. queryset=Device.objects.all(),
  863. to_field_name='name'
  864. )
  865. installed_device = CSVModelChoiceField(
  866. label=_('Installed device'),
  867. queryset=Device.objects.all(),
  868. required=False,
  869. to_field_name='name',
  870. help_text=_('Child device installed within this bay'),
  871. error_messages={
  872. 'invalid_choice': _('Child device not found.'),
  873. }
  874. )
  875. class Meta:
  876. model = DeviceBay
  877. fields = ('device', 'name', 'label', 'installed_device', 'description', 'tags')
  878. def __init__(self, *args, **kwargs):
  879. super().__init__(*args, **kwargs)
  880. # Limit installed device choices to devices of the correct type and location
  881. if self.is_bound and 'device' in self.data:
  882. try:
  883. device = self.fields['device'].to_python(self.data['device'])
  884. except forms.ValidationError:
  885. device = None
  886. else:
  887. try:
  888. device = self.instance.device
  889. except Device.DoesNotExist:
  890. device = None
  891. if device:
  892. self.fields['installed_device'].queryset = Device.objects.filter(
  893. site=device.site,
  894. rack=device.rack,
  895. parent_bay__isnull=True,
  896. device_type__u_height=0,
  897. device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
  898. ).exclude(pk=device.pk)
  899. else:
  900. self.fields['installed_device'].queryset = Device.objects.none()
  901. class InventoryItemImportForm(NetBoxModelImportForm):
  902. device = CSVModelChoiceField(
  903. label=_('Device'),
  904. queryset=Device.objects.all(),
  905. to_field_name='name'
  906. )
  907. role = CSVModelChoiceField(
  908. label=_('Role'),
  909. queryset=InventoryItemRole.objects.all(),
  910. to_field_name='name',
  911. required=False
  912. )
  913. manufacturer = CSVModelChoiceField(
  914. label=_('Manufacturer'),
  915. queryset=Manufacturer.objects.all(),
  916. to_field_name='name',
  917. required=False
  918. )
  919. parent = CSVModelChoiceField(
  920. label=_('Parent'),
  921. queryset=Device.objects.all(),
  922. to_field_name='name',
  923. required=False,
  924. help_text=_('Parent inventory item')
  925. )
  926. component_type = CSVContentTypeField(
  927. label=_('Component type'),
  928. queryset=ContentType.objects.all(),
  929. limit_choices_to=MODULAR_COMPONENT_MODELS,
  930. required=False,
  931. help_text=_('Component Type')
  932. )
  933. component_name = forms.CharField(
  934. label=_('Compnent name'),
  935. required=False,
  936. help_text=_('Component Name')
  937. )
  938. class Meta:
  939. model = InventoryItem
  940. fields = (
  941. 'device', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
  942. 'description', 'tags', 'component_type', 'component_name',
  943. )
  944. def __init__(self, *args, **kwargs):
  945. super().__init__(*args, **kwargs)
  946. # Limit parent choices to inventory items belonging to this device
  947. device = None
  948. if self.is_bound and 'device' in self.data:
  949. try:
  950. device = self.fields['device'].to_python(self.data['device'])
  951. except forms.ValidationError:
  952. pass
  953. if device:
  954. self.fields['parent'].queryset = InventoryItem.objects.filter(device=device)
  955. else:
  956. self.fields['parent'].queryset = InventoryItem.objects.none()
  957. def clean_component_name(self):
  958. content_type = self.cleaned_data.get('component_type')
  959. component_name = self.cleaned_data.get('component_name')
  960. device = self.cleaned_data.get("device")
  961. if not device and hasattr(self, 'instance') and hasattr(self.instance, 'device'):
  962. device = self.instance.device
  963. if not all([device, content_type, component_name]):
  964. return None
  965. model = content_type.model_class()
  966. try:
  967. component = model.objects.get(device=device, name=component_name)
  968. self.instance.component = component
  969. except ObjectDoesNotExist:
  970. raise forms.ValidationError(
  971. _("Component not found: {device} - {component_name}").format(
  972. device=device, component_name=component_name
  973. )
  974. )
  975. #
  976. # Device component roles
  977. #
  978. class InventoryItemRoleImportForm(NetBoxModelImportForm):
  979. slug = SlugField()
  980. class Meta:
  981. model = InventoryItemRole
  982. fields = ('name', 'slug', 'color', 'description')
  983. help_texts = {
  984. 'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
  985. }
  986. #
  987. # Cables
  988. #
  989. class CableImportForm(NetBoxModelImportForm):
  990. # Termination A
  991. side_a_device = CSVModelChoiceField(
  992. label=_('Side A device'),
  993. queryset=Device.objects.all(),
  994. to_field_name='name',
  995. help_text=_('Device name')
  996. )
  997. side_a_type = CSVContentTypeField(
  998. label=_('Side A type'),
  999. queryset=ContentType.objects.all(),
  1000. limit_choices_to=CABLE_TERMINATION_MODELS,
  1001. help_text=_('Termination type')
  1002. )
  1003. side_a_name = forms.CharField(
  1004. label=_('Side A name'),
  1005. help_text=_('Termination name')
  1006. )
  1007. # Termination B
  1008. side_b_device = CSVModelChoiceField(
  1009. label=_('Side B device'),
  1010. queryset=Device.objects.all(),
  1011. to_field_name='name',
  1012. help_text=_('Device name')
  1013. )
  1014. side_b_type = CSVContentTypeField(
  1015. label=_('Side B type'),
  1016. queryset=ContentType.objects.all(),
  1017. limit_choices_to=CABLE_TERMINATION_MODELS,
  1018. help_text=_('Termination type')
  1019. )
  1020. side_b_name = forms.CharField(
  1021. label=_('Side B name'),
  1022. help_text=_('Termination name')
  1023. )
  1024. # Cable attributes
  1025. status = CSVChoiceField(
  1026. label=_('Status'),
  1027. choices=LinkStatusChoices,
  1028. required=False,
  1029. help_text=_('Connection status')
  1030. )
  1031. type = CSVChoiceField(
  1032. label=_('Type'),
  1033. choices=CableTypeChoices,
  1034. required=False,
  1035. help_text=_('Physical medium classification')
  1036. )
  1037. tenant = CSVModelChoiceField(
  1038. label=_('Tenant'),
  1039. queryset=Tenant.objects.all(),
  1040. required=False,
  1041. to_field_name='name',
  1042. help_text=_('Assigned tenant')
  1043. )
  1044. length_unit = CSVChoiceField(
  1045. label=_('Length unit'),
  1046. choices=CableLengthUnitChoices,
  1047. required=False,
  1048. help_text=_('Length unit')
  1049. )
  1050. class Meta:
  1051. model = Cable
  1052. fields = [
  1053. 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
  1054. 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
  1055. ]
  1056. help_texts = {
  1057. 'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
  1058. }
  1059. def _clean_side(self, side):
  1060. """
  1061. Derive a Cable's A/B termination objects.
  1062. :param side: 'a' or 'b'
  1063. """
  1064. assert side in 'ab', f"Invalid side designation: {side}"
  1065. device = self.cleaned_data.get(f'side_{side}_device')
  1066. content_type = self.cleaned_data.get(f'side_{side}_type')
  1067. name = self.cleaned_data.get(f'side_{side}_name')
  1068. if not device or not content_type or not name:
  1069. return None
  1070. model = content_type.model_class()
  1071. try:
  1072. if device.virtual_chassis and device.virtual_chassis.master == device and \
  1073. model.objects.filter(device=device, name=name).count() == 0:
  1074. termination_object = model.objects.get(device__in=device.virtual_chassis.members.all(), name=name)
  1075. else:
  1076. termination_object = model.objects.get(device=device, name=name)
  1077. if termination_object.cable is not None and termination_object.cable != self.instance:
  1078. raise forms.ValidationError(
  1079. _("Side {side_upper}: {device} {termination_object} is already connected").format(
  1080. side_upper=side.upper(), device=device, termination_object=termination_object
  1081. )
  1082. )
  1083. except ObjectDoesNotExist:
  1084. raise forms.ValidationError(
  1085. _("{side_upper} side termination not found: {device} {name}").format(
  1086. side_upper=side.upper(), device=device, name=name
  1087. )
  1088. )
  1089. setattr(self.instance, f'{side}_terminations', [termination_object])
  1090. return termination_object
  1091. def clean_side_a_name(self):
  1092. return self._clean_side('a')
  1093. def clean_side_b_name(self):
  1094. return self._clean_side('b')
  1095. def clean_length_unit(self):
  1096. # Avoid trying to save as NULL
  1097. length_unit = self.cleaned_data.get('length_unit', None)
  1098. return length_unit if length_unit is not None else ''
  1099. #
  1100. # Virtual chassis
  1101. #
  1102. class VirtualChassisImportForm(NetBoxModelImportForm):
  1103. master = CSVModelChoiceField(
  1104. label=_('Master'),
  1105. queryset=Device.objects.all(),
  1106. to_field_name='name',
  1107. required=False,
  1108. help_text=_('Master device')
  1109. )
  1110. class Meta:
  1111. model = VirtualChassis
  1112. fields = ('name', 'domain', 'master', 'description', 'comments', 'tags')
  1113. #
  1114. # Power
  1115. #
  1116. class PowerPanelImportForm(NetBoxModelImportForm):
  1117. site = CSVModelChoiceField(
  1118. label=_('Site'),
  1119. queryset=Site.objects.all(),
  1120. to_field_name='name',
  1121. help_text=_('Name of parent site')
  1122. )
  1123. location = CSVModelChoiceField(
  1124. label=_('Location'),
  1125. queryset=Location.objects.all(),
  1126. required=False,
  1127. to_field_name='name'
  1128. )
  1129. class Meta:
  1130. model = PowerPanel
  1131. fields = ('site', 'location', 'name', 'description', 'comments', 'tags')
  1132. def __init__(self, data=None, *args, **kwargs):
  1133. super().__init__(data, *args, **kwargs)
  1134. if data:
  1135. # Limit group queryset by assigned site
  1136. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  1137. self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
  1138. class PowerFeedImportForm(NetBoxModelImportForm):
  1139. site = CSVModelChoiceField(
  1140. label=_('Site'),
  1141. queryset=Site.objects.all(),
  1142. to_field_name='name',
  1143. help_text=_('Assigned site')
  1144. )
  1145. power_panel = CSVModelChoiceField(
  1146. label=_('Power panel'),
  1147. queryset=PowerPanel.objects.all(),
  1148. to_field_name='name',
  1149. help_text=_('Upstream power panel')
  1150. )
  1151. location = CSVModelChoiceField(
  1152. label=_('Location'),
  1153. queryset=Location.objects.all(),
  1154. to_field_name='name',
  1155. required=False,
  1156. help_text=_("Rack's location (if any)")
  1157. )
  1158. rack = CSVModelChoiceField(
  1159. label=_('Rack'),
  1160. queryset=Rack.objects.all(),
  1161. to_field_name='name',
  1162. required=False,
  1163. help_text=_('Rack')
  1164. )
  1165. tenant = CSVModelChoiceField(
  1166. queryset=Tenant.objects.all(),
  1167. to_field_name='name',
  1168. required=False,
  1169. help_text=_('Assigned tenant')
  1170. )
  1171. status = CSVChoiceField(
  1172. label=_('Status'),
  1173. choices=PowerFeedStatusChoices,
  1174. help_text=_('Operational status')
  1175. )
  1176. type = CSVChoiceField(
  1177. label=_('Type'),
  1178. choices=PowerFeedTypeChoices,
  1179. help_text=_('Primary or redundant')
  1180. )
  1181. supply = CSVChoiceField(
  1182. label=_('Supply'),
  1183. choices=PowerFeedSupplyChoices,
  1184. help_text=_('Supply type (AC/DC)')
  1185. )
  1186. phase = CSVChoiceField(
  1187. label=_('Phase'),
  1188. choices=PowerFeedPhaseChoices,
  1189. help_text=_('Single or three-phase')
  1190. )
  1191. class Meta:
  1192. model = PowerFeed
  1193. fields = (
  1194. 'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
  1195. 'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'comments', 'tags',
  1196. )
  1197. def __init__(self, data=None, *args, **kwargs):
  1198. super().__init__(data, *args, **kwargs)
  1199. if data:
  1200. # Limit power_panel queryset by site
  1201. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  1202. self.fields['power_panel'].queryset = self.fields['power_panel'].queryset.filter(**params)
  1203. # Limit location queryset by site
  1204. params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
  1205. self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
  1206. # Limit rack queryset by site and group
  1207. params = {
  1208. f"site__{self.fields['site'].to_field_name}": data.get('site'),
  1209. f"location__{self.fields['location'].to_field_name}": data.get('location'),
  1210. }
  1211. self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
  1212. class VirtualDeviceContextImportForm(NetBoxModelImportForm):
  1213. device = CSVModelChoiceField(
  1214. label=_('Device'),
  1215. queryset=Device.objects.all(),
  1216. to_field_name='name',
  1217. help_text='Assigned role'
  1218. )
  1219. tenant = CSVModelChoiceField(
  1220. label=_('Tenant'),
  1221. queryset=Tenant.objects.all(),
  1222. required=False,
  1223. to_field_name='name',
  1224. help_text='Assigned tenant'
  1225. )
  1226. status = CSVChoiceField(
  1227. label=_('Status'),
  1228. choices=VirtualDeviceContextStatusChoices,
  1229. )
  1230. class Meta:
  1231. fields = [
  1232. 'name', 'device', 'status', 'tenant', 'identifier', 'comments',
  1233. ]
  1234. model = VirtualDeviceContext