| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- 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.response import Response
- from rest_framework.routers import APIRootView
- from rest_framework.viewsets import ViewSet
- 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, RenderConfigMixin
- from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
- from netbox.api.metadata import ContentTypeMetadata
- from netbox.api.pagination import StripCountAnnotationsPaginator
- from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
- from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
- from utilities.api import get_serializer_for_model
- from utilities.query_functions import CollateAsChar
- 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])
- near_ends = serializer_a(near_ends, nested=True, 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])
- far_ends = serializer_b(far_ends, nested=True, 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(MPTTLockedMixin, NetBoxModelViewSet):
- queryset = Region.objects.add_related_count(
- Region.objects.all(),
- Site,
- 'region',
- 'site_count',
- cumulative=True
- )
- serializer_class = serializers.RegionSerializer
- filterset_class = filtersets.RegionFilterSet
- #
- # Site groups
- #
- class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
- queryset = SiteGroup.objects.add_related_count(
- SiteGroup.objects.all(),
- Site,
- 'group',
- 'site_count',
- cumulative=True
- )
- serializer_class = serializers.SiteGroupSerializer
- filterset_class = filtersets.SiteGroupFilterSet
- #
- # Sites
- #
- class SiteViewSet(NetBoxModelViewSet):
- queryset = Site.objects.all()
- serializer_class = serializers.SiteSerializer
- filterset_class = filtersets.SiteFilterSet
- #
- # Locations
- #
- class LocationViewSet(MPTTLockedMixin, 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
- )
- serializer_class = serializers.LocationSerializer
- filterset_class = filtersets.LocationFilterSet
- #
- # Rack roles
- #
- class RackRoleViewSet(NetBoxModelViewSet):
- queryset = RackRole.objects.all()
- serializer_class = serializers.RackRoleSerializer
- filterset_class = filtersets.RackRoleFilterSet
- #
- # Racks
- #
- class RackViewSet(NetBoxModelViewSet):
- queryset = Rack.objects.all()
- serializer_class = serializers.RackSerializer
- filterset_class = filtersets.RackFilterSet
- @extend_schema(
- operation_id='dcim_racks_elevation_retrieve',
- filters=False,
- parameters=[serializers.RackElevationDetailFilterSerializer],
- responses={200: serializers.RackUnitSerializer(many=True)}
- )
- @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.all()
- serializer_class = serializers.RackReservationSerializer
- filterset_class = filtersets.RackReservationFilterSet
- #
- # Manufacturers
- #
- class ManufacturerViewSet(NetBoxModelViewSet):
- queryset = Manufacturer.objects.all()
- serializer_class = serializers.ManufacturerSerializer
- filterset_class = filtersets.ManufacturerFilterSet
- #
- # Device/module types
- #
- class DeviceTypeViewSet(NetBoxModelViewSet):
- queryset = DeviceType.objects.all()
- serializer_class = serializers.DeviceTypeSerializer
- filterset_class = filtersets.DeviceTypeFilterSet
- class ModuleTypeViewSet(NetBoxModelViewSet):
- queryset = ModuleType.objects.all()
- serializer_class = serializers.ModuleTypeSerializer
- filterset_class = filtersets.ModuleTypeFilterSet
- #
- # Device type components
- #
- class ConsolePortTemplateViewSet(NetBoxModelViewSet):
- queryset = ConsolePortTemplate.objects.all()
- serializer_class = serializers.ConsolePortTemplateSerializer
- filterset_class = filtersets.ConsolePortTemplateFilterSet
- class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
- queryset = ConsoleServerPortTemplate.objects.all()
- serializer_class = serializers.ConsoleServerPortTemplateSerializer
- filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
- class PowerPortTemplateViewSet(NetBoxModelViewSet):
- queryset = PowerPortTemplate.objects.all()
- serializer_class = serializers.PowerPortTemplateSerializer
- filterset_class = filtersets.PowerPortTemplateFilterSet
- class PowerOutletTemplateViewSet(NetBoxModelViewSet):
- queryset = PowerOutletTemplate.objects.all()
- serializer_class = serializers.PowerOutletTemplateSerializer
- filterset_class = filtersets.PowerOutletTemplateFilterSet
- class InterfaceTemplateViewSet(NetBoxModelViewSet):
- queryset = InterfaceTemplate.objects.all()
- serializer_class = serializers.InterfaceTemplateSerializer
- filterset_class = filtersets.InterfaceTemplateFilterSet
- class FrontPortTemplateViewSet(NetBoxModelViewSet):
- queryset = FrontPortTemplate.objects.all()
- serializer_class = serializers.FrontPortTemplateSerializer
- filterset_class = filtersets.FrontPortTemplateFilterSet
- class RearPortTemplateViewSet(NetBoxModelViewSet):
- queryset = RearPortTemplate.objects.all()
- serializer_class = serializers.RearPortTemplateSerializer
- filterset_class = filtersets.RearPortTemplateFilterSet
- class ModuleBayTemplateViewSet(NetBoxModelViewSet):
- queryset = ModuleBayTemplate.objects.all()
- serializer_class = serializers.ModuleBayTemplateSerializer
- filterset_class = filtersets.ModuleBayTemplateFilterSet
- class DeviceBayTemplateViewSet(NetBoxModelViewSet):
- queryset = DeviceBayTemplate.objects.all()
- serializer_class = serializers.DeviceBayTemplateSerializer
- filterset_class = filtersets.DeviceBayTemplateFilterSet
- class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet):
- queryset = InventoryItemTemplate.objects.all()
- serializer_class = serializers.InventoryItemTemplateSerializer
- filterset_class = filtersets.InventoryItemTemplateFilterSet
- #
- # Device roles
- #
- class DeviceRoleViewSet(NetBoxModelViewSet):
- queryset = DeviceRole.objects.all()
- serializer_class = serializers.DeviceRoleSerializer
- filterset_class = filtersets.DeviceRoleFilterSet
- #
- # Platforms
- #
- class PlatformViewSet(NetBoxModelViewSet):
- queryset = Platform.objects.all()
- serializer_class = serializers.PlatformSerializer
- filterset_class = filtersets.PlatformFilterSet
- #
- # Devices/modules
- #
- class DeviceViewSet(
- SequentialBulkCreatesMixin,
- ConfigContextQuerySetMixin,
- RenderConfigMixin,
- NetBoxModelViewSet
- ):
- queryset = Device.objects.prefetch_related(
- 'parent_bay', # Referenced by DeviceSerializer.get_parent_device()
- )
- 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 self.brief or 'config_context' in request.query_params.get('exclude', []):
- return serializers.DeviceSerializer
- return serializers.DeviceWithConfigContextSerializer
- class VirtualDeviceContextViewSet(NetBoxModelViewSet):
- queryset = VirtualDeviceContext.objects.all()
- serializer_class = serializers.VirtualDeviceContextSerializer
- filterset_class = filtersets.VirtualDeviceContextFilterSet
- class ModuleViewSet(NetBoxModelViewSet):
- queryset = Module.objects.all()
- serializer_class = serializers.ModuleSerializer
- filterset_class = filtersets.ModuleFilterSet
- #
- # Device components
- #
- class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = ConsolePort.objects.prefetch_related(
- '_path', 'cable__terminations',
- )
- serializer_class = serializers.ConsolePortSerializer
- filterset_class = filtersets.ConsolePortFilterSet
- class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = ConsoleServerPort.objects.prefetch_related(
- '_path', 'cable__terminations',
- )
- serializer_class = serializers.ConsoleServerPortSerializer
- filterset_class = filtersets.ConsoleServerPortFilterSet
- class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerPort.objects.prefetch_related(
- '_path', 'cable__terminations',
- )
- serializer_class = serializers.PowerPortSerializer
- filterset_class = filtersets.PowerPortFilterSet
- class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerOutlet.objects.prefetch_related(
- '_path', 'cable__terminations',
- )
- serializer_class = serializers.PowerOutletSerializer
- filterset_class = filtersets.PowerOutletFilterSet
- class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = Interface.objects.prefetch_related(
- '_path', 'cable__terminations',
- 'l2vpn_terminations', # Referenced by InterfaceSerializer.l2vpn_termination
- 'ip_addresses', # Referenced by Interface.count_ipaddresses()
- 'fhrp_group_assignments', # Referenced by Interface.count_fhrp_groups()
- )
- serializer_class = serializers.InterfaceSerializer
- filterset_class = filtersets.InterfaceFilterSet
- def get_bulk_destroy_queryset(self):
- # Ensure child interfaces are deleted prior to their parents
- return self.get_queryset().order_by('device', 'parent', CollateAsChar('_name'))
- class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
- queryset = FrontPort.objects.prefetch_related(
- 'cable__terminations',
- )
- serializer_class = serializers.FrontPortSerializer
- filterset_class = filtersets.FrontPortFilterSet
- class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
- queryset = RearPort.objects.prefetch_related(
- 'cable__terminations',
- )
- serializer_class = serializers.RearPortSerializer
- filterset_class = filtersets.RearPortFilterSet
- class ModuleBayViewSet(NetBoxModelViewSet):
- queryset = ModuleBay.objects.all()
- serializer_class = serializers.ModuleBaySerializer
- filterset_class = filtersets.ModuleBayFilterSet
- class DeviceBayViewSet(NetBoxModelViewSet):
- queryset = DeviceBay.objects.all()
- serializer_class = serializers.DeviceBaySerializer
- filterset_class = filtersets.DeviceBayFilterSet
- class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet):
- queryset = InventoryItem.objects.all()
- serializer_class = serializers.InventoryItemSerializer
- filterset_class = filtersets.InventoryItemFilterSet
- #
- # Device component roles
- #
- class InventoryItemRoleViewSet(NetBoxModelViewSet):
- queryset = InventoryItemRole.objects.all()
- 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.all()
- serializer_class = serializers.CableTerminationSerializer
- filterset_class = filtersets.CableTerminationFilterSet
- #
- # Virtual chassis
- #
- class VirtualChassisViewSet(NetBoxModelViewSet):
- queryset = VirtualChassis.objects.prefetch_related(
- # Prefetch related object for the display of unnamed devices
- 'master__virtual_chassis',
- )
- serializer_class = serializers.VirtualChassisSerializer
- filterset_class = filtersets.VirtualChassisFilterSet
- #
- # Power panels
- #
- class PowerPanelViewSet(NetBoxModelViewSet):
- queryset = PowerPanel.objects.all()
- serializer_class = serializers.PowerPanelSerializer
- filterset_class = filtersets.PowerPanelFilterSet
- #
- # Power feeds
- #
- class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
- queryset = PowerFeed.objects.prefetch_related(
- '_path', 'cable__terminations',
- )
- 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(
- parameters=[_device_param, _interface_param],
- responses={200: serializers.DeviceSerializer}
- )
- 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
|