model_forms.py 51 KB

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