2023-07-18 18:17:53 +01:00
|
|
|
|
from rest_framework import serializers
|
|
|
|
|
|
|
|
|
|
|
|
from common.api_helpers.exceptions import BadRequest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrderedModelSerializer(serializers.ModelSerializer):
|
|
|
|
|
|
"""Ordered model serializer to be used in public API."""
|
|
|
|
|
|
|
|
|
|
|
|
position = serializers.IntegerField(required=False, source="order")
|
|
|
|
|
|
# manual_order=True is intended for use by Terraform provider only, and is not a documented feature.
|
|
|
|
|
|
manual_order = serializers.BooleanField(default=False, write_only=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
fields = ["position", "manual_order"]
|
|
|
|
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
|
|
# Remove "manual_order" and "order" fields from validated_data, so they are not passed to create method.
|
2023-07-18 20:29:04 +01:00
|
|
|
|
manual_order = validated_data.pop("manual_order", False)
|
2023-07-18 18:17:53 +01:00
|
|
|
|
order = validated_data.pop("order", None)
|
|
|
|
|
|
|
|
|
|
|
|
# Create the instance.
|
|
|
|
|
|
# Instances are always created at the end of the list, and then moved to the desired position by _adjust_order.
|
|
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
|
|
|
|
|
|
|
|
# Adjust order of the instance if necessary.
|
|
|
|
|
|
if order is not None:
|
|
|
|
|
|
self._adjust_order(instance, manual_order, order, created=True)
|
|
|
|
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
|
|
# Remove "manual_order" and "order" fields from validated_data, so they are not passed to update method.
|
2023-07-18 20:29:04 +01:00
|
|
|
|
manual_order = validated_data.pop("manual_order", False)
|
2023-07-18 18:17:53 +01:00
|
|
|
|
order = validated_data.pop("order", None)
|
|
|
|
|
|
|
|
|
|
|
|
# Adjust order of the instance if necessary.
|
|
|
|
|
|
if order is not None:
|
|
|
|
|
|
self._adjust_order(instance, manual_order, order, created=False)
|
|
|
|
|
|
|
|
|
|
|
|
# Proceed with the update.
|
|
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _adjust_order(instance, manual_order, order, created):
|
|
|
|
|
|
# Passing order=-1 means that the policy should be moved to the end of the list.
|
|
|
|
|
|
# Works only for public API but not for Terraform provider.
|
|
|
|
|
|
if order == -1 and not manual_order:
|
|
|
|
|
|
if created:
|
|
|
|
|
|
# The policy was just created, so it is already at the end of the list.
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
order = instance.max_order()
|
|
|
|
|
|
# max_order() can't be None here because at least one instance exists – the one we are moving.
|
|
|
|
|
|
assert order is not None
|
|
|
|
|
|
|
|
|
|
|
|
# Check the order is in the valid range.
|
|
|
|
|
|
# https://docs.djangoproject.com/en/4.1/ref/models/fields/#positiveintegerfield
|
|
|
|
|
|
if order < 0 or order > 2147483647:
|
|
|
|
|
|
raise BadRequest(detail="Invalid value for position field")
|
|
|
|
|
|
|
|
|
|
|
|
# Orders are swapped instead of moved when using Terraform, because Terraform may issue concurrent requests
|
|
|
|
|
|
# to create / update / delete multiple policies. "Move to" operation is not deterministic in this case, and
|
|
|
|
|
|
# final order of policies may be different depending on the order in which requests are processed. On the other
|
|
|
|
|
|
# hand, the result of concurrent "swap" operations is deterministic and does not depend on the order in
|
|
|
|
|
|
# which requests are processed.
|
|
|
|
|
|
if manual_order:
|
|
|
|
|
|
instance.swap(order)
|
|
|
|
|
|
else:
|
|
|
|
|
|
instance.to(order)
|