model_forms.py 49 KB

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