model_forms.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504
  1. from django import forms
  2. from django.contrib.auth import get_user_model
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.utils.translation import gettext_lazy as _
  5. from timezone_field import TimeZoneFormField
  6. from dcim.choices import *
  7. from dcim.constants import *
  8. from dcim.models import *
  9. from extras.models import ConfigTemplate
  10. from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
  11. from netbox.forms import NetBoxModelForm
  12. from tenancy.forms import TenancyForm
  13. from utilities.forms import add_blank_choice
  14. from utilities.forms.fields import (
  15. CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
  16. NumericArrayField, SlugField,
  17. )
  18. from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
  19. from virtualization.models import Cluster
  20. from wireless.models import WirelessLAN, WirelessLANGroup
  21. from .common import InterfaceCommonForm, ModuleCommonForm
  22. __all__ = (
  23. 'CableForm',
  24. 'ConsolePortForm',
  25. 'ConsolePortTemplateForm',
  26. 'ConsoleServerPortForm',
  27. 'ConsoleServerPortTemplateForm',
  28. 'DeviceBayForm',
  29. 'DeviceBayTemplateForm',
  30. 'DeviceForm',
  31. 'DeviceRoleForm',
  32. 'DeviceTypeForm',
  33. 'DeviceVCMembershipForm',
  34. 'FrontPortForm',
  35. 'FrontPortTemplateForm',
  36. 'InterfaceForm',
  37. 'InterfaceTemplateForm',
  38. 'InventoryItemForm',
  39. 'InventoryItemRoleForm',
  40. 'InventoryItemTemplateForm',
  41. 'LocationForm',
  42. 'ManufacturerForm',
  43. 'ModuleForm',
  44. 'ModuleBayForm',
  45. 'ModuleBayTemplateForm',
  46. 'ModuleTypeForm',
  47. 'PlatformForm',
  48. 'PopulateDeviceBayForm',
  49. 'PowerFeedForm',
  50. 'PowerOutletForm',
  51. 'PowerOutletTemplateForm',
  52. 'PowerPanelForm',
  53. 'PowerPortForm',
  54. 'PowerPortTemplateForm',
  55. 'RackForm',
  56. 'RackReservationForm',
  57. 'RackRoleForm',
  58. 'RearPortForm',
  59. 'RearPortTemplateForm',
  60. 'RegionForm',
  61. 'SiteForm',
  62. 'SiteGroupForm',
  63. 'VCMemberSelectForm',
  64. 'VirtualChassisForm',
  65. 'VirtualDeviceContextForm'
  66. )
  67. class RegionForm(NetBoxModelForm):
  68. parent = DynamicModelChoiceField(
  69. label=_('Parent'),
  70. queryset=Region.objects.all(),
  71. required=False
  72. )
  73. slug = SlugField()
  74. fieldsets = (
  75. (_('Region'), (
  76. 'parent', 'name', 'slug', 'description', 'tags',
  77. )),
  78. )
  79. class Meta:
  80. model = Region
  81. fields = (
  82. 'parent', 'name', 'slug', 'description', 'tags',
  83. )
  84. class SiteGroupForm(NetBoxModelForm):
  85. parent = DynamicModelChoiceField(
  86. label=_('Parent'),
  87. queryset=SiteGroup.objects.all(),
  88. required=False
  89. )
  90. slug = SlugField()
  91. fieldsets = (
  92. (_('Site Group'), (
  93. 'parent', 'name', 'slug', 'description', 'tags',
  94. )),
  95. )
  96. class Meta:
  97. model = SiteGroup
  98. fields = (
  99. 'parent', 'name', 'slug', 'description', 'tags',
  100. )
  101. class SiteForm(TenancyForm, NetBoxModelForm):
  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. asns = DynamicModelMultipleChoiceField(
  113. queryset=ASN.objects.all(),
  114. label=_('ASNs'),
  115. required=False
  116. )
  117. slug = SlugField()
  118. time_zone = TimeZoneFormField(
  119. label=_('Time zone'),
  120. choices=add_blank_choice(TimeZoneFormField().choices),
  121. required=False
  122. )
  123. comments = CommentField()
  124. fieldsets = (
  125. (_('Site'), (
  126. 'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
  127. )),
  128. (_('Tenancy'), ('tenant_group', 'tenant')),
  129. (_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')),
  130. )
  131. class Meta:
  132. model = Site
  133. fields = (
  134. 'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
  135. 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
  136. )
  137. widgets = {
  138. 'physical_address': forms.Textarea(
  139. attrs={
  140. 'rows': 3,
  141. }
  142. ),
  143. 'shipping_address': forms.Textarea(
  144. attrs={
  145. 'rows': 3,
  146. }
  147. ),
  148. }
  149. class LocationForm(TenancyForm, NetBoxModelForm):
  150. site = DynamicModelChoiceField(
  151. label=_('Site'),
  152. queryset=Site.objects.all(),
  153. selector=True
  154. )
  155. parent = DynamicModelChoiceField(
  156. label=_('Parent'),
  157. queryset=Location.objects.all(),
  158. required=False,
  159. query_params={
  160. 'site_id': '$site'
  161. }
  162. )
  163. slug = SlugField()
  164. fieldsets = (
  165. (_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
  166. (_('Tenancy'), ('tenant_group', 'tenant')),
  167. )
  168. class Meta:
  169. model = Location
  170. fields = (
  171. 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'tags',
  172. )
  173. class RackRoleForm(NetBoxModelForm):
  174. slug = SlugField()
  175. fieldsets = (
  176. (_('Rack Role'), (
  177. 'name', 'slug', 'color', 'description', 'tags',
  178. )),
  179. )
  180. class Meta:
  181. model = RackRole
  182. fields = [
  183. 'name', 'slug', 'color', 'description', 'tags',
  184. ]
  185. class RackForm(TenancyForm, NetBoxModelForm):
  186. site = DynamicModelChoiceField(
  187. label=_('Site'),
  188. queryset=Site.objects.all(),
  189. selector=True
  190. )
  191. location = DynamicModelChoiceField(
  192. label=_('Location'),
  193. queryset=Location.objects.all(),
  194. required=False,
  195. query_params={
  196. 'site_id': '$site'
  197. }
  198. )
  199. role = DynamicModelChoiceField(
  200. label=_('Role'),
  201. queryset=RackRole.objects.all(),
  202. required=False
  203. )
  204. comments = CommentField()
  205. class Meta:
  206. model = Rack
  207. fields = [
  208. 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
  209. 'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
  210. 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
  211. ]
  212. class RackReservationForm(TenancyForm, NetBoxModelForm):
  213. rack = DynamicModelChoiceField(
  214. label=_('Rack'),
  215. queryset=Rack.objects.all(),
  216. selector=True
  217. )
  218. units = NumericArrayField(
  219. label=_('Units'),
  220. base_field=forms.IntegerField(),
  221. help_text=_("Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.")
  222. )
  223. user = forms.ModelChoiceField(
  224. label=_('User'),
  225. queryset=get_user_model().objects.order_by(
  226. 'username'
  227. )
  228. )
  229. comments = CommentField()
  230. fieldsets = (
  231. (_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')),
  232. (_('Tenancy'), ('tenant_group', 'tenant')),
  233. )
  234. class Meta:
  235. model = RackReservation
  236. fields = [
  237. 'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
  238. ]
  239. class ManufacturerForm(NetBoxModelForm):
  240. slug = SlugField()
  241. fieldsets = (
  242. (_('Manufacturer'), (
  243. 'name', 'slug', 'description', 'tags',
  244. )),
  245. )
  246. class Meta:
  247. model = Manufacturer
  248. fields = [
  249. 'name', 'slug', 'description', 'tags',
  250. ]
  251. class DeviceTypeForm(NetBoxModelForm):
  252. manufacturer = DynamicModelChoiceField(
  253. label=_('Manufacturer'),
  254. queryset=Manufacturer.objects.all()
  255. )
  256. default_platform = DynamicModelChoiceField(
  257. label=_('Default platform'),
  258. queryset=Platform.objects.all(),
  259. required=False
  260. )
  261. slug = SlugField(
  262. label=_('Slug'),
  263. slug_source='model'
  264. )
  265. comments = CommentField()
  266. fieldsets = (
  267. (_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
  268. (_('Chassis'), (
  269. 'u_height', 'exclude_from_utilization', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
  270. 'weight', 'weight_unit',
  271. )),
  272. (_('Images'), ('front_image', 'rear_image')),
  273. )
  274. class Meta:
  275. model = DeviceType
  276. fields = [
  277. 'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization',
  278. 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
  279. 'description', 'comments', 'tags',
  280. ]
  281. widgets = {
  282. 'front_image': ClearableFileInput(attrs={
  283. 'accept': DEVICETYPE_IMAGE_FORMATS
  284. }),
  285. 'rear_image': ClearableFileInput(attrs={
  286. 'accept': DEVICETYPE_IMAGE_FORMATS
  287. }),
  288. }
  289. class ModuleTypeForm(NetBoxModelForm):
  290. manufacturer = DynamicModelChoiceField(
  291. label=_('Manufacturer'),
  292. queryset=Manufacturer.objects.all()
  293. )
  294. comments = CommentField()
  295. fieldsets = (
  296. (_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')),
  297. (_('Weight'), ('weight', 'weight_unit'))
  298. )
  299. class Meta:
  300. model = ModuleType
  301. fields = [
  302. 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description', 'comments', 'tags',
  303. ]
  304. class DeviceRoleForm(NetBoxModelForm):
  305. config_template = DynamicModelChoiceField(
  306. label=_('Config template'),
  307. queryset=ConfigTemplate.objects.all(),
  308. required=False
  309. )
  310. slug = SlugField()
  311. fieldsets = (
  312. (_('Device Role'), (
  313. 'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
  314. )),
  315. )
  316. class Meta:
  317. model = DeviceRole
  318. fields = [
  319. 'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
  320. ]
  321. class PlatformForm(NetBoxModelForm):
  322. manufacturer = DynamicModelChoiceField(
  323. label=_('Manufacturer'),
  324. queryset=Manufacturer.objects.all(),
  325. required=False
  326. )
  327. config_template = DynamicModelChoiceField(
  328. label=_('Config template'),
  329. queryset=ConfigTemplate.objects.all(),
  330. required=False
  331. )
  332. slug = SlugField(
  333. label=_('Slug'),
  334. max_length=64
  335. )
  336. fieldsets = (
  337. (_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
  338. )
  339. class Meta:
  340. model = Platform
  341. fields = [
  342. 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
  343. ]
  344. class DeviceForm(TenancyForm, NetBoxModelForm):
  345. site = DynamicModelChoiceField(
  346. label=_('Site'),
  347. queryset=Site.objects.all(),
  348. selector=True
  349. )
  350. location = DynamicModelChoiceField(
  351. label=_('Location'),
  352. queryset=Location.objects.all(),
  353. required=False,
  354. query_params={
  355. 'site_id': '$site'
  356. },
  357. initial_params={
  358. 'racks': '$rack'
  359. }
  360. )
  361. rack = DynamicModelChoiceField(
  362. label=_('Rack'),
  363. queryset=Rack.objects.all(),
  364. required=False,
  365. query_params={
  366. 'site_id': '$site',
  367. 'location_id': '$location',
  368. }
  369. )
  370. position = forms.DecimalField(
  371. label=_('Position'),
  372. required=False,
  373. help_text=_("The lowest-numbered unit occupied by the device"),
  374. localize=True,
  375. widget=APISelect(
  376. api_url='/api/dcim/racks/{{rack}}/elevation/',
  377. attrs={
  378. 'ts-disabled-field': 'device',
  379. 'data-dynamic-params': '[{"fieldName":"face","queryParam":"face"}]'
  380. },
  381. )
  382. )
  383. device_type = DynamicModelChoiceField(
  384. label=_('Device type'),
  385. queryset=DeviceType.objects.all(),
  386. context={
  387. 'parent': 'manufacturer',
  388. },
  389. selector=True
  390. )
  391. role = DynamicModelChoiceField(
  392. label=_('Device role'),
  393. queryset=DeviceRole.objects.all()
  394. )
  395. platform = DynamicModelChoiceField(
  396. label=_('Platform'),
  397. queryset=Platform.objects.all(),
  398. required=False,
  399. selector=True
  400. )
  401. cluster = DynamicModelChoiceField(
  402. label=_('Cluster'),
  403. queryset=Cluster.objects.all(),
  404. required=False,
  405. selector=True
  406. )
  407. comments = CommentField()
  408. local_context_data = JSONField(
  409. required=False,
  410. label=''
  411. )
  412. virtual_chassis = DynamicModelChoiceField(
  413. label=_('Virtual chassis'),
  414. queryset=VirtualChassis.objects.all(),
  415. required=False,
  416. context={
  417. 'parent': 'master',
  418. },
  419. selector=True
  420. )
  421. vc_position = forms.IntegerField(
  422. required=False,
  423. label=_('Position'),
  424. help_text=_("The position in the virtual chassis this device is identified by")
  425. )
  426. vc_priority = forms.IntegerField(
  427. required=False,
  428. label=_('Priority'),
  429. help_text=_("The priority of the device in the virtual chassis")
  430. )
  431. config_template = DynamicModelChoiceField(
  432. label=_('Config template'),
  433. queryset=ConfigTemplate.objects.all(),
  434. required=False
  435. )
  436. class Meta:
  437. model = Device
  438. fields = [
  439. 'name', 'role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
  440. 'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster',
  441. 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
  442. 'comments', 'tags', 'local_context_data',
  443. ]
  444. def __init__(self, *args, **kwargs):
  445. super().__init__(*args, **kwargs)
  446. if self.instance.pk:
  447. # Compile list of choices for primary IPv4 and IPv6 addresses
  448. oob_ip_choices = [(None, '---------')]
  449. for family in [4, 6]:
  450. ip_choices = [(None, '---------')]
  451. # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
  452. interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True)
  453. # Collect interface IPs
  454. interface_ips = IPAddress.objects.filter(
  455. address__family=family,
  456. assigned_object_type=ContentType.objects.get_for_model(Interface),
  457. assigned_object_id__in=interface_ids
  458. ).prefetch_related('assigned_object')
  459. if interface_ips:
  460. ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
  461. ip_choices.append(('Interface IPs', ip_list))
  462. oob_ip_choices.extend(ip_list)
  463. # Collect NAT IPs
  464. nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
  465. address__family=family,
  466. nat_inside__assigned_object_type=ContentType.objects.get_for_model(Interface),
  467. nat_inside__assigned_object_id__in=interface_ids
  468. ).prefetch_related('assigned_object')
  469. if nat_ips:
  470. ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
  471. ip_choices.append(('NAT IPs', ip_list))
  472. self.fields['primary_ip{}'.format(family)].choices = ip_choices
  473. self.fields['oob_ip'].choices = oob_ip_choices
  474. # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
  475. # can be flipped from one face to another.
  476. self.fields['position'].widget.add_query_param('exclude', self.instance.pk)
  477. # Disable rack assignment if this is a child device installed in a parent device
  478. if self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
  479. self.fields['site'].disabled = True
  480. self.fields['rack'].disabled = True
  481. self.initial['site'] = self.instance.parent_bay.device.site_id
  482. self.initial['rack'] = self.instance.parent_bay.device.rack_id
  483. else:
  484. # An object that doesn't exist yet can't have any IPs assigned to it
  485. self.fields['primary_ip4'].choices = []
  486. self.fields['primary_ip4'].widget.attrs['readonly'] = True
  487. self.fields['primary_ip6'].choices = []
  488. self.fields['primary_ip6'].widget.attrs['readonly'] = True
  489. self.fields['oob_ip'].choices = []
  490. self.fields['oob_ip'].widget.attrs['readonly'] = True
  491. # Rack position
  492. position = self.data.get('position') or self.initial.get('position')
  493. if position:
  494. self.fields['position'].widget.choices = [(position, f'U{position}')]
  495. class ModuleForm(ModuleCommonForm, NetBoxModelForm):
  496. device = DynamicModelChoiceField(
  497. label=_('Device'),
  498. queryset=Device.objects.all(),
  499. initial_params={
  500. 'modulebays': '$module_bay'
  501. }
  502. )
  503. module_bay = DynamicModelChoiceField(
  504. label=_('Module bay'),
  505. queryset=ModuleBay.objects.all(),
  506. query_params={
  507. 'device_id': '$device'
  508. }
  509. )
  510. module_type = DynamicModelChoiceField(
  511. label=_('Module type'),
  512. queryset=ModuleType.objects.all(),
  513. context={
  514. 'parent': 'manufacturer',
  515. },
  516. selector=True
  517. )
  518. comments = CommentField()
  519. replicate_components = forms.BooleanField(
  520. label=_('Replicate components'),
  521. required=False,
  522. initial=True,
  523. help_text=_("Automatically populate components associated with this module type")
  524. )
  525. adopt_components = forms.BooleanField(
  526. label=_('Adopt components'),
  527. required=False,
  528. initial=False,
  529. help_text=_("Adopt already existing components")
  530. )
  531. fieldsets = (
  532. (_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
  533. (_('Hardware'), (
  534. 'serial', 'asset_tag', 'replicate_components', 'adopt_components',
  535. )),
  536. )
  537. class Meta:
  538. model = Module
  539. fields = [
  540. 'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'tags', 'replicate_components',
  541. 'adopt_components', 'description', 'comments',
  542. ]
  543. def __init__(self, *args, **kwargs):
  544. super().__init__(*args, **kwargs)
  545. if self.instance.pk:
  546. self.fields['device'].disabled = True
  547. self.fields['replicate_components'].initial = False
  548. self.fields['replicate_components'].disabled = True
  549. self.fields['adopt_components'].initial = False
  550. self.fields['adopt_components'].disabled = True
  551. class CableForm(TenancyForm, NetBoxModelForm):
  552. comments = CommentField()
  553. class Meta:
  554. model = Cable
  555. fields = [
  556. 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
  557. 'comments', 'tags',
  558. ]
  559. error_messages = {
  560. 'length': {
  561. 'max_value': _('Maximum length is 32767 (any unit)')
  562. }
  563. }
  564. class PowerPanelForm(NetBoxModelForm):
  565. site = DynamicModelChoiceField(
  566. label=_('Site'),
  567. queryset=Site.objects.all(),
  568. selector=True
  569. )
  570. location = DynamicModelChoiceField(
  571. label=_('Location'),
  572. queryset=Location.objects.all(),
  573. required=False,
  574. query_params={
  575. 'site_id': '$site'
  576. }
  577. )
  578. comments = CommentField()
  579. fieldsets = (
  580. ('Power Panel', ('site', 'location', 'name', 'description', 'tags')),
  581. )
  582. class Meta:
  583. model = PowerPanel
  584. fields = [
  585. 'site', 'location', 'name', 'description', 'comments', 'tags',
  586. ]
  587. class PowerFeedForm(TenancyForm, NetBoxModelForm):
  588. power_panel = DynamicModelChoiceField(
  589. label=_('Power panel'),
  590. queryset=PowerPanel.objects.all(),
  591. selector=True
  592. )
  593. rack = DynamicModelChoiceField(
  594. label=_('Rack'),
  595. queryset=Rack.objects.all(),
  596. required=False,
  597. selector=True
  598. )
  599. comments = CommentField()
  600. fieldsets = (
  601. (_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
  602. (_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
  603. (_('Tenancy'), ('tenant_group', 'tenant')),
  604. )
  605. class Meta:
  606. model = PowerFeed
  607. fields = [
  608. 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
  609. 'max_utilization', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
  610. ]
  611. #
  612. # Virtual chassis
  613. #
  614. class VirtualChassisForm(NetBoxModelForm):
  615. master = forms.ModelChoiceField(
  616. label=_('Master'),
  617. queryset=Device.objects.all(),
  618. required=False,
  619. )
  620. comments = CommentField()
  621. class Meta:
  622. model = VirtualChassis
  623. fields = [
  624. 'name', 'domain', 'master', 'description', 'comments', 'tags',
  625. ]
  626. widgets = {
  627. 'master': SelectWithPK(),
  628. }
  629. def __init__(self, *args, **kwargs):
  630. super().__init__(*args, **kwargs)
  631. self.fields['master'].queryset = Device.objects.filter(virtual_chassis=self.instance)
  632. class DeviceVCMembershipForm(forms.ModelForm):
  633. class Meta:
  634. model = Device
  635. fields = [
  636. 'vc_position', 'vc_priority',
  637. ]
  638. labels = {
  639. 'vc_position': 'Position',
  640. 'vc_priority': 'Priority',
  641. }
  642. def __init__(self, validate_vc_position=False, *args, **kwargs):
  643. super().__init__(*args, **kwargs)
  644. # Require VC position (only required when the Device is a VirtualChassis member)
  645. self.fields['vc_position'].required = True
  646. # Add bootstrap classes to form elements.
  647. self.fields['vc_position'].widget.attrs = {'class': 'form-control'}
  648. self.fields['vc_priority'].widget.attrs = {'class': 'form-control'}
  649. # Validation of vc_position is optional. This is only required when adding a new member to an existing
  650. # VirtualChassis. Otherwise, vc_position validation is handled by BaseVCMemberFormSet.
  651. self.validate_vc_position = validate_vc_position
  652. def clean_vc_position(self):
  653. vc_position = self.cleaned_data['vc_position']
  654. if self.validate_vc_position:
  655. conflicting_members = Device.objects.filter(
  656. virtual_chassis=self.instance.virtual_chassis,
  657. vc_position=vc_position
  658. )
  659. if conflicting_members.exists():
  660. raise forms.ValidationError(
  661. 'A virtual chassis member already exists in position {}.'.format(vc_position)
  662. )
  663. return vc_position
  664. class VCMemberSelectForm(forms.Form):
  665. device = DynamicModelChoiceField(
  666. label=_('Device'),
  667. queryset=Device.objects.all(),
  668. query_params={
  669. 'virtual_chassis_id': 'null',
  670. },
  671. selector=True
  672. )
  673. def clean_device(self):
  674. device = self.cleaned_data['device']
  675. if device.virtual_chassis is not None:
  676. raise forms.ValidationError(
  677. f"Device {device} is already assigned to a virtual chassis."
  678. )
  679. return device
  680. #
  681. # Device component templates
  682. #
  683. class ComponentTemplateForm(forms.ModelForm):
  684. device_type = DynamicModelChoiceField(
  685. label=_('Device type'),
  686. queryset=DeviceType.objects.all(),
  687. context={
  688. 'parent': 'manufacturer',
  689. }
  690. )
  691. def __init__(self, *args, **kwargs):
  692. super().__init__(*args, **kwargs)
  693. # Disable reassignment of DeviceType when editing an existing instance
  694. if self.instance.pk:
  695. self.fields['device_type'].disabled = True
  696. class ModularComponentTemplateForm(ComponentTemplateForm):
  697. device_type = DynamicModelChoiceField(
  698. label=_('Device type'),
  699. queryset=DeviceType.objects.all().all(),
  700. required=False,
  701. context={
  702. 'parent': 'manufacturer',
  703. }
  704. )
  705. module_type = DynamicModelChoiceField(
  706. label=_('Module type'),
  707. queryset=ModuleType.objects.all(),
  708. required=False,
  709. context={
  710. 'parent': 'manufacturer',
  711. }
  712. )
  713. def __init__(self, *args, **kwargs):
  714. super().__init__(*args, **kwargs)
  715. # Disable reassignment of ModuleType when editing an existing instance
  716. if self.instance.pk:
  717. self.fields['module_type'].disabled = True
  718. class ConsolePortTemplateForm(ModularComponentTemplateForm):
  719. fieldsets = (
  720. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
  721. )
  722. class Meta:
  723. model = ConsolePortTemplate
  724. fields = [
  725. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  726. ]
  727. class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
  728. fieldsets = (
  729. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
  730. )
  731. class Meta:
  732. model = ConsoleServerPortTemplate
  733. fields = [
  734. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  735. ]
  736. class PowerPortTemplateForm(ModularComponentTemplateForm):
  737. fieldsets = (
  738. (None, (
  739. 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
  740. )),
  741. )
  742. class Meta:
  743. model = PowerPortTemplate
  744. fields = [
  745. 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
  746. ]
  747. class PowerOutletTemplateForm(ModularComponentTemplateForm):
  748. power_port = DynamicModelChoiceField(
  749. label=_('Power port'),
  750. queryset=PowerPortTemplate.objects.all(),
  751. required=False,
  752. query_params={
  753. 'devicetype_id': '$device_type',
  754. }
  755. )
  756. fieldsets = (
  757. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')),
  758. )
  759. class Meta:
  760. model = PowerOutletTemplate
  761. fields = [
  762. 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
  763. ]
  764. class InterfaceTemplateForm(ModularComponentTemplateForm):
  765. bridge = DynamicModelChoiceField(
  766. label=_('Bridge'),
  767. queryset=InterfaceTemplate.objects.all(),
  768. required=False,
  769. query_params={
  770. 'devicetype_id': '$device_type',
  771. 'moduletype_id': '$module_type',
  772. }
  773. )
  774. fieldsets = (
  775. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
  776. (_('PoE'), ('poe_mode', 'poe_type')),
  777. (_('Wireless'), ('rf_role',)),
  778. )
  779. class Meta:
  780. model = InterfaceTemplate
  781. fields = [
  782. 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role',
  783. ]
  784. class FrontPortTemplateForm(ModularComponentTemplateForm):
  785. rear_port = DynamicModelChoiceField(
  786. label=_('Rear port'),
  787. queryset=RearPortTemplate.objects.all(),
  788. required=False,
  789. query_params={
  790. 'devicetype_id': '$device_type',
  791. 'moduletype_id': '$module_type',
  792. }
  793. )
  794. fieldsets = (
  795. (None, (
  796. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
  797. 'description',
  798. )),
  799. )
  800. class Meta:
  801. model = FrontPortTemplate
  802. fields = [
  803. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
  804. 'description',
  805. ]
  806. class RearPortTemplateForm(ModularComponentTemplateForm):
  807. fieldsets = (
  808. (None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')),
  809. )
  810. class Meta:
  811. model = RearPortTemplate
  812. fields = [
  813. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
  814. ]
  815. class ModuleBayTemplateForm(ComponentTemplateForm):
  816. fieldsets = (
  817. (None, ('device_type', 'name', 'label', 'position', 'description')),
  818. )
  819. class Meta:
  820. model = ModuleBayTemplate
  821. fields = [
  822. 'device_type', 'name', 'label', 'position', 'description',
  823. ]
  824. class DeviceBayTemplateForm(ComponentTemplateForm):
  825. fieldsets = (
  826. (None, ('device_type', 'name', 'label', 'description')),
  827. )
  828. class Meta:
  829. model = DeviceBayTemplate
  830. fields = [
  831. 'device_type', 'name', 'label', 'description',
  832. ]
  833. class InventoryItemTemplateForm(ComponentTemplateForm):
  834. parent = DynamicModelChoiceField(
  835. label=_('Parent'),
  836. queryset=InventoryItemTemplate.objects.all(),
  837. required=False,
  838. query_params={
  839. 'devicetype_id': '$device_type'
  840. }
  841. )
  842. role = DynamicModelChoiceField(
  843. label=_('Role'),
  844. queryset=InventoryItemRole.objects.all(),
  845. required=False
  846. )
  847. manufacturer = DynamicModelChoiceField(
  848. label=_('Manufacturer'),
  849. queryset=Manufacturer.objects.all(),
  850. required=False
  851. )
  852. component_type = ContentTypeChoiceField(
  853. queryset=ContentType.objects.all(),
  854. limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
  855. required=False,
  856. widget=forms.HiddenInput
  857. )
  858. component_id = forms.IntegerField(
  859. required=False,
  860. widget=forms.HiddenInput
  861. )
  862. fieldsets = (
  863. (None, (
  864. 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
  865. 'component_type', 'component_id',
  866. )),
  867. )
  868. class Meta:
  869. model = InventoryItemTemplate
  870. fields = [
  871. 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
  872. 'component_type', 'component_id',
  873. ]
  874. #
  875. # Device components
  876. #
  877. class DeviceComponentForm(NetBoxModelForm):
  878. device = DynamicModelChoiceField(
  879. label=_('Device'),
  880. queryset=Device.objects.all(),
  881. selector=True
  882. )
  883. def __init__(self, *args, **kwargs):
  884. super().__init__(*args, **kwargs)
  885. # Disable reassignment of Device when editing an existing instance
  886. if self.instance.pk:
  887. self.fields['device'].disabled = True
  888. class ModularDeviceComponentForm(DeviceComponentForm):
  889. module = DynamicModelChoiceField(
  890. label=_('Module'),
  891. queryset=Module.objects.all(),
  892. required=False,
  893. query_params={
  894. 'device_id': '$device',
  895. }
  896. )
  897. class ConsolePortForm(ModularDeviceComponentForm):
  898. fieldsets = (
  899. (None, (
  900. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  901. )),
  902. )
  903. class Meta:
  904. model = ConsolePort
  905. fields = [
  906. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  907. ]
  908. class ConsoleServerPortForm(ModularDeviceComponentForm):
  909. fieldsets = (
  910. (None, (
  911. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  912. )),
  913. )
  914. class Meta:
  915. model = ConsoleServerPort
  916. fields = [
  917. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  918. ]
  919. class PowerPortForm(ModularDeviceComponentForm):
  920. fieldsets = (
  921. (None, (
  922. 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
  923. 'description', 'tags',
  924. )),
  925. )
  926. class Meta:
  927. model = PowerPort
  928. fields = [
  929. 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
  930. 'description', 'tags',
  931. ]
  932. class PowerOutletForm(ModularDeviceComponentForm):
  933. power_port = DynamicModelChoiceField(
  934. label=_('Power port'),
  935. queryset=PowerPort.objects.all(),
  936. required=False,
  937. query_params={
  938. 'device_id': '$device',
  939. }
  940. )
  941. fieldsets = (
  942. (None, (
  943. 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
  944. 'tags',
  945. )),
  946. )
  947. class Meta:
  948. model = PowerOutlet
  949. fields = [
  950. 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
  951. 'tags',
  952. ]
  953. class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
  954. vdcs = DynamicModelMultipleChoiceField(
  955. queryset=VirtualDeviceContext.objects.all(),
  956. required=False,
  957. label=_('Virtual device contexts'),
  958. initial_params={
  959. 'interfaces': '$parent',
  960. },
  961. query_params={
  962. 'device_id': '$device',
  963. }
  964. )
  965. parent = DynamicModelChoiceField(
  966. queryset=Interface.objects.all(),
  967. required=False,
  968. label=_('Parent interface'),
  969. query_params={
  970. 'virtual_chassis_member_id': '$device',
  971. }
  972. )
  973. bridge = DynamicModelChoiceField(
  974. queryset=Interface.objects.all(),
  975. required=False,
  976. label=_('Bridged interface'),
  977. query_params={
  978. 'virtual_chassis_member_id': '$device',
  979. }
  980. )
  981. lag = DynamicModelChoiceField(
  982. queryset=Interface.objects.all(),
  983. required=False,
  984. label=_('LAG interface'),
  985. query_params={
  986. 'virtual_chassis_member_id': '$device',
  987. 'type': 'lag',
  988. }
  989. )
  990. wireless_lan_group = DynamicModelChoiceField(
  991. queryset=WirelessLANGroup.objects.all(),
  992. required=False,
  993. label=_('Wireless LAN group')
  994. )
  995. wireless_lans = DynamicModelMultipleChoiceField(
  996. queryset=WirelessLAN.objects.all(),
  997. required=False,
  998. label=_('Wireless LANs'),
  999. query_params={
  1000. 'group_id': '$wireless_lan_group',
  1001. }
  1002. )
  1003. vlan_group = DynamicModelChoiceField(
  1004. queryset=VLANGroup.objects.all(),
  1005. required=False,
  1006. label=_('VLAN group')
  1007. )
  1008. untagged_vlan = DynamicModelChoiceField(
  1009. queryset=VLAN.objects.all(),
  1010. required=False,
  1011. label=_('Untagged VLAN'),
  1012. query_params={
  1013. 'group_id': '$vlan_group',
  1014. 'available_on_device': '$device',
  1015. }
  1016. )
  1017. tagged_vlans = DynamicModelMultipleChoiceField(
  1018. queryset=VLAN.objects.all(),
  1019. required=False,
  1020. label=_('Tagged VLANs'),
  1021. query_params={
  1022. 'group_id': '$vlan_group',
  1023. 'available_on_device': '$device',
  1024. }
  1025. )
  1026. vrf = DynamicModelChoiceField(
  1027. queryset=VRF.objects.all(),
  1028. required=False,
  1029. label=_('VRF')
  1030. )
  1031. wwn = forms.CharField(
  1032. empty_value=None,
  1033. required=False,
  1034. label=_('WWN')
  1035. )
  1036. fieldsets = (
  1037. (_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
  1038. (_('Addressing'), ('vrf', 'mac_address', 'wwn')),
  1039. (_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1040. (_('Related Interfaces'), ('parent', 'bridge', 'lag')),
  1041. (_('PoE'), ('poe_mode', 'poe_type')),
  1042. (_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1043. (_('Wireless'), (
  1044. 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
  1045. )),
  1046. )
  1047. class Meta:
  1048. model = Interface
  1049. fields = [
  1050. 'device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag',
  1051. 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
  1052. 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
  1053. 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
  1054. ]
  1055. widgets = {
  1056. 'speed': NumberWithOptions(
  1057. options=InterfaceSpeedChoices
  1058. ),
  1059. 'mode': HTMXSelect(),
  1060. }
  1061. labels = {
  1062. 'mode': '802.1Q Mode',
  1063. }
  1064. class FrontPortForm(ModularDeviceComponentForm):
  1065. rear_port = DynamicModelChoiceField(
  1066. queryset=RearPort.objects.all(),
  1067. query_params={
  1068. 'device_id': '$device',
  1069. }
  1070. )
  1071. fieldsets = (
  1072. (None, (
  1073. 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
  1074. 'description', 'tags',
  1075. )),
  1076. )
  1077. class Meta:
  1078. model = FrontPort
  1079. fields = [
  1080. 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
  1081. 'description', 'tags',
  1082. ]
  1083. class RearPortForm(ModularDeviceComponentForm):
  1084. fieldsets = (
  1085. (None, (
  1086. 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
  1087. )),
  1088. )
  1089. class Meta:
  1090. model = RearPort
  1091. fields = [
  1092. 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
  1093. ]
  1094. class ModuleBayForm(DeviceComponentForm):
  1095. fieldsets = (
  1096. (None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
  1097. )
  1098. class Meta:
  1099. model = ModuleBay
  1100. fields = [
  1101. 'device', 'name', 'label', 'position', 'description', 'tags',
  1102. ]
  1103. class DeviceBayForm(DeviceComponentForm):
  1104. fieldsets = (
  1105. (None, ('device', 'name', 'label', 'description', 'tags',)),
  1106. )
  1107. class Meta:
  1108. model = DeviceBay
  1109. fields = [
  1110. 'device', 'name', 'label', 'description', 'tags',
  1111. ]
  1112. class PopulateDeviceBayForm(forms.Form):
  1113. installed_device = forms.ModelChoiceField(
  1114. queryset=Device.objects.all(),
  1115. label=_('Child Device'),
  1116. help_text=_("Child devices must first be created and assigned to the site and rack of the parent device.")
  1117. )
  1118. def __init__(self, device_bay, *args, **kwargs):
  1119. super().__init__(*args, **kwargs)
  1120. self.fields['installed_device'].queryset = Device.objects.filter(
  1121. site=device_bay.device.site,
  1122. rack=device_bay.device.rack,
  1123. parent_bay__isnull=True,
  1124. device_type__u_height=0,
  1125. device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
  1126. ).exclude(pk=device_bay.device.pk)
  1127. class InventoryItemForm(DeviceComponentForm):
  1128. parent = DynamicModelChoiceField(
  1129. label=_('Parent'),
  1130. queryset=InventoryItem.objects.all(),
  1131. required=False,
  1132. query_params={
  1133. 'device_id': '$device'
  1134. }
  1135. )
  1136. role = DynamicModelChoiceField(
  1137. label=_('Role'),
  1138. queryset=InventoryItemRole.objects.all(),
  1139. required=False
  1140. )
  1141. manufacturer = DynamicModelChoiceField(
  1142. label=_('Manufacturer'),
  1143. queryset=Manufacturer.objects.all(),
  1144. required=False
  1145. )
  1146. # Assigned component selectors
  1147. consoleport = DynamicModelChoiceField(
  1148. queryset=ConsolePort.objects.all(),
  1149. required=False,
  1150. query_params={
  1151. 'device_id': '$device'
  1152. },
  1153. label=_('Console port')
  1154. )
  1155. consoleserverport = DynamicModelChoiceField(
  1156. queryset=ConsoleServerPort.objects.all(),
  1157. required=False,
  1158. query_params={
  1159. 'device_id': '$device'
  1160. },
  1161. label=_('Console server port')
  1162. )
  1163. frontport = DynamicModelChoiceField(
  1164. queryset=FrontPort.objects.all(),
  1165. required=False,
  1166. query_params={
  1167. 'device_id': '$device'
  1168. },
  1169. label=_('Front port')
  1170. )
  1171. interface = DynamicModelChoiceField(
  1172. queryset=Interface.objects.all(),
  1173. required=False,
  1174. query_params={
  1175. 'device_id': '$device'
  1176. },
  1177. label=_('Interface')
  1178. )
  1179. poweroutlet = DynamicModelChoiceField(
  1180. queryset=PowerOutlet.objects.all(),
  1181. required=False,
  1182. query_params={
  1183. 'device_id': '$device'
  1184. },
  1185. label=_('Power outlet')
  1186. )
  1187. powerport = DynamicModelChoiceField(
  1188. queryset=PowerPort.objects.all(),
  1189. required=False,
  1190. query_params={
  1191. 'device_id': '$device'
  1192. },
  1193. label=_('Power port')
  1194. )
  1195. rearport = DynamicModelChoiceField(
  1196. queryset=RearPort.objects.all(),
  1197. required=False,
  1198. query_params={
  1199. 'device_id': '$device'
  1200. },
  1201. label=_('Rear port')
  1202. )
  1203. fieldsets = (
  1204. (_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
  1205. (_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
  1206. )
  1207. class Meta:
  1208. model = InventoryItem
  1209. fields = [
  1210. 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
  1211. 'description', 'tags',
  1212. ]
  1213. def __init__(self, *args, **kwargs):
  1214. instance = kwargs.get('instance')
  1215. initial = kwargs.get('initial', {}).copy()
  1216. component_type = initial.get('component_type')
  1217. component_id = initial.get('component_id')
  1218. # Used for picking the default active tab for component selection
  1219. self.no_component = True
  1220. if instance:
  1221. # When editing set the initial value for component selectin
  1222. for component_model in ContentType.objects.filter(MODULAR_COMPONENT_MODELS):
  1223. if type(instance.component) is component_model.model_class():
  1224. initial[component_model.model] = instance.component
  1225. self.no_component = False
  1226. break
  1227. elif component_type and component_id:
  1228. # When adding the InventoryItem from a component page
  1229. if content_type := ContentType.objects.filter(MODULAR_COMPONENT_MODELS).filter(pk=component_type).first():
  1230. if component := content_type.model_class().objects.filter(pk=component_id).first():
  1231. initial[content_type.model] = component
  1232. self.no_component = False
  1233. kwargs['initial'] = initial
  1234. super().__init__(*args, **kwargs)
  1235. # Specifically allow editing the device of IntentoryItems
  1236. if self.instance.pk:
  1237. self.fields['device'].disabled = False
  1238. def clean(self):
  1239. super().clean()
  1240. # Handle object assignment
  1241. selected_objects = [
  1242. field for field in (
  1243. 'consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport'
  1244. ) if self.cleaned_data[field]
  1245. ]
  1246. if len(selected_objects) > 1:
  1247. raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
  1248. elif selected_objects:
  1249. self.instance.component = self.cleaned_data[selected_objects[0]]
  1250. else:
  1251. self.instance.component = None
  1252. # Device component roles
  1253. #
  1254. class InventoryItemRoleForm(NetBoxModelForm):
  1255. slug = SlugField()
  1256. fieldsets = (
  1257. (_('Inventory Item Role'), (
  1258. 'name', 'slug', 'color', 'description', 'tags',
  1259. )),
  1260. )
  1261. class Meta:
  1262. model = InventoryItemRole
  1263. fields = [
  1264. 'name', 'slug', 'color', 'description', 'tags',
  1265. ]
  1266. class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
  1267. device = DynamicModelChoiceField(
  1268. label=_('Device'),
  1269. queryset=Device.objects.all(),
  1270. selector=True
  1271. )
  1272. primary_ip4 = DynamicModelChoiceField(
  1273. queryset=IPAddress.objects.all(),
  1274. label=_('Primary IPv4'),
  1275. required=False,
  1276. query_params={
  1277. 'device_id': '$device',
  1278. 'family': '4',
  1279. }
  1280. )
  1281. primary_ip6 = DynamicModelChoiceField(
  1282. queryset=IPAddress.objects.all(),
  1283. label=_('Primary IPv6'),
  1284. required=False,
  1285. query_params={
  1286. 'device_id': '$device',
  1287. 'family': '6',
  1288. }
  1289. )
  1290. fieldsets = (
  1291. (_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
  1292. (_('Tenancy'), ('tenant_group', 'tenant'))
  1293. )
  1294. class Meta:
  1295. model = VirtualDeviceContext
  1296. fields = [
  1297. 'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant',
  1298. 'comments', 'tags'
  1299. ]