views.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. from django.contrib.contenttypes.prefetch import GenericPrefetch
  2. from django.db.models import FETCH_PEERS, Prefetch
  3. from django.http import Http404, HttpResponse
  4. from django.shortcuts import get_object_or_404
  5. from drf_spectacular.types import OpenApiTypes
  6. from drf_spectacular.utils import OpenApiParameter, extend_schema
  7. from rest_framework.decorators import action
  8. from rest_framework.response import Response
  9. from rest_framework.routers import APIRootView
  10. from rest_framework.viewsets import ViewSet
  11. from dcim import filtersets
  12. from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
  13. from dcim.models import *
  14. from dcim.svg import CableTraceSVG
  15. from extras.api.mixins import ConfigContextQuerySetMixin, RenderConfigMixin
  16. from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
  17. from netbox.api.metadata import ContentTypeMetadata
  18. from netbox.api.pagination import StripCountAnnotationsPaginator
  19. from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
  20. from utilities.api import get_serializer_for_model
  21. from utilities.query import count_related
  22. from utilities.query_functions import CollateAsChar
  23. from virtualization.models import VirtualMachine
  24. from . import serializers
  25. from .exceptions import MissingFilterException
  26. class DCIMRootView(APIRootView):
  27. """
  28. DCIM API root view
  29. """
  30. def get_view_name(self):
  31. return 'DCIM'
  32. # Mixins
  33. class PathEndpointMixin:
  34. @action(detail=True, url_path='trace')
  35. def trace(self, request, pk):
  36. """
  37. Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
  38. """
  39. # Path nodes load as they do for the connected endpoint fields
  40. queryset = self.queryset.prefetch_related(*self.get_field_prefetches({'connected_endpoints'}))
  41. obj = get_object_or_404(queryset, pk=pk)
  42. # Initialize the path array
  43. path = []
  44. # Render SVG image if requested
  45. if request.GET.get('render', None) == 'svg':
  46. try:
  47. width = int(request.GET.get('width', CABLE_TRACE_SVG_DEFAULT_WIDTH))
  48. except (ValueError, TypeError):
  49. width = CABLE_TRACE_SVG_DEFAULT_WIDTH
  50. drawing = CableTraceSVG(obj, base_url=request.build_absolute_uri('/'), width=width)
  51. return HttpResponse(drawing.render().tostring(), content_type='image/svg+xml')
  52. # Serialize path objects, iterating over each three-tuple in the path
  53. for near_ends, cable, far_ends in obj.trace():
  54. if near_ends:
  55. serializer_a = get_serializer_for_model(near_ends[0])
  56. near_ends = serializer_a(near_ends, nested=True, many=True, context={'request': request}).data
  57. else:
  58. # Path is split; stop here
  59. break
  60. if cable:
  61. cable = serializers.TracedCableSerializer(cable[0], context={'request': request}).data
  62. if far_ends:
  63. serializer_b = get_serializer_for_model(far_ends[0])
  64. far_ends = serializer_b(far_ends, nested=True, many=True, context={'request': request}).data
  65. path.append((near_ends, cable, far_ends))
  66. return Response(path)
  67. class PassThroughPortMixin:
  68. @action(detail=True, url_path='paths')
  69. def paths(self, request, pk):
  70. """
  71. Return all CablePaths which traverse a given pass-through port.
  72. """
  73. obj = get_object_or_404(self.queryset, pk=pk)
  74. cablepaths = CablePath.objects.filter(_nodes__contains=obj)
  75. serializer = serializers.CablePathSerializer(cablepaths, context={'request': request}, many=True)
  76. return Response(serializer.data)
  77. #
  78. # Regions
  79. #
  80. class RegionViewSet(NetBoxModelViewSet):
  81. queryset = Region.objects.add_related_count(
  82. Region.objects.all(),
  83. Site,
  84. 'region',
  85. 'site_count',
  86. cumulative=True
  87. )
  88. serializer_class = serializers.RegionSerializer
  89. filterset_class = filtersets.RegionFilterSet
  90. #
  91. # Site groups
  92. #
  93. class SiteGroupViewSet(NetBoxModelViewSet):
  94. queryset = SiteGroup.objects.add_related_count(
  95. SiteGroup.objects.all(),
  96. Site,
  97. 'group',
  98. 'site_count',
  99. cumulative=True
  100. )
  101. serializer_class = serializers.SiteGroupSerializer
  102. filterset_class = filtersets.SiteGroupFilterSet
  103. #
  104. # Sites
  105. #
  106. class SiteViewSet(NetBoxModelViewSet):
  107. queryset = Site.objects.all()
  108. serializer_class = serializers.SiteSerializer
  109. filterset_class = filtersets.SiteFilterSet
  110. #
  111. # Locations
  112. #
  113. class LocationViewSet(NetBoxModelViewSet):
  114. queryset = Location.objects.add_related_count(
  115. Location.objects.add_related_count(
  116. Location.objects.all(),
  117. Device,
  118. 'location',
  119. 'device_count',
  120. cumulative=True
  121. ),
  122. Rack,
  123. 'location',
  124. 'rack_count',
  125. cumulative=True
  126. )
  127. serializer_class = serializers.LocationSerializer
  128. filterset_class = filtersets.LocationFilterSet
  129. #
  130. # Rack groups
  131. #
  132. class RackGroupViewSet(NetBoxModelViewSet):
  133. queryset = RackGroup.objects.all()
  134. serializer_class = serializers.RackGroupSerializer
  135. filterset_class = filtersets.RackGroupFilterSet
  136. #
  137. # Rack roles
  138. #
  139. class RackRoleViewSet(NetBoxModelViewSet):
  140. queryset = RackRole.objects.all()
  141. serializer_class = serializers.RackRoleSerializer
  142. filterset_class = filtersets.RackRoleFilterSet
  143. #
  144. # Rack Types
  145. #
  146. class RackTypeViewSet(NetBoxModelViewSet):
  147. queryset = RackType.objects.all()
  148. serializer_class = serializers.RackTypeSerializer
  149. filterset_class = filtersets.RackTypeFilterSet
  150. #
  151. # Racks
  152. #
  153. class RackViewSet(NetBoxModelViewSet):
  154. queryset = Rack.objects.all()
  155. serializer_class = serializers.RackSerializer
  156. filterset_class = filtersets.RackFilterSet
  157. @extend_schema(
  158. operation_id='dcim_racks_elevation_retrieve',
  159. filters=False,
  160. parameters=[serializers.RackElevationDetailFilterSerializer],
  161. responses={200: serializers.RackUnitSerializer(many=True)}
  162. )
  163. @action(detail=True)
  164. def elevation(self, request, pk=None):
  165. """
  166. Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
  167. """
  168. rack = get_object_or_404(self.queryset, pk=pk)
  169. serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
  170. if not serializer.is_valid():
  171. return Response(serializer.errors, 400)
  172. data = serializer.validated_data
  173. if data['render'] == 'svg':
  174. # Determine attributes for highlighting devices (if any)
  175. highlight_params = []
  176. for param in request.GET.getlist('highlight'):
  177. if ':' in param:
  178. highlight_params.append(param.split(':', 1))
  179. # Render and return the elevation as an SVG drawing with the correct content type
  180. drawing = rack.get_elevation_svg(
  181. face=data['face'],
  182. user=request.user,
  183. unit_width=data['unit_width'],
  184. unit_height=data['unit_height'],
  185. legend_width=data['legend_width'],
  186. include_images=data['include_images'],
  187. base_url=request.build_absolute_uri('/'),
  188. highlight_params=highlight_params
  189. )
  190. return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
  191. # Return a JSON representation of the rack units in the elevation
  192. elevation = rack.get_rack_units(
  193. face=data['face'],
  194. user=request.user,
  195. exclude=data['exclude'],
  196. expand_devices=data['expand_devices']
  197. )
  198. # Enable filtering rack units by ID
  199. if q := data['q']:
  200. q = q.lower()
  201. elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name']).lower()]
  202. page = self.paginate_queryset(elevation)
  203. if page is not None:
  204. rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
  205. return self.get_paginated_response(rack_units.data)
  206. # TODO: This endpoint should always return an HttpResponse/DRF Response; `None` is not a meaningful result.
  207. return None
  208. #
  209. # Rack reservations
  210. #
  211. class RackReservationViewSet(NetBoxModelViewSet):
  212. queryset = RackReservation.objects.all()
  213. serializer_class = serializers.RackReservationSerializer
  214. filterset_class = filtersets.RackReservationFilterSet
  215. #
  216. # Manufacturers
  217. #
  218. class ManufacturerViewSet(NetBoxModelViewSet):
  219. queryset = Manufacturer.objects.all()
  220. serializer_class = serializers.ManufacturerSerializer
  221. filterset_class = filtersets.ManufacturerFilterSet
  222. #
  223. # Device/module types
  224. #
  225. class DeviceTypeViewSet(NetBoxModelViewSet):
  226. queryset = DeviceType.objects.all()
  227. serializer_class = serializers.DeviceTypeSerializer
  228. filterset_class = filtersets.DeviceTypeFilterSet
  229. class ModuleBayTypeViewSet(NetBoxModelViewSet):
  230. queryset = ModuleBayType.objects.all()
  231. serializer_class = serializers.ModuleBayTypeSerializer
  232. filterset_class = filtersets.ModuleBayTypeFilterSet
  233. class ModuleTypeProfileViewSet(NetBoxModelViewSet):
  234. queryset = ModuleTypeProfile.objects.all()
  235. serializer_class = serializers.ModuleTypeProfileSerializer
  236. filterset_class = filtersets.ModuleTypeProfileFilterSet
  237. class ModuleTypeViewSet(NetBoxModelViewSet):
  238. queryset = ModuleType.objects.prefetch_related('module_bay_types__manufacturer')
  239. serializer_class = serializers.ModuleTypeSerializer
  240. filterset_class = filtersets.ModuleTypeFilterSet
  241. #
  242. # Device type components
  243. #
  244. class ConsolePortTemplateViewSet(NetBoxModelViewSet):
  245. queryset = ConsolePortTemplate.objects.all()
  246. serializer_class = serializers.ConsolePortTemplateSerializer
  247. filterset_class = filtersets.ConsolePortTemplateFilterSet
  248. class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
  249. queryset = ConsoleServerPortTemplate.objects.all()
  250. serializer_class = serializers.ConsoleServerPortTemplateSerializer
  251. filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
  252. class PowerPortTemplateViewSet(NetBoxModelViewSet):
  253. queryset = PowerPortTemplate.objects.all()
  254. serializer_class = serializers.PowerPortTemplateSerializer
  255. filterset_class = filtersets.PowerPortTemplateFilterSet
  256. class PowerOutletTemplateViewSet(NetBoxModelViewSet):
  257. queryset = PowerOutletTemplate.objects.all()
  258. serializer_class = serializers.PowerOutletTemplateSerializer
  259. filterset_class = filtersets.PowerOutletTemplateFilterSet
  260. class CoolingIntakeTemplateViewSet(NetBoxModelViewSet):
  261. queryset = CoolingIntakeTemplate.objects.all()
  262. serializer_class = serializers.CoolingIntakeTemplateSerializer
  263. filterset_class = filtersets.CoolingIntakeTemplateFilterSet
  264. class CoolingOutflowTemplateViewSet(NetBoxModelViewSet):
  265. queryset = CoolingOutflowTemplate.objects.all()
  266. serializer_class = serializers.CoolingOutflowTemplateSerializer
  267. filterset_class = filtersets.CoolingOutflowTemplateFilterSet
  268. class InterfaceTemplateViewSet(NetBoxModelViewSet):
  269. queryset = InterfaceTemplate.objects.all()
  270. serializer_class = serializers.InterfaceTemplateSerializer
  271. filterset_class = filtersets.InterfaceTemplateFilterSet
  272. class FrontPortTemplateViewSet(NetBoxModelViewSet):
  273. queryset = FrontPortTemplate.objects.all()
  274. serializer_class = serializers.FrontPortTemplateSerializer
  275. filterset_class = filtersets.FrontPortTemplateFilterSet
  276. class RearPortTemplateViewSet(NetBoxModelViewSet):
  277. queryset = RearPortTemplate.objects.all()
  278. serializer_class = serializers.RearPortTemplateSerializer
  279. filterset_class = filtersets.RearPortTemplateFilterSet
  280. class ModuleBayTemplateViewSet(NetBoxModelViewSet):
  281. queryset = ModuleBayTemplate.objects.prefetch_related('module_bay_types__manufacturer')
  282. serializer_class = serializers.ModuleBayTemplateSerializer
  283. filterset_class = filtersets.ModuleBayTemplateFilterSet
  284. class DeviceBayTemplateViewSet(NetBoxModelViewSet):
  285. queryset = DeviceBayTemplate.objects.all()
  286. serializer_class = serializers.DeviceBayTemplateSerializer
  287. filterset_class = filtersets.DeviceBayTemplateFilterSet
  288. class InventoryItemTemplateViewSet(NetBoxModelViewSet):
  289. queryset = InventoryItemTemplate.objects.all()
  290. serializer_class = serializers.InventoryItemTemplateSerializer
  291. filterset_class = filtersets.InventoryItemTemplateFilterSet
  292. #
  293. # Device roles
  294. #
  295. class DeviceRoleViewSet(NetBoxModelViewSet):
  296. queryset = DeviceRole.objects.add_related_count(
  297. DeviceRole.objects.add_related_count(
  298. DeviceRole.objects.all(),
  299. VirtualMachine,
  300. 'role',
  301. 'virtualmachine_count',
  302. cumulative=True
  303. ),
  304. Device,
  305. 'role',
  306. 'device_count',
  307. cumulative=True
  308. )
  309. serializer_class = serializers.DeviceRoleSerializer
  310. filterset_class = filtersets.DeviceRoleFilterSet
  311. #
  312. # Platforms
  313. #
  314. class PlatformViewSet(NetBoxModelViewSet):
  315. queryset = Platform.objects.add_related_count(
  316. Platform.objects.add_related_count(
  317. Platform.objects.all(),
  318. VirtualMachine,
  319. 'platform',
  320. 'virtualmachine_count',
  321. cumulative=True
  322. ),
  323. Device,
  324. 'platform',
  325. 'device_count',
  326. cumulative=True
  327. )
  328. serializer_class = serializers.PlatformSerializer
  329. filterset_class = filtersets.PlatformFilterSet
  330. #
  331. # Devices/modules
  332. #
  333. class DeviceViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBoxModelViewSet):
  334. queryset = Device.objects.all()
  335. serializer_class = serializers.DeviceSerializer
  336. filterset_class = filtersets.DeviceFilterSet
  337. pagination_class = StripCountAnnotationsPaginator
  338. field_prefetches = {
  339. # Read by Device.__str__() for unnamed devices and by config context rendered without its annotation
  340. ('display', 'config_context'): ('device_type__manufacturer',),
  341. ('parent_device',): ('parent_bay',),
  342. }
  343. class VirtualDeviceContextViewSet(NetBoxModelViewSet):
  344. queryset = VirtualDeviceContext.objects.all()
  345. serializer_class = serializers.VirtualDeviceContextSerializer
  346. filterset_class = filtersets.VirtualDeviceContextFilterSet
  347. class ModuleViewSet(NetBoxModelViewSet):
  348. queryset = Module.objects.prefetch_related(
  349. 'module_bay__module_bay_types',
  350. 'module_type__module_bay_types',
  351. )
  352. serializer_class = serializers.ModuleSerializer
  353. filterset_class = filtersets.ModuleFilterSet
  354. #
  355. # Device components
  356. #
  357. def cable_terminations_prefetch(lookup):
  358. # Objects loaded by a to-many prefetch don't inherit the parent queryset's fetch mode
  359. return Prefetch(lookup, queryset=CableTermination.objects.fetch_mode(FETCH_PEERS))
  360. class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  361. queryset = ConsolePort.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  362. '_path', cable_terminations_prefetch('cable__terminations'),
  363. )
  364. serializer_class = serializers.ConsolePortSerializer
  365. filterset_class = filtersets.ConsolePortFilterSet
  366. class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  367. queryset = ConsoleServerPort.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  368. '_path', cable_terminations_prefetch('cable__terminations'),
  369. )
  370. serializer_class = serializers.ConsoleServerPortSerializer
  371. filterset_class = filtersets.ConsoleServerPortFilterSet
  372. class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  373. queryset = PowerPort.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  374. '_path', cable_terminations_prefetch('cable__terminations'),
  375. )
  376. serializer_class = serializers.PowerPortSerializer
  377. filterset_class = filtersets.PowerPortFilterSet
  378. class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
  379. queryset = PowerOutlet.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  380. '_path', cable_terminations_prefetch('cable__terminations'),
  381. )
  382. serializer_class = serializers.PowerOutletSerializer
  383. filterset_class = filtersets.PowerOutletFilterSet
  384. class CoolingIntakeViewSet(NetBoxModelViewSet):
  385. queryset = CoolingIntake.objects.all()
  386. serializer_class = serializers.CoolingIntakeSerializer
  387. filterset_class = filtersets.CoolingIntakeFilterSet
  388. class CoolingOutflowViewSet(NetBoxModelViewSet):
  389. queryset = CoolingOutflow.objects.all()
  390. serializer_class = serializers.CoolingOutflowSerializer
  391. filterset_class = filtersets.CoolingOutflowFilterSet
  392. class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
  393. queryset = Interface.objects.all()
  394. serializer_class = serializers.InterfaceSerializer
  395. filterset_class = filtersets.InterfaceFilterSet
  396. pagination_class = StripCountAnnotationsPaginator
  397. field_prefetches = {
  398. ('link_peers', 'link_peers_type'): (
  399. GenericPrefetch('cable__terminations__termination', [Interface.objects.select_related('device', 'cable')]),
  400. ),
  401. ('connected_endpoints', 'connected_endpoints_type'): (
  402. GenericPrefetch('_path__path_objects', [Interface.objects.select_related('device', 'cable')]),
  403. 'virtual_circuit_termination',
  404. ),
  405. ('connected_endpoints_reachable',): ('_path',),
  406. ('l2vpn_termination',): ('l2vpn_terminations',),
  407. ('count_ipaddresses',): ('ip_addresses',),
  408. ('count_fhrp_groups',): ('fhrp_group_assignments',),
  409. }
  410. def get_bulk_destroy_queryset(self):
  411. # Ensure child interfaces are deleted prior to their parents
  412. return self.get_queryset().order_by('device', 'parent', CollateAsChar('_name'))
  413. class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  414. queryset = FrontPort.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  415. cable_terminations_prefetch('cable__terminations'),
  416. )
  417. serializer_class = serializers.FrontPortSerializer
  418. filterset_class = filtersets.FrontPortFilterSet
  419. class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  420. queryset = RearPort.objects.fetch_mode(FETCH_PEERS).prefetch_related(
  421. cable_terminations_prefetch('cable__terminations'),
  422. )
  423. serializer_class = serializers.RearPortSerializer
  424. filterset_class = filtersets.RearPortFilterSet
  425. class ModuleBayViewSet(NetBoxModelViewSet):
  426. queryset = ModuleBay.objects.prefetch_related(
  427. 'module_bay_types__manufacturer',
  428. 'installed_module__module_type__module_bay_types',
  429. )
  430. serializer_class = serializers.ModuleBaySerializer
  431. filterset_class = filtersets.ModuleBayFilterSet
  432. class DeviceBayViewSet(NetBoxModelViewSet):
  433. queryset = DeviceBay.objects.all()
  434. serializer_class = serializers.DeviceBaySerializer
  435. filterset_class = filtersets.DeviceBayFilterSet
  436. class InventoryItemViewSet(NetBoxModelViewSet):
  437. queryset = InventoryItem.objects.all()
  438. serializer_class = serializers.InventoryItemSerializer
  439. filterset_class = filtersets.InventoryItemFilterSet
  440. #
  441. # Device component roles
  442. #
  443. class InventoryItemRoleViewSet(NetBoxModelViewSet):
  444. queryset = InventoryItemRole.objects.all()
  445. serializer_class = serializers.InventoryItemRoleSerializer
  446. filterset_class = filtersets.InventoryItemRoleFilterSet
  447. #
  448. # Addressing
  449. #
  450. class MACAddressViewSet(NetBoxModelViewSet):
  451. queryset = MACAddress.objects.all()
  452. serializer_class = serializers.MACAddressSerializer
  453. filterset_class = filtersets.MACAddressFilterSet
  454. #
  455. # Cables
  456. #
  457. class CableViewSet(NetBoxModelViewSet):
  458. queryset = Cable.objects.prefetch_related(
  459. cable_terminations_prefetch('terminations'),
  460. 'terminations__termination',
  461. )
  462. serializer_class = serializers.CableSerializer
  463. filterset_class = filtersets.CableFilterSet
  464. class CableTerminationViewSet(NetBoxReadOnlyModelViewSet):
  465. metadata_class = ContentTypeMetadata
  466. queryset = CableTermination.objects.fetch_mode(FETCH_PEERS)
  467. serializer_class = serializers.CableTerminationSerializer
  468. filterset_class = filtersets.CableTerminationFilterSet
  469. class CableBundleViewSet(NetBoxModelViewSet):
  470. queryset = CableBundle.objects.annotate(
  471. cable_count=count_related(Cable, 'bundle')
  472. )
  473. serializer_class = serializers.CableBundleSerializer
  474. filterset_class = filtersets.CableBundleFilterSet
  475. #
  476. # Virtual chassis
  477. #
  478. class VirtualChassisViewSet(NetBoxModelViewSet):
  479. queryset = VirtualChassis.objects.prefetch_related(
  480. # Prefetch related object for the display of unnamed devices
  481. 'master__virtual_chassis',
  482. )
  483. serializer_class = serializers.VirtualChassisSerializer
  484. filterset_class = filtersets.VirtualChassisFilterSet
  485. #
  486. # Power panels
  487. #
  488. class PowerPanelViewSet(NetBoxModelViewSet):
  489. queryset = PowerPanel.objects.all()
  490. serializer_class = serializers.PowerPanelSerializer
  491. filterset_class = filtersets.PowerPanelFilterSet
  492. #
  493. # Power feeds
  494. #
  495. class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
  496. queryset = PowerFeed.objects.prefetch_related(
  497. '_path', 'cable__terminations',
  498. )
  499. serializer_class = serializers.PowerFeedSerializer
  500. filterset_class = filtersets.PowerFeedFilterSet
  501. #
  502. # Cooling sources
  503. #
  504. class CoolingSourceViewSet(NetBoxModelViewSet):
  505. queryset = CoolingSource.objects.all()
  506. serializer_class = serializers.CoolingSourceSerializer
  507. filterset_class = filtersets.CoolingSourceFilterSet
  508. #
  509. # Cooling feeds
  510. #
  511. class CoolingFeedViewSet(NetBoxModelViewSet):
  512. queryset = CoolingFeed.objects.all()
  513. serializer_class = serializers.CoolingFeedSerializer
  514. filterset_class = filtersets.CoolingFeedFilterSet
  515. #
  516. # Miscellaneous
  517. #
  518. class ConnectedDeviceViewSet(ViewSet):
  519. """
  520. This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer
  521. interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
  522. via a protocol such as LLDP. Two query parameters must be included in the request:
  523. * `peer_device`: The name of the peer device
  524. * `peer_interface`: The name of the peer interface
  525. """
  526. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  527. _device_param = OpenApiParameter(
  528. name='peer_device',
  529. location='query',
  530. description='The name of the peer device',
  531. required=True,
  532. type=OpenApiTypes.STR
  533. )
  534. _interface_param = OpenApiParameter(
  535. name='peer_interface',
  536. location='query',
  537. description='The name of the peer interface',
  538. required=True,
  539. type=OpenApiTypes.STR
  540. )
  541. serializer_class = serializers.DeviceSerializer
  542. def get_view_name(self):
  543. return "Connected Device Locator"
  544. @extend_schema(
  545. parameters=[_device_param, _interface_param],
  546. responses={200: serializers.DeviceSerializer}
  547. )
  548. def list(self, request):
  549. peer_device_name = request.query_params.get(self._device_param.name)
  550. peer_interface_name = request.query_params.get(self._interface_param.name)
  551. if not peer_device_name or not peer_interface_name:
  552. raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
  553. # Determine local endpoint from peer interface's connection
  554. peer_device = get_object_or_404(
  555. Device.objects.restrict(request.user, 'view'),
  556. name=peer_device_name
  557. )
  558. peer_interface = get_object_or_404(
  559. Interface.objects.restrict(request.user, 'view'),
  560. device=peer_device,
  561. name=peer_interface_name
  562. )
  563. endpoints = peer_interface.connected_endpoints
  564. # If an Interface, return the parent device
  565. if endpoints and type(endpoints[0]) is Interface:
  566. device = get_object_or_404(
  567. Device.objects.restrict(request.user, 'view'),
  568. pk=endpoints[0].device_id
  569. )
  570. return Response(serializers.DeviceSerializer(device, context={'request': request}).data)
  571. # Connected endpoint is none or not an Interface
  572. raise Http404