mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -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']
|
||||
|
||||
|
||||
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
|
||||
#
|
||||
|
@ -176,11 +176,13 @@ class RackViewSet(CustomFieldModelViewSet):
|
||||
serializer_class = serializers.RackSerializer
|
||||
filterset_class = filters.RackFilter
|
||||
|
||||
@swagger_auto_schema(deprecated=True)
|
||||
@action(detail=True)
|
||||
def units(self, request, pk=None):
|
||||
"""
|
||||
List rack units (by rack)
|
||||
"""
|
||||
# TODO: Remove this action detail route in v2.8
|
||||
rack = get_object_or_404(Rack, pk=pk)
|
||||
face = request.GET.get('face', 'front')
|
||||
exclude_pk = request.GET.get('exclude', None)
|
||||
@ -201,53 +203,33 @@ class RackViewSet(CustomFieldModelViewSet):
|
||||
rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
|
||||
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)
|
||||
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(Rack, pk=pk)
|
||||
face = request.GET.get('face')
|
||||
if face not in ['front', 'rear']:
|
||||
face = 'front'
|
||||
|
||||
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)
|
||||
serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, 400)
|
||||
data = serializer.validated_data
|
||||
|
||||
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')
|
||||
|
||||
else:
|
||||
# Render a JSON response of the elevation
|
||||
exclude = request.GET.get('exclude', None)
|
||||
if exclude is not None:
|
||||
try:
|
||||
if isinstance(exclude, list):
|
||||
exclude = [int(item) for item in exclude]
|
||||
else:
|
||||
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'])]
|
||||
elevation = rack.get_rack_units(
|
||||
face=data['face'],
|
||||
exclude=data['exclude'],
|
||||
expand_devices=data['expand_devices']
|
||||
)
|
||||
if data['q']:
|
||||
elevation = [u for u in elevation if data['q'] in str(u['id'])]
|
||||
|
||||
page = self.paginate_queryset(elevation)
|
||||
if page is not None:
|
||||
|
@ -1475,7 +1475,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
empty_value=None,
|
||||
help_text="The lowest-numbered unit occupied by the device",
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/{{rack}}/units/',
|
||||
api_url='/api/dcim/racks/{{rack}}/elevation/',
|
||||
disabled_indicator='device'
|
||||
)
|
||||
)
|
||||
|
@ -526,17 +526,8 @@ class RackElevationHelperMixin:
|
||||
# Loop through all units in the elevation
|
||||
unit = elevation[unit_cursor]
|
||||
device = unit['device']
|
||||
if device:
|
||||
# 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
|
||||
|
||||
height = unit.get('height', 1)
|
||||
|
||||
# Setup drawing cordinates
|
||||
start_y = unit_cursor * unit_height
|
||||
end_y = unit_height * height
|
||||
@ -567,13 +558,16 @@ class RackElevationHelperMixin:
|
||||
|
||||
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'}
|
||||
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 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()
|
||||
@ -582,13 +576,29 @@ class RackElevationHelperMixin:
|
||||
|
||||
# Add devices to rack units list
|
||||
if self.pk:
|
||||
for device in Device.objects.prefetch_related('device_type__manufacturer', 'device_role')\
|
||||
.annotate(devicebay_count=Count('device_bays'))\
|
||||
.exclude(pk=exclude)\
|
||||
.filter(rack=self, position__gt=0)\
|
||||
.filter(Q(face=face) | Q(device_type__is_full_depth=True)):
|
||||
for u in range(device.position, device.position + device.device_type.u_height):
|
||||
elevation[u]['device'] = device
|
||||
queryset = Device.objects.prefetch_related(
|
||||
'device_type',
|
||||
'device_type__manufacturer',
|
||||
'device_role'
|
||||
).annotate(
|
||||
devicebay_count=Count('device_bays')
|
||||
).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()]
|
||||
|
||||
@ -646,7 +656,7 @@ class RackElevationHelperMixin:
|
||||
:param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total
|
||||
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()
|
||||
|
||||
return self._draw_elevations(elevation, reserved_units, face, width, unit_height)
|
||||
|
@ -8,6 +8,6 @@
|
||||
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user