diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index af806acb8..85767201b 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -619,6 +619,24 @@ class PlatformSerializer(NetBoxModelSerializer): ] +class CustomDeviceListSerializer(serializers.ListSerializer): + def validate(self, attrs): + validated_data = super().validate(attrs) + devices = [Device(**item) for item in validated_data] + # break these up into racks for space validation + racks = {} + for device in devices: + if device.rack: + if device.rack not in racks: + racks[device.rack] = [] + racks[device.rack].append(device) + + for rack, devices in racks.items(): + units = rack.check_for_space(devices) + + return validated_data + + class DeviceSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') device_type = NestedDeviceTypeSerializer() @@ -655,6 +673,7 @@ class DeviceSerializer(NetBoxModelSerializer): 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', ] + list_serializer_class = CustomDeviceListSerializer @swagger_serializer_method(serializer_or_field=NestedDeviceSerializer) def get_parent_device(self, obj): diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 5c41450a7..4334471ac 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -758,7 +758,7 @@ class Device(NetBoxModel, ConfigContextModel): }) # Validate rack space - rack.check_for_space([self, ]) + self.rack.check_for_space([self, ]) except DeviceType.DoesNotExist: pass diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 49df8397b..8f7e35598 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -332,7 +332,7 @@ class Rack(NetBoxModel): :param exclude: List of devices IDs to exclude (useful when moving a device within a rack) """ if exclude is not None: - devices = devices.exclude(pk__in=exclude) + devices = [device for device in devices if device.pk not in exclude] # Initialize the rack unit skeleton units = list(self.units) @@ -369,7 +369,7 @@ class Rack(NetBoxModel): :param exclude: List of devices IDs to exclude (useful when moving a device within a rack) """ # Gather all devices which consume U space within the rack - devices = self.get_all_devices() + devices = list(self.get_all_devices()) return self.check_available_units(devices, u_height, rack_face, exclude) def check_for_space(self, devices): @@ -377,13 +377,13 @@ class Rack(NetBoxModel): for device in devices: rack_face = device.face if not device.device_type.is_full_depth else None exclude_list = [device.pk] if device.pk else [] - available_units = rack.check_available_units( + available_units = self.check_available_units( rack_devices, u_height=device.device_type.u_height, rack_face=rack_face, exclude=exclude_list ) if device.position and device.position not in available_units: raise ValidationError({ - 'position': f"U{self.position} is already occupied or does not have sufficient space to " - f"accommodate this device type: {self.device_type} ({self.device_type.u_height}U)" + 'position': f"U{device.position} is already occupied or does not have sufficient space to " + f"accommodate this device type: {device.device_type} ({device.device_type.u_height}U)" }) rack_devices.append(device)