views.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. from django.http import Http404, HttpResponse
  2. from django.shortcuts import get_object_or_404
  3. from drf_spectacular.types import OpenApiTypes
  4. from drf_spectacular.utils import extend_schema, OpenApiParameter
  5. from rest_framework.decorators import action
  6. from rest_framework.renderers import JSONRenderer
  7. from rest_framework.response import Response
  8. from rest_framework.routers import APIRootView
  9. from rest_framework.status import HTTP_400_BAD_REQUEST
  10. from rest_framework.viewsets import ViewSet
  11. from circuits.models import Circuit
  12. from dcim import filtersets
  13. from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
  14. from dcim.models import *
  15. from dcim.svg import CableTraceSVG
  16. from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
  17. from ipam.models import Prefix, VLAN
  18. from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
  19. from netbox.api.metadata import ContentTypeMetadata
  20. from netbox.api.pagination import StripCountAnnotationsPaginator
  21. from netbox.api.renderers import TextRenderer
  22. from netbox.api.viewsets import NetBoxModelViewSet
  23. from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
  24. from netbox.constants import NESTED_SERIALIZER_PREFIX
  25. from utilities.api import get_serializer_for_model
  26. from utilities.utils import count_related
  27. from virtualization.models import VirtualMachine
  28. from . import serializers
  29. from .exceptions import MissingFilterException
  30. class DCIMRootView(APIRootView):
  31. """
  32. DCIM API root view
  33. """
  34. def get_view_name(self):
  35. return 'DCIM'
  36. # Mixins
  37. class PathEndpointMixin(object):
  38. @action(detail=True, url_path='trace')
  39. def trace(self, request, pk):
  40. """
  41. Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
  42. """
  43. obj = get_object_or_404(self.queryset, pk=pk)
  44. # Initialize the path array
  45. path = []
  46. # Render SVG image if requested
  47. if request.GET.get('render', None) == 'svg':
  48. try:
  49. width = int(request.GET.get('width', CABLE_TRACE_SVG_DEFAULT_WIDTH))
  50. except (ValueError, TypeError):
  51. width = CABLE_TRACE_SVG_DEFAULT_WIDTH
  52. drawing = CableTraceSVG(obj, base_url=request.build_absolute_uri('/'), width=width)
  53. return HttpResponse(drawing.render().tostring(), content_type='image/svg+xml')
  54. # Serialize path objects, iterating over each three-tuple in the path
  55. for near_ends, cable, far_ends in obj.trace():
  56. if near_ends:
  57. serializer_a = get_serializer_for_model(near_ends[0], prefix=NESTED_SERIALIZER_PREFIX)
  58. near_ends = serializer_a(near_ends, many=True, context={'request': request}).data
  59. else:
  60. # Path is split; stop here
  61. break
  62. if cable:
  63. cable = serializers.TracedCableSerializer(cable[0], context={'request': request}).data
  64. if far_ends:
  65. serializer_b = get_serializer_for_model(far_ends[0], prefix=NESTED_SERIALIZER_PREFIX)
  66. far_ends = serializer_b(far_ends, many=True, context={'request': request}).data
  67. path.append((near_ends, cable, far_ends))
  68. return Response(path)
  69. class PassThroughPortMixin(object):
  70. @action(detail=True, url_path='paths')
  71. def paths(self, request, pk):
  72. """
  73. Return all CablePaths which traverse a given pass-through port.
  74. """
  75. obj = get_object_or_404(self.queryset, pk=pk)
  76. cablepaths = CablePath.objects.filter(_nodes__contains=obj)
  77. serializer = serializers.CablePathSerializer(cablepaths, context={'request': request}, many=True)
  78. return Response(serializer.data)
  79. #
  80. # Regions
  81. #
  82. class RegionViewSet(NetBoxModelViewSet):
  83. queryset = Region.objects.add_related_count(
  84. Region.objects.all(),
  85. Site,
  86. 'region',
  87. 'site_count',
  88. cumulative=True
  89. ).prefetch_related('tags')
  90. serializer_class = serializers.RegionSerializer
  91. filterset_class = filtersets.RegionFilterSet
  92. #
  93. # Site groups
  94. #
  95. class SiteGroupViewSet(NetBoxModelViewSet):
  96. queryset = SiteGroup.objects.add_related_count(
  97. SiteGroup.objects.all(),
  98. Site,
  99. 'group',
  100. 'site_count',
  101. cumulative=True
  102. ).prefetch_related('tags')
  103. serializer_class = serializers.SiteGroupSerializer
  104. filterset_class = filtersets.SiteGroupFilterSet
  105. #
  106. # Sites
  107. #
  108. class SiteViewSet(NetBoxModelViewSet):
  109. queryset = Site.objects.prefetch_related(
  110. 'region', 'tenant', 'asns', 'tags'
  111. ).annotate(
  112. device_count=count_related(Device, 'site'),
  113. rack_count=count_related(Rack, 'site'),
  114. prefix_count=count_related(Prefix, 'site'),
  115. vlan_count=count_related(VLAN, 'site'),
  116. circuit_count=count_related(Circuit, 'terminations__site'),
  117. virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
  118. )
  119. serializer_class = serializers.SiteSerializer
  120. filterset_class = filtersets.SiteFilterSet
  121. #
  122. # Locations
  123. #
  124. class LocationViewSet(NetBoxModelViewSet):
  125. queryset = Location.objects.add_related_count(
  126. Location.objects.add_related_count(
  127. Location.objects.all(),
  128. Device,
  129. 'location',
  130. 'device_count',
  131. cumulative=True
  132. ),
  133. Rack,
  134. 'location',
  135. 'rack_count',
  136. cumulative=True
  137. ).prefetch_related('site', 'tags')
  138. serializer_class = serializers.LocationSerializer
  139. filterset_class = filtersets.LocationFilterSet
  140. #
  141. # Rack roles
  142. #
  143. class RackRoleViewSet(NetBoxModelViewSet):
  144. queryset = RackRole.objects.prefetch_related('tags').annotate(
  145. rack_count=count_related(Rack, 'role')
  146. )
  147. serializer_class = serializers.RackRoleSerializer
  148. filterset_class = filtersets.RackRoleFilterSet
  149. #
  150. # Racks
  151. #
  152. class RackViewSet(NetBoxModelViewSet):
  153. queryset = Rack.objects.prefetch_related(
  154. 'site', 'location', 'role', 'tenant', 'tags'
  155. ).annotate(
  156. device_count=count_related(Device, 'rack'),
  157. powerfeed_count=count_related(PowerFeed, 'rack')
  158. )
  159. serializer_class = serializers.RackSerializer
  160. filterset_class = filtersets.RackFilterSet
  161. @action(detail=True)
  162. def elevation(self, request, pk=None):
  163. """
  164. Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
  165. """
  166. rack = get_object_or_404(self.queryset, pk=pk)
  167. serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
  168. if not serializer.is_valid():
  169. return Response(serializer.errors, 400)
  170. data = serializer.validated_data
  171. if data['render'] == 'svg':
  172. # Determine attributes for highlighting devices (if any)
  173. highlight_params = []
  174. for param in request.GET.getlist('highlight'):
  175. try:
  176. highlight_params.append(param.split(':', 1))
  177. except ValueError:
  178. pass
  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. else:
  192. # Return a JSON representation of the rack units in the elevation
  193. elevation = rack.get_rack_units(
  194. face=data['face'],
  195. user=request.user,
  196. exclude=data['exclude'],
  197. expand_devices=data['expand_devices']
  198. )
  199. # Enable filtering rack units by ID
  200. q = data['q']
  201. if q:
  202. elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
  203. page = self.paginate_queryset(elevation)
  204. if page is not None:
  205. rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
  206. return self.get_paginated_response(rack_units.data)
  207. #
  208. # Rack reservations
  209. #
  210. class RackReservationViewSet(NetBoxModelViewSet):
  211. queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
  212. serializer_class = serializers.RackReservationSerializer
  213. filterset_class = filtersets.RackReservationFilterSet
  214. #
  215. # Manufacturers
  216. #
  217. class ManufacturerViewSet(NetBoxModelViewSet):
  218. queryset = Manufacturer.objects.prefetch_related('tags').annotate(
  219. devicetype_count=count_related(DeviceType, 'manufacturer'),
  220. inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
  221. platform_count=count_related(Platform, 'manufacturer')
  222. )
  223. serializer_class = serializers.ManufacturerSerializer
  224. filterset_class = filtersets.ManufacturerFilterSet
  225. #
  226. # Device/module types
  227. #
  228. class DeviceTypeViewSet(NetBoxModelViewSet):
  229. queryset = DeviceType.objects.prefetch_related('manufacturer', 'default_platform', 'tags').annotate(
  230. device_count=count_related(Device, 'device_type')
  231. )
  232. serializer_class = serializers.DeviceTypeSerializer
  233. filterset_class = filtersets.DeviceTypeFilterSet
  234. brief_prefetch_fields = ['manufacturer']
  235. class ModuleTypeViewSet(NetBoxModelViewSet):
  236. queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate(
  237. # module_count=count_related(Module, 'module_type')
  238. )
  239. serializer_class = serializers.ModuleTypeSerializer
  240. filterset_class = filtersets.ModuleTypeFilterSet
  241. brief_prefetch_fields = ['manufacturer']
  242. #
  243. # Device type components
  244. #
  245. class ConsolePortTemplateViewSet(NetBoxModelViewSet):
  246. queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
  247. serializer_class = serializers.ConsolePortTemplateSerializer
  248. filterset_class = filtersets.ConsolePortTemplateFilterSet
  249. class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
  250. queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
  251. serializer_class = serializers.ConsoleServerPortTemplateSerializer
  252. filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
  253. class PowerPortTemplateViewSet(NetBoxModelViewSet):
  254. queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
  255. serializer_class = serializers.PowerPortTemplateSerializer
  256. filterset_class = filtersets.PowerPortTemplateFilterSet
  257. class PowerOutletTemplateViewSet(NetBoxModelViewSet):
  258. queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
  259. serializer_class = serializers.PowerOutletTemplateSerializer
  260. filterset_class = filtersets.PowerOutletTemplateFilterSet
  261. class InterfaceTemplateViewSet(NetBoxModelViewSet):
  262. queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
  263. serializer_class = serializers.InterfaceTemplateSerializer
  264. filterset_class = filtersets.InterfaceTemplateFilterSet
  265. class FrontPortTemplateViewSet(NetBoxModelViewSet):
  266. queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
  267. serializer_class = serializers.FrontPortTemplateSerializer
  268. filterset_class = filtersets.FrontPortTemplateFilterSet
  269. class RearPortTemplateViewSet(NetBoxModelViewSet):
  270. queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
  271. serializer_class = serializers.RearPortTemplateSerializer
  272. filterset_class = filtersets.RearPortTemplateFilterSet
  273. class ModuleBayTemplateViewSet(NetBoxModelViewSet):
  274. queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer')
  275. serializer_class = serializers.ModuleBayTemplateSerializer
  276. filterset_class = filtersets.ModuleBayTemplateFilterSet
  277. class DeviceBayTemplateViewSet(NetBoxModelViewSet):
  278. queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
  279. serializer_class = serializers.DeviceBayTemplateSerializer
  280. filterset_class = filtersets.DeviceBayTemplateFilterSet
  281. class InventoryItemTemplateViewSet(NetBoxModelViewSet):
  282. queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
  283. serializer_class = serializers.InventoryItemTemplateSerializer
  284. filterset_class = filtersets.InventoryItemTemplateFilterSet
  285. #
  286. # Device roles
  287. #
  288. class DeviceRoleViewSet(NetBoxModelViewSet):
  289. queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate(
  290. device_count=count_related(Device, 'device_role'),
  291. virtualmachine_count=count_related(VirtualMachine, 'role')
  292. )
  293. serializer_class = serializers.DeviceRoleSerializer
  294. filterset_class = filtersets.DeviceRoleFilterSet
  295. #
  296. # Platforms
  297. #
  298. class PlatformViewSet(NetBoxModelViewSet):
  299. queryset = Platform.objects.prefetch_related('config_template', 'tags').annotate(
  300. device_count=count_related(Device, 'platform'),
  301. virtualmachine_count=count_related(VirtualMachine, 'platform')
  302. )
  303. serializer_class = serializers.PlatformSerializer
  304. filterset_class = filtersets.PlatformFilterSet
  305. #
  306. # Devices/modules
  307. #
  308. class DeviceViewSet(
  309. SequentialBulkCreatesMixin,
  310. ConfigContextQuerySetMixin,
  311. ConfigTemplateRenderMixin,
  312. NetBoxModelViewSet
  313. ):
  314. queryset = Device.objects.prefetch_related(
  315. 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
  316. 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
  317. )
  318. filterset_class = filtersets.DeviceFilterSet
  319. pagination_class = StripCountAnnotationsPaginator
  320. def get_serializer_class(self):
  321. """
  322. Select the specific serializer based on the request context.
  323. If the `brief` query param equates to True, return the NestedDeviceSerializer
  324. If the `exclude` query param includes `config_context` as a value, return the DeviceSerializer
  325. Else, return the DeviceWithConfigContextSerializer
  326. """
  327. request = self.get_serializer_context()['request']
  328. if request.query_params.get('brief', False):
  329. return serializers.NestedDeviceSerializer
  330. elif 'config_context' in request.query_params.get('exclude', []):
  331. return serializers.DeviceSerializer
  332. return serializers.DeviceWithConfigContextSerializer
  333. @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
  334. def render_config(self, request, pk):
  335. """
  336. Resolve and render the preferred ConfigTemplate for this Device.
  337. """
  338. device = self.get_object()
  339. configtemplate = device.get_config_template()
  340. if not configtemplate:
  341. return Response({'error': 'No config template found for this device.'}, status=HTTP_400_BAD_REQUEST)
  342. # Compile context data
  343. context_data = device.get_config_context()
  344. context_data.update(request.data)
  345. context_data.update({'device': device})
  346. return self.render_configtemplate(request, configtemplate, context_data)
  347. class VirtualDeviceContextViewSet(NetBoxModelViewSet):
  348. queryset = VirtualDeviceContext.objects.prefetch_related(
  349. 'device__device_type', 'device', 'tenant', 'tags',
  350. ).annotate(
  351. interface_count=count_related(Interface, 'vdcs'),
  352. )
  353. serializer_class = serializers.VirtualDeviceContextSerializer
  354. filterset_class = filtersets.VirtualDeviceContextFilterSet
  355. class ModuleViewSet(NetBoxModelViewSet):
  356. queryset = Module.objects.prefetch_related(
  357. 'device', 'module_bay', 'module_type__manufacturer', 'tags',
  358. )
  359. serializer_class = serializers.ModuleSerializer
  360. filterset_class = filtersets.ModuleFilterSet
  361. #
  362. # Device components
  363. #
  364. class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  365. queryset = ConsolePort.objects.prefetch_related(
  366. 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
  367. )
  368. serializer_class = serializers.ConsolePortSerializer
  369. filterset_class = filtersets.ConsolePortFilterSet
  370. brief_prefetch_fields = ['device']
  371. class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  372. queryset = ConsoleServerPort.objects.prefetch_related(
  373. 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
  374. )
  375. serializer_class = serializers.ConsoleServerPortSerializer
  376. filterset_class = filtersets.ConsoleServerPortFilterSet
  377. brief_prefetch_fields = ['device']
  378. class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  379. queryset = PowerPort.objects.prefetch_related(
  380. 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
  381. )
  382. serializer_class = serializers.PowerPortSerializer
  383. filterset_class = filtersets.PowerPortFilterSet
  384. brief_prefetch_fields = ['device']
  385. class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
  386. queryset = PowerOutlet.objects.prefetch_related(
  387. 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
  388. )
  389. serializer_class = serializers.PowerOutletSerializer
  390. filterset_class = filtersets.PowerOutletFilterSet
  391. brief_prefetch_fields = ['device']
  392. class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
  393. queryset = Interface.objects.prefetch_related(
  394. 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans',
  395. 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations',
  396. 'vdcs',
  397. )
  398. serializer_class = serializers.InterfaceSerializer
  399. filterset_class = filtersets.InterfaceFilterSet
  400. brief_prefetch_fields = ['device']
  401. class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  402. queryset = FrontPort.objects.prefetch_related(
  403. 'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable__terminations', 'tags'
  404. )
  405. serializer_class = serializers.FrontPortSerializer
  406. filterset_class = filtersets.FrontPortFilterSet
  407. brief_prefetch_fields = ['device']
  408. class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  409. queryset = RearPort.objects.prefetch_related(
  410. 'device__device_type__manufacturer', 'module__module_bay', 'cable__terminations', 'tags'
  411. )
  412. serializer_class = serializers.RearPortSerializer
  413. filterset_class = filtersets.RearPortFilterSet
  414. brief_prefetch_fields = ['device']
  415. class ModuleBayViewSet(NetBoxModelViewSet):
  416. queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module')
  417. serializer_class = serializers.ModuleBaySerializer
  418. filterset_class = filtersets.ModuleBayFilterSet
  419. brief_prefetch_fields = ['device']
  420. class DeviceBayViewSet(NetBoxModelViewSet):
  421. queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags')
  422. serializer_class = serializers.DeviceBaySerializer
  423. filterset_class = filtersets.DeviceBayFilterSet
  424. brief_prefetch_fields = ['device']
  425. class InventoryItemViewSet(NetBoxModelViewSet):
  426. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
  427. serializer_class = serializers.InventoryItemSerializer
  428. filterset_class = filtersets.InventoryItemFilterSet
  429. brief_prefetch_fields = ['device']
  430. #
  431. # Device component roles
  432. #
  433. class InventoryItemRoleViewSet(NetBoxModelViewSet):
  434. queryset = InventoryItemRole.objects.prefetch_related('tags').annotate(
  435. inventoryitem_count=count_related(InventoryItem, 'role')
  436. )
  437. serializer_class = serializers.InventoryItemRoleSerializer
  438. filterset_class = filtersets.InventoryItemRoleFilterSet
  439. #
  440. # Cables
  441. #
  442. class CableViewSet(NetBoxModelViewSet):
  443. queryset = Cable.objects.prefetch_related('terminations__termination')
  444. serializer_class = serializers.CableSerializer
  445. filterset_class = filtersets.CableFilterSet
  446. class CableTerminationViewSet(NetBoxModelViewSet):
  447. metadata_class = ContentTypeMetadata
  448. queryset = CableTermination.objects.prefetch_related('cable', 'termination')
  449. serializer_class = serializers.CableTerminationSerializer
  450. filterset_class = filtersets.CableTerminationFilterSet
  451. #
  452. # Virtual chassis
  453. #
  454. class VirtualChassisViewSet(NetBoxModelViewSet):
  455. queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
  456. member_count=count_related(Device, 'virtual_chassis')
  457. )
  458. serializer_class = serializers.VirtualChassisSerializer
  459. filterset_class = filtersets.VirtualChassisFilterSet
  460. brief_prefetch_fields = ['master']
  461. #
  462. # Power panels
  463. #
  464. class PowerPanelViewSet(NetBoxModelViewSet):
  465. queryset = PowerPanel.objects.prefetch_related(
  466. 'site', 'location'
  467. ).annotate(
  468. powerfeed_count=count_related(PowerFeed, 'power_panel')
  469. )
  470. serializer_class = serializers.PowerPanelSerializer
  471. filterset_class = filtersets.PowerPanelFilterSet
  472. #
  473. # Power feeds
  474. #
  475. class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
  476. queryset = PowerFeed.objects.prefetch_related(
  477. 'power_panel', 'rack', '_path', 'cable__terminations', 'tags'
  478. )
  479. serializer_class = serializers.PowerFeedSerializer
  480. filterset_class = filtersets.PowerFeedFilterSet
  481. #
  482. # Miscellaneous
  483. #
  484. class ConnectedDeviceViewSet(ViewSet):
  485. """
  486. This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer
  487. interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
  488. via a protocol such as LLDP. Two query parameters must be included in the request:
  489. * `peer_device`: The name of the peer device
  490. * `peer_interface`: The name of the peer interface
  491. """
  492. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  493. _device_param = OpenApiParameter(
  494. name='peer_device',
  495. location='query',
  496. description='The name of the peer device',
  497. required=True,
  498. type=OpenApiTypes.STR
  499. )
  500. _interface_param = OpenApiParameter(
  501. name='peer_interface',
  502. location='query',
  503. description='The name of the peer interface',
  504. required=True,
  505. type=OpenApiTypes.STR
  506. )
  507. serializer_class = serializers.DeviceSerializer
  508. def get_view_name(self):
  509. return "Connected Device Locator"
  510. @extend_schema(responses={200: OpenApiTypes.OBJECT})
  511. def list(self, request):
  512. peer_device_name = request.query_params.get(self._device_param.name)
  513. peer_interface_name = request.query_params.get(self._interface_param.name)
  514. if not peer_device_name or not peer_interface_name:
  515. raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
  516. # Determine local endpoint from peer interface's connection
  517. peer_device = get_object_or_404(
  518. Device.objects.restrict(request.user, 'view'),
  519. name=peer_device_name
  520. )
  521. peer_interface = get_object_or_404(
  522. Interface.objects.restrict(request.user, 'view'),
  523. device=peer_device,
  524. name=peer_interface_name
  525. )
  526. endpoints = peer_interface.connected_endpoints
  527. # If an Interface, return the parent device
  528. if endpoints and type(endpoints[0]) is Interface:
  529. device = get_object_or_404(
  530. Device.objects.restrict(request.user, 'view'),
  531. pk=endpoints[0].device_id
  532. )
  533. return Response(serializers.DeviceSerializer(device, context={'request': request}).data)
  534. # Connected endpoint is none or not an Interface
  535. raise Http404