From 31e65a09e8b01341d1b7d641dd41fbb3c9aa6245 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 3 Aug 2020 13:37:32 -0400 Subject: [PATCH] Closes #4940: Added an occupied field to rack unit representations for rack elevation views --- docs/release-notes/version-2.9.md | 2 ++ netbox/dcim/api/serializers.py | 1 + netbox/dcim/api/views.py | 1 + netbox/dcim/models/__init__.py | 28 ++++++++++++++++++++++++---- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 745b14c6b..a2e94d04f 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -5,6 +5,7 @@ ### Enhancements * [#4919](https://github.com/netbox-community/netbox/issues/4919) - Allow adding/changing assigned permissions within group and user admin views +* [#4940](https://github.com/netbox-community/netbox/issues/4940) - Added an `occupied` field to rack unit representations for rack elevation views ### Bug Fixes @@ -86,6 +87,7 @@ When running a report or custom script, its execution is now queued for backgrou * dcim.PowerPortTemplate: Added `description` and `label` fields * dcim.PowerOutlet: Added `label` field * dcim.PowerOutletTemplate: Added `description` and `label` fields +* dcim.Rack: Added an `occupied` field to rack unit representations for rack elevation views * dcim.RackGroup: Added a `_depth` attribute indicating an object's position in the tree. * dcim.RackReservation: Added `tags` field * dcim.RearPort: Added `label` field diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index c97298b2b..c6db11bbd 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -165,6 +165,7 @@ class RackUnitSerializer(serializers.Serializer): name = serializers.CharField(read_only=True) face = ChoiceField(choices=DeviceFaceChoices, read_only=True) device = NestedDeviceSerializer(read_only=True) + occupied = serializers.BooleanField(read_only=True) class RackReservationSerializer(TaggedObjectSerializer, ValidatedModelSerializer): diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 81407f000..86d72c16c 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -189,6 +189,7 @@ class RackViewSet(CustomFieldModelViewSet): # 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'] ) diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 5932dac88..2803bb8de 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -656,12 +656,14 @@ class Rack(ChangeLoggedModel, CustomFieldModel): def get_status_class(self): return self.STATUS_CLASS_MAP.get(self.status) - def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None, expand_devices=True): + def get_rack_units(self, user=None, 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 user: User instance to be used for evaluating device view permissions. If None, all devices + will be included. :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 @@ -670,10 +672,18 @@ class Rack(ChangeLoggedModel, CustomFieldModel): elevation = OrderedDict() for u in self.units: - elevation[u] = {'id': u, 'name': 'U{}'.format(u), 'face': face, 'device': None} + elevation[u] = { + 'id': u, + 'name': f'U{u}', + 'face': face, + 'device': None, + 'occupied': False + } # Add devices to rack units list if self.pk: + + # Retrieve all devices installed within the rack queryset = Device.objects.prefetch_related( 'device_type', 'device_type__manufacturer', @@ -689,12 +699,22 @@ class Rack(ChangeLoggedModel, CustomFieldModel): ).filter( Q(face=face) | Q(device_type__is_full_depth=True) ) + + # Determine which devices the user has permission to view + permitted_device_ids = [] + if user is not None: + permitted_device_ids = self.devices.restrict(user, 'view').values_list('pk', flat=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 + if user is None or device.pk in permitted_device_ids: + elevation[u]['device'] = device + elevation[u]['occupied'] = True else: - elevation[device.position]['device'] = device + if user is None or device.pk in permitted_device_ids: + elevation[device.position]['device'] = device + elevation[device.position]['occupied'] = True 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)