diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 7145d679b..fc56dfa09 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -7,6 +7,7 @@ * [#4905](https://github.com/netbox-community/netbox/issues/4905) - Fix front port count on device type view * [#4912](https://github.com/netbox-community/netbox/issues/4912) - Fix image attachment API endpoint * [#4914](https://github.com/netbox-community/netbox/issues/4914) - Fix toggling cable status under device view +* [#4921](https://github.com/netbox-community/netbox/issues/4921) - Render non-viewable devices as unavailable space in rack elevations --- diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 038907b97..81407f000 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -176,6 +176,7 @@ class RackViewSet(CustomFieldModelViewSet): # 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'], diff --git a/netbox/dcim/elevations.py b/netbox/dcim/elevations.py index ea780b2d9..cef95a7b6 100644 --- a/netbox/dcim/elevations.py +++ b/netbox/dcim/elevations.py @@ -14,10 +14,11 @@ class RackElevationSVG: Use this class to render a rack elevation as an SVG image. :param rack: A NetBox Rack instance + :param user: User instance. If specified, only devices viewable by this user will be fully displayed. :param include_images: If true, the SVG document will embed front/rear device face images, where available :param base_url: Base URL for links within the SVG document. If none, links will be relative. """ - def __init__(self, rack, include_images=True, base_url=None): + def __init__(self, rack, user=None, include_images=True, base_url=None): self.rack = rack self.include_images = include_images if base_url is not None: @@ -25,7 +26,14 @@ class RackElevationSVG: else: self.base_url = '' - def _get_device_description(self, device): + # Determine the subset of devices within this rack that are viewable by the user, if any + permitted_devices = self.rack.devices + if user is not None: + permitted_devices = permitted_devices.restrict(user, 'view') + self.permitted_device_ids = permitted_devices.values_list('pk', flat=True) + + @staticmethod + def _get_device_description(device): return '{} ({}) — {} ({}U) {} {}'.format( device.name, device.device_role, @@ -174,10 +182,13 @@ class RackElevationSVG: text_cordinates = (x_offset + (unit_width / 2), y_offset + end_y / 2) # Draw the device - if device and device.face == face: + if device and device.face == face and device.pk in self.permitted_device_ids: self._draw_device_front(drawing, device, start_cordinates, end_cordinates, text_cordinates) - elif device and device.device_type.is_full_depth: + elif device and device.device_type.is_full_depth and device.pk in self.permitted_device_ids: self._draw_device_rear(drawing, device, start_cordinates, end_cordinates, text_cordinates) + elif device: + # Devices which the user does not have permission to view are rendered only as unavailable space + drawing.add(drawing.rect(start_cordinates, end_cordinates, class_='blocked')) else: # Draw shallow devices, reservations, or empty units class_ = 'slot' diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 3deea8140..5932dac88 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -750,6 +750,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): def get_elevation_svg( self, face=DeviceFaceChoices.FACE_FRONT, + user=None, unit_width=settings.RACK_ELEVATION_DEFAULT_UNIT_WIDTH, unit_height=settings.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT, legend_width=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT, @@ -760,6 +761,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel): Return an SVG of the rack elevation :param face: Enum of [front, rear] representing the desired side of the rack elevation to render + :param user: User instance to be used for evaluating device view permissions. If None, all devices + will be included. :param unit_width: Width in pixels for the rendered drawing :param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total height of the elevation @@ -767,7 +770,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): :param include_images: Embed front/rear device images where available :param base_url: Base URL for links and images. If none, URLs will be relative. """ - elevation = RackElevationSVG(self, include_images=include_images, base_url=base_url) + elevation = RackElevationSVG(self, user=user, include_images=include_images, base_url=base_url) return elevation.render(face, unit_width, unit_height, legend_width)