models.py 45 KB

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