| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- from django.http import Http404, HttpResponse
- from django.shortcuts import get_object_or_404
- from drf_spectacular.types import OpenApiTypes
- from drf_spectacular.utils import extend_schema, OpenApiParameter
- from rest_framework.decorators import action
- from rest_framework.renderers import JSONRenderer
- from rest_framework.response import Response
- from rest_framework.routers import APIRootView
- from rest_framework.status import HTTP_400_BAD_REQUEST
- from rest_framework.viewsets import ViewSet
- from circuits.models import Circuit
- from dcim import filtersets
- from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
- from dcim.models import *
- from dcim.svg import CableTraceSVG
- from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
- from ipam.models import Prefix, VLAN
- from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
- from netbox.api.metadata import ContentTypeMetadata
- from netbox.api.pagination import StripCountAnnotationsPaginator
- from netbox.api.renderers import TextRenderer
- from netbox.api.viewsets import NetBoxModelViewSet
- from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
- from netbox.constants import NESTED_SERIALIZER_PREFIX
- from utilities.api import get_serializer_for_model
- from utilities.utils import count_related
- from virtualization.models import VirtualMachine
- from . import serializers
- from .exceptions import MissingFilterException
- class DCIMRootView(APIRootView):
- """
- DCIM API root view
- """
- def get_view_name(self):
- return 'DCIM'
- # Mixins
- class PathEndpointMixin(object):
- @action(detail=True, url_path='trace')
- def trace(self, request, pk):
- """
- Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
- """
- obj = get_object_or_404(self.queryset, pk=pk)
- # Initialize the path array
- path = []
- # Render SVG image if requested
- if request.GET.get('render', None) == 'svg':
- try:
- width = int(request.GET.get('width', CABLE_TRACE_SVG_DEFAULT_WIDTH))
- except (ValueError, TypeError):
- width = CABLE_TRACE_SVG_DEFAULT_WIDTH
- drawing = CableTraceSVG(obj, base_url=request.build_absolute_uri('/'), width=width)
- return HttpResponse(drawing.render().tostring(), content_type='image/svg+xml')
- # Serialize path objects, iterating over each three-tuple in the path
- for near_ends, cable, far_ends in obj.trace():
- if near_ends:
- serializer_a = get_serializer_for_model(near_ends[0], prefix=NESTED_SERIALIZER_PREFIX)
- near_ends = serializer_a(near_ends, many=True, context={'request': request}).data
- else:
- # Path is split; stop here
- break
- if cable:
- cable = serializers.TracedCableSerializer(cable[0], context={'request': request}).data
- if far_ends:
- serializer_b = get_serializer_for_model(far_ends[0], prefix=NESTED_SERIALIZER_PREFIX)
- far_ends = serializer_b(far_ends, many=True, context={'request': request}).data
- path.append((near_ends, cable, far_ends))
- return Response(path)
- class PassThroughPortMixin(object):
- @action(detail=True, url_path='paths')
- def paths(self, request, pk):
- """
- Return all CablePaths which traverse a given pass-through port.
- """
- obj = get_object_or_404(self.queryset, pk=pk)
- cablepaths = CablePath.objects.filter(_nodes__contains=obj)
- serializer = serializers.CablePathSerializer(cablepaths, context={'request': request}, many=True)
- return Response(serializer.data)
- #
- # Regions
- #
- class RegionViewSet(NetBoxModelViewSet):
- queryset = Region.objects.add_related_count(
- Region.objects.all(),
- Site,
- 'region',
- 'site_count',
- cumulative=True
- ).prefetch_related('tags')
- serializer_class = serializers.RegionSerializer
- filterset_class = filtersets.RegionFilterSet
- #
- # Site groups
- #
- class SiteGroupViewSet(NetBoxModelViewSet):
- queryset = SiteGroup.objects.add_related_count(
- SiteGroup.objects.all(),
- Site,
- 'group',
- 'site_count',
- cumulative=True
- ).prefetch_related('tags')
- serializer_class = serializers.SiteGroupSerializer
- filterset_class = filtersets.SiteGroupFilterSet
- #
- # Sites
- #
- class SiteViewSet(NetBoxModelViewSet):
- queryset = Site.objects.prefetch_related(
- 'region', 'tenant', 'asns', 'tags'
- ).annotate(
- device_count=count_related(Device, 'site'),
- rack_count=count_related(Rack, 'site'),
- prefix_count=count_related(Prefix, 'site'),
- vlan_count=count_related(VLAN, 'site'),
- circuit_count=count_related(Circuit, 'terminations__site'),
- virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
- )
- serializer_class = serializers.SiteSerializer
- filterset_class = filtersets.SiteFilterSet
- #
- # Locations
- #
- class LocationViewSet(NetBoxModelViewSet):
- queryset = Location.objects.add_related_count(
- Location.objects.add_related_count(
- Location.objects.all(),
- Device,
- 'location',
- 'device_count',
- cumulative=True
- ),
- Rack,
- 'location',
- 'rack_count',
- cumulative=True
- ).prefetch_related('site', 'tags')
- serializer_class = serializers.LocationSerializer
- filterset_class = filtersets.LocationFilterSet
- #
- # Rack roles
- #
- class RackRoleViewSet(NetBoxModelViewSet):
- queryset = RackRole.objects.prefetch_related('tags').annotate(
- rack_count=count_related(Rack, 'role')
- )
- serializer_class = serializers.RackRoleSerializer
- filterset_class = filtersets.RackRoleFilterSet
- #
- # Racks
- #
- class RackViewSet(NetBoxModelViewSet):
- queryset = Rack.objects.prefetch_related(
- 'site', 'location', 'role', 'tenant', 'tags'
- ).annotate(
- device_count=count_related(Device, 'rack'),
- powerfeed_count=count_related(PowerFeed, 'rack')
- )
- serializer_class = serializers.RackSerializer
- filterset_class = filtersets.RackFilterSet
- @action(detail=True)
- def elevation(self, request, pk=None):
- """
- Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
- """
- rack = get_object_or_404(self.queryset, pk=pk)
- serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
- if not serializer.is_valid():
- return Response(serializer.errors, 400)
- data = serializer.validated_data
- if data['render'] == 'svg':
- # Determine attributes for highlighting devices (if any)
- highlight_params = []
- for param in request.GET.getlist('highlight'):
- try:
- highlight_params.append(param.split(':', 1))
- except ValueError:
- pass
- # Render and return the elevation as an SVG drawing with the correct content type
- drawing = rack.get_elevation_svg(
- face=data['face'],
- user=request.user,
- unit_width=data['unit_width'],
- unit_height=data['unit_height'],
- legend_width=data['legend_width'],
- include_images=data['include_images'],
- base_url=request.build_absolute_uri('/'),
- highlight_params=highlight_params
- )
- return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
- else:
- # Return a JSON representation of the rack units in the elevation
- elevation = rack.get_rack_units(
- face=data['face'],
- user=request.user,
- exclude=data['exclude'],
- expand_devices=data['expand_devices']
- )
- # Enable filtering rack units by ID
- q = data['q']
- if q:
- elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
- page = self.paginate_queryset(elevation)
- if page is not None:
- rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
- return self.get_paginated_response(rack_units.data)
- #
- # Rack reservations
- #
- class RackReservationViewSet(NetBoxModelViewSet):
- queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
- serializer_class = serializers.RackReservationSerializer
- filterset_class = filtersets.RackReservationFilterSet
- #
- # Manufacturers
- #
- class ManufacturerViewSet(NetBoxModelViewSet):
- queryset = Manufacturer.objects.prefetch_related('tags').annotate(
- devicetype_count=count_related(DeviceType, 'manufacturer'),
- inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
- platform_count=count_related(Platform, 'manufacturer')
- )
- serializer_class = serializers.ManufacturerSerializer
- filterset_class = filtersets.ManufacturerFilterSet
- #
- # Device/module types
- #
- class DeviceTypeViewSet(NetBoxModelViewSet):
- queryset = DeviceType.objects.prefetch_related('manufacturer', 'default_platform', 'tags').annotate(
- device_count=count_related(Device, 'device_type')
- )
- serializer_class = serializers.DeviceTypeSerializer
- filterset_class = filtersets.DeviceTypeFilterSet
- brief_prefetch_fields = ['manufacturer']
- class ModuleTypeViewSet(NetBoxModelViewSet):
- queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate(
- # module_count=count_related(Module, 'module_type')
- )
- serializer_class = serializers.ModuleTypeSerializer
- filterset_class = filtersets.ModuleTypeFilterSet
- brief_prefetch_fields = ['manufacturer']
- #
- # Device type components
- #
- class ConsolePortTemplateViewSet(NetBoxModelViewSet):
- queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.ConsolePortTemplateSerializer
- filterset_class = filtersets.ConsolePortTemplateFilterSet
- class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
- queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.ConsoleServerPortTemplateSerializer
- filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
- class PowerPortTemplateViewSet(NetBoxModelViewSet):
- queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.PowerPortTemplateSerializer
- filterset_class = filtersets.PowerPortTemplateFilterSet
- class PowerOutletTemplateViewSet(NetBoxModelViewSet):
- queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.PowerOutletTemplateSerializer
- filterset_class = filtersets.PowerOutletTemplateFilterSet
- class InterfaceTemplateViewSet(NetBoxModelViewSet):
- queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.InterfaceTemplateSerializer
- filterset_class = filtersets.InterfaceTemplateFilterSet
- class FrontPortTemplateViewSet(NetBoxModelViewSet):
- queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.FrontPortTemplateSerializer
- filterset_class = filtersets.FrontPortTemplateFilterSet
- class RearPortTemplateViewSet(NetBoxModelViewSet):
- queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.RearPortTemplateSerializer
- filterset_class = filtersets.RearPortTemplateFilterSet
- class ModuleBayTemplateViewSet(NetBoxModelViewSet):
- queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.ModuleBayTemplateSerializer
- filterset_class = filtersets.ModuleBayTemplateFilterSet
- class DeviceBayTemplateViewSet(NetBoxModelViewSet):
- queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
- serializer_class = serializers.DeviceBayTemplateSerializer
- filterset_class = filtersets.DeviceBayTemplateFilterSet
- class InventoryItemTemplateViewSet(NetBoxModelViewSet):
- queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
- serializer_class = serializers.InventoryItemTemplateSerializer
- filterset_class = filtersets.InventoryItemTemplateFilterSet
- #
- # Device roles
- #
- class DeviceRoleViewSet(NetBoxModelViewSet):
- queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate(
- device_count=count_related(Device, 'device_role'),
- virtualmachine_count=count_related(VirtualMachine, 'role')
- )
- serializer_class = serializers.DeviceRoleSerializer
- filterset_class = filtersets.DeviceRoleFilterSet
- #
- # Platforms
- #
- class PlatformViewSet(NetBoxModelViewSet):
- queryset = Platform.objects.prefetch_related('config_template', 'tags').annotate(
- device_count=count_related(Device, 'platform'),
- virtualmachine_count=count_related(VirtualMachine, 'platform')
- )
- serializer_class = serializers.PlatformSerializer
- filterset_class = filtersets.PlatformFilterSet
- #
- # Devices/modules
- #
- class DeviceViewSet(
- SequentialBulkCreatesMixin,
- ConfigContextQuerySetMixin,
- ConfigTemplateRenderMixin,
- NetBoxModelViewSet
- ):
- queryset = Device.objects.prefetch_related(
- 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
- 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
- )
- filterset_class = filtersets.DeviceFilterSet
- pagination_class = StripCountAnnotationsPaginator
- def get_serializer_class(self):
- """
- Select the specific serializer based on the request context.
- If the `brief` query param equates to True, return the NestedDeviceSerializer
- If the `exclude` query param includes `config_context` as a value, return the DeviceSerializer
- Else, return the DeviceWithConfigContextSerializer
- """
- request = self.get_serializer_context()['request']
- if request.query_params.get('brief', False):
- return serializers.NestedDeviceSerializer
- elif 'config_context' in request.query_params.get('exclude', []):
- return serializers.DeviceSerializer
- return serializers.DeviceWithConfigContextSerializer
- @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
- def render_config(self, request, pk):
- """
- Resolve and render the preferred ConfigTemplate for this Device.
- """
- device = self.get_object()
- configtemplate = device.get_config_template()
- if not configtemplate:
- return Response({'error': 'No config template found for this device.'}, status=HTTP_400_BAD_REQUEST)
- # Compile context data
- context_data = device.get_config_context()
- context_data.update(request.data)
- context_data.update({'device': device})
- return self.render_configtemplate(request, configtemplate, context_data)
- class VirtualDeviceContextViewSet(NetBoxModelViewSet):
- queryset = VirtualDeviceContext.objects.prefetch_related(
- 'device__device_type', 'device', 'tenant', 'tags',
- ).annotate(
- interface_count=count_related(Interface, 'vdcs'),
- )
- serializer_class = serializers.VirtualDeviceContextSerializer
- filterset_class = filtersets.VirtualDeviceContextFilterSet
- class ModuleViewSet(NetBoxModelViewSet):
- queryset = Module.objects.prefetch_related(
- 'device', 'module_bay', 'module_type__manufacturer', 'tags',
- )
- serializer_class = serializers.ModuleSerializer
- filterset_class = filtersets.ModuleFilterSet
- #
- # Device components
- #
- class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = ConsolePort.objects.prefetch_related(
- 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.ConsolePortSerializer
- filterset_class = filtersets.ConsolePortFilterSet
- brief_prefetch_fields = ['device']
- class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = ConsoleServerPort.objects.prefetch_related(
- 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.ConsoleServerPortSerializer
- filterset_class = filtersets.ConsoleServerPortFilterSet
- brief_prefetch_fields = ['device']
- class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerPort.objects.prefetch_related(
- 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.PowerPortSerializer
- filterset_class = filtersets.PowerPortFilterSet
- brief_prefetch_fields = ['device']
- class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerOutlet.objects.prefetch_related(
- 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.PowerOutletSerializer
- filterset_class = filtersets.PowerOutletFilterSet
- brief_prefetch_fields = ['device']
- class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = Interface.objects.prefetch_related(
- 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans',
- 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations',
- 'vdcs',
- )
- serializer_class = serializers.InterfaceSerializer
- filterset_class = filtersets.InterfaceFilterSet
- brief_prefetch_fields = ['device']
- class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
- queryset = FrontPort.objects.prefetch_related(
- 'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.FrontPortSerializer
- filterset_class = filtersets.FrontPortFilterSet
- brief_prefetch_fields = ['device']
- class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
- queryset = RearPort.objects.prefetch_related(
- 'device__device_type__manufacturer', 'module__module_bay', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.RearPortSerializer
- filterset_class = filtersets.RearPortFilterSet
- brief_prefetch_fields = ['device']
- class ModuleBayViewSet(NetBoxModelViewSet):
- queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module')
- serializer_class = serializers.ModuleBaySerializer
- filterset_class = filtersets.ModuleBayFilterSet
- brief_prefetch_fields = ['device']
- class DeviceBayViewSet(NetBoxModelViewSet):
- queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags')
- serializer_class = serializers.DeviceBaySerializer
- filterset_class = filtersets.DeviceBayFilterSet
- brief_prefetch_fields = ['device']
- class InventoryItemViewSet(NetBoxModelViewSet):
- queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
- serializer_class = serializers.InventoryItemSerializer
- filterset_class = filtersets.InventoryItemFilterSet
- brief_prefetch_fields = ['device']
- #
- # Device component roles
- #
- class InventoryItemRoleViewSet(NetBoxModelViewSet):
- queryset = InventoryItemRole.objects.prefetch_related('tags').annotate(
- inventoryitem_count=count_related(InventoryItem, 'role')
- )
- serializer_class = serializers.InventoryItemRoleSerializer
- filterset_class = filtersets.InventoryItemRoleFilterSet
- #
- # Cables
- #
- class CableViewSet(NetBoxModelViewSet):
- queryset = Cable.objects.prefetch_related('terminations__termination')
- serializer_class = serializers.CableSerializer
- filterset_class = filtersets.CableFilterSet
- class CableTerminationViewSet(NetBoxModelViewSet):
- metadata_class = ContentTypeMetadata
- queryset = CableTermination.objects.prefetch_related('cable', 'termination')
- serializer_class = serializers.CableTerminationSerializer
- filterset_class = filtersets.CableTerminationFilterSet
- #
- # Virtual chassis
- #
- class VirtualChassisViewSet(NetBoxModelViewSet):
- queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
- member_count=count_related(Device, 'virtual_chassis')
- )
- serializer_class = serializers.VirtualChassisSerializer
- filterset_class = filtersets.VirtualChassisFilterSet
- brief_prefetch_fields = ['master']
- #
- # Power panels
- #
- class PowerPanelViewSet(NetBoxModelViewSet):
- queryset = PowerPanel.objects.prefetch_related(
- 'site', 'location'
- ).annotate(
- powerfeed_count=count_related(PowerFeed, 'power_panel')
- )
- serializer_class = serializers.PowerPanelSerializer
- filterset_class = filtersets.PowerPanelFilterSet
- #
- # Power feeds
- #
- class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerFeed.objects.prefetch_related(
- 'power_panel', 'rack', '_path', 'cable__terminations', 'tags'
- )
- serializer_class = serializers.PowerFeedSerializer
- filterset_class = filtersets.PowerFeedFilterSet
- #
- # Miscellaneous
- #
- class ConnectedDeviceViewSet(ViewSet):
- """
- This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer
- interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
- via a protocol such as LLDP. Two query parameters must be included in the request:
- * `peer_device`: The name of the peer device
- * `peer_interface`: The name of the peer interface
- """
- permission_classes = [IsAuthenticatedOrLoginNotRequired]
- _device_param = OpenApiParameter(
- name='peer_device',
- location='query',
- description='The name of the peer device',
- required=True,
- type=OpenApiTypes.STR
- )
- _interface_param = OpenApiParameter(
- name='peer_interface',
- location='query',
- description='The name of the peer interface',
- required=True,
- type=OpenApiTypes.STR
- )
- serializer_class = serializers.DeviceSerializer
- def get_view_name(self):
- return "Connected Device Locator"
- @extend_schema(responses={200: OpenApiTypes.OBJECT})
- def list(self, request):
- peer_device_name = request.query_params.get(self._device_param.name)
- peer_interface_name = request.query_params.get(self._interface_param.name)
- if not peer_device_name or not peer_interface_name:
- raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
- # Determine local endpoint from peer interface's connection
- peer_device = get_object_or_404(
- Device.objects.restrict(request.user, 'view'),
- name=peer_device_name
- )
- peer_interface = get_object_or_404(
- Interface.objects.restrict(request.user, 'view'),
- device=peer_device,
- name=peer_interface_name
- )
- endpoints = peer_interface.connected_endpoints
- # If an Interface, return the parent device
- if endpoints and type(endpoints[0]) is Interface:
- device = get_object_or_404(
- Device.objects.restrict(request.user, 'view'),
- pk=endpoints[0].device_id
- )
- return Response(serializers.DeviceSerializer(device, context={'request': request}).data)
- # Connected endpoint is none or not an Interface
- raise Http404
|