mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
611 lines
19 KiB
Python
611 lines
19 KiB
Python
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
|