mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
drf-yasg updates for rack elevations
This commit is contained in:
parent
d8dd5f00c1
commit
b4d724b5ae
@ -171,6 +171,16 @@ class RackReservationSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
|
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
class RackElevationDetailFilterSerializer(serializers.Serializer):
|
||||||
|
face = serializers.ChoiceField(choices=['front', 'rear'], default='front')
|
||||||
|
render_format = serializers.ChoiceField(choices=['json', 'svg'], default='json')
|
||||||
|
width = serializers.IntegerField(default=230)
|
||||||
|
unit_height = serializers.IntegerField(default=20)
|
||||||
|
exclude = serializers.IntegerField(required=False, default=None)
|
||||||
|
q = serializers.CharField(required=False, default=None)
|
||||||
|
expand_devices = serializers.BooleanField(required=False, default=True)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device types
|
# Device types
|
||||||
#
|
#
|
||||||
|
@ -176,11 +176,13 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
serializer_class = serializers.RackSerializer
|
serializer_class = serializers.RackSerializer
|
||||||
filterset_class = filters.RackFilter
|
filterset_class = filters.RackFilter
|
||||||
|
|
||||||
|
@swagger_auto_schema(deprecated=True)
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def units(self, request, pk=None):
|
def units(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
List rack units (by rack)
|
List rack units (by rack)
|
||||||
"""
|
"""
|
||||||
|
# TODO: Remove this action detail route in v2.8
|
||||||
rack = get_object_or_404(Rack, pk=pk)
|
rack = get_object_or_404(Rack, pk=pk)
|
||||||
face = request.GET.get('face', 'front')
|
face = request.GET.get('face', 'front')
|
||||||
exclude_pk = request.GET.get('exclude', None)
|
exclude_pk = request.GET.get('exclude', None)
|
||||||
@ -201,53 +203,33 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
|
rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
|
||||||
return self.get_paginated_response(rack_units.data)
|
return self.get_paginated_response(rack_units.data)
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: serializers.RackUnitSerializer(many=True)})
|
@swagger_auto_schema(
|
||||||
|
responses={200: serializers.RackUnitSerializer(many=True)},
|
||||||
|
query_serializer=serializers.RackElevationDetailFilterSerializer
|
||||||
|
)
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def elevation(self, request, pk=None):
|
def elevation(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
|
Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
|
||||||
"""
|
"""
|
||||||
rack = get_object_or_404(Rack, pk=pk)
|
rack = get_object_or_404(Rack, pk=pk)
|
||||||
face = request.GET.get('face')
|
serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
|
||||||
if face not in ['front', 'rear']:
|
if not serializer.is_valid():
|
||||||
face = 'front'
|
return Response(serializer.errors, 400)
|
||||||
|
data = serializer.validated_data
|
||||||
if request.GET.get('render_format', 'json') == 'svg':
|
|
||||||
# Render the elevantion as an SVG
|
|
||||||
width = request.GET.get('width', 230)
|
|
||||||
try:
|
|
||||||
width = int(width)
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponseBadRequest('width must be an integer.')
|
|
||||||
|
|
||||||
unit_height = request.GET.get('unit_height', 20)
|
|
||||||
try:
|
|
||||||
unit_height = int(unit_height)
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponseBadRequest('unit_height must be numeric.')
|
|
||||||
|
|
||||||
drawing = rack.get_elevation_svg(face, width, unit_height)
|
|
||||||
|
|
||||||
|
if data['render_format'] == 'svg':
|
||||||
|
drawing = rack.get_elevation_svg(data['face'], data['width'], data['unit_height'])
|
||||||
return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
|
return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Render a JSON response of the elevation
|
elevation = rack.get_rack_units(
|
||||||
exclude = request.GET.get('exclude', None)
|
face=data['face'],
|
||||||
if exclude is not None:
|
exclude=data['exclude'],
|
||||||
try:
|
expand_devices=data['expand_devices']
|
||||||
if isinstance(exclude, list):
|
)
|
||||||
exclude = [int(item) for item in exclude]
|
if data['q']:
|
||||||
else:
|
elevation = [u for u in elevation if data['q'] in str(u['id'])]
|
||||||
exclude = int(exclude)
|
|
||||||
except ValueError:
|
|
||||||
exclude = None
|
|
||||||
|
|
||||||
elevation = rack.get_rack_units(face, exclude)
|
|
||||||
|
|
||||||
# Enable filtering rack units by ID
|
|
||||||
q = request.GET.get('q', None)
|
|
||||||
if q:
|
|
||||||
elevation = [u for u in elevation if q in str(u['id'])]
|
|
||||||
|
|
||||||
page = self.paginate_queryset(elevation)
|
page = self.paginate_queryset(elevation)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
@ -1475,7 +1475,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
empty_value=None,
|
empty_value=None,
|
||||||
help_text="The lowest-numbered unit occupied by the device",
|
help_text="The lowest-numbered unit occupied by the device",
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/{{rack}}/units/',
|
api_url='/api/dcim/racks/{{rack}}/elevation/',
|
||||||
disabled_indicator='device'
|
disabled_indicator='device'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -526,17 +526,8 @@ class RackElevationHelperMixin:
|
|||||||
# Loop through all units in the elevation
|
# Loop through all units in the elevation
|
||||||
unit = elevation[unit_cursor]
|
unit = elevation[unit_cursor]
|
||||||
device = unit['device']
|
device = unit['device']
|
||||||
if device:
|
height = unit.get('height', 1)
|
||||||
# Look ahead to get the total device height
|
|
||||||
height = 0
|
|
||||||
look_ahead_unit_cursor = unit_cursor
|
|
||||||
while elevation[look_ahead_unit_cursor]['device'] == device and look_ahead_unit_cursor < total_units:
|
|
||||||
height += 1
|
|
||||||
look_ahead_unit_cursor += 1
|
|
||||||
else:
|
|
||||||
# Empty unit
|
|
||||||
height = 1
|
|
||||||
|
|
||||||
# Setup drawing cordinates
|
# Setup drawing cordinates
|
||||||
start_y = unit_cursor * unit_height
|
start_y = unit_cursor * unit_height
|
||||||
end_y = unit_height * height
|
end_y = unit_height * height
|
||||||
@ -567,13 +558,16 @@ class RackElevationHelperMixin:
|
|||||||
|
|
||||||
return drawing
|
return drawing
|
||||||
|
|
||||||
def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None):
|
def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None, expand_devices=True):
|
||||||
"""
|
"""
|
||||||
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
||||||
Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.
|
Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.
|
||||||
|
|
||||||
:param face: Rack face (front or rear)
|
:param face: Rack face (front or rear)
|
||||||
:param exclude: PK of a Device to exclude (optional); helpful when relocating a Device within a Rack
|
:param exclude: PK of a Device to exclude (optional); helpful when relocating a Device within a Rack
|
||||||
|
:param expand_devices: When True, all units that a device occupies will be listed with each containing a
|
||||||
|
reference to the device. When False, only the bottom most unit for a device is included and that unit
|
||||||
|
contains a height attribute for the device
|
||||||
"""
|
"""
|
||||||
|
|
||||||
elevation = OrderedDict()
|
elevation = OrderedDict()
|
||||||
@ -582,13 +576,29 @@ class RackElevationHelperMixin:
|
|||||||
|
|
||||||
# Add devices to rack units list
|
# Add devices to rack units list
|
||||||
if self.pk:
|
if self.pk:
|
||||||
for device in Device.objects.prefetch_related('device_type__manufacturer', 'device_role')\
|
queryset = Device.objects.prefetch_related(
|
||||||
.annotate(devicebay_count=Count('device_bays'))\
|
'device_type',
|
||||||
.exclude(pk=exclude)\
|
'device_type__manufacturer',
|
||||||
.filter(rack=self, position__gt=0)\
|
'device_role'
|
||||||
.filter(Q(face=face) | Q(device_type__is_full_depth=True)):
|
).annotate(
|
||||||
for u in range(device.position, device.position + device.device_type.u_height):
|
devicebay_count=Count('device_bays')
|
||||||
elevation[u]['device'] = device
|
).exclude(
|
||||||
|
pk=exclude
|
||||||
|
).filter(
|
||||||
|
rack=self,
|
||||||
|
position__gt=0
|
||||||
|
).filter(
|
||||||
|
Q(face=face) | Q(device_type__is_full_depth=True)
|
||||||
|
)
|
||||||
|
for device in queryset:
|
||||||
|
if expand_devices:
|
||||||
|
for u in range(device.position, device.position + device.device_type.u_height):
|
||||||
|
elevation[u]['device'] = device
|
||||||
|
else:
|
||||||
|
elevation[device.position]['device'] = device
|
||||||
|
elevation[device.position]['height'] = device.device_type.u_height
|
||||||
|
for u in range(device.position + 1, device.position + device.device_type.u_height):
|
||||||
|
elevation.pop(u, None)
|
||||||
|
|
||||||
return [u for u in elevation.values()]
|
return [u for u in elevation.values()]
|
||||||
|
|
||||||
@ -646,7 +656,7 @@ class RackElevationHelperMixin:
|
|||||||
:param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total
|
:param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total
|
||||||
height of the elevation
|
height of the elevation
|
||||||
"""
|
"""
|
||||||
elevation = self.get_rack_units(face=face)
|
elevation = self.get_rack_units(face=face, expand_devices=False)
|
||||||
reserved_units = self.get_reserved_units().keys()
|
reserved_units = self.get_reserved_units().keys()
|
||||||
|
|
||||||
return self._draw_elevations(elevation, reserved_units, face, width, unit_height)
|
return self._draw_elevations(elevation, reserved_units, face, width, unit_height)
|
||||||
|
@ -8,6 +8,6 @@
|
|||||||
|
|
||||||
<div class="rack_frame">
|
<div class="rack_frame">
|
||||||
|
|
||||||
<object data="{% url 'dcim-api:rack-elevation-detail' pk=rack.pk %}?face={{face}}"></object>
|
<object data="{% url 'dcim-api:rack-elevation' pk=rack.pk %}?face={{face}}&render_format=svg"></object>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user