✨(backend) add API endpoint to move a document in the document tree
Only administrators or owners of a document can move it to a target document for which they are also administrator or owner. We allow different moving modes: - first-child: move the document as the first child of the target - last-child: move the document as the last child of the target - first-sibling: move the document as the first sibling of the target - last-sibling: move the document as the last sibling of the target - left: move the document as sibling ordered just before the target - right: move the document as sibling ordered just after the target The whole subtree below the document that is being moved, moves as well and remains below the document after it is moved.
This commit is contained in:
committed by
Anthony LC
parent
2e8a399668
commit
4de03d292a
@@ -568,3 +568,37 @@ class AITranslateSerializer(serializers.Serializer):
|
||||
if len(value.strip()) == 0:
|
||||
raise serializers.ValidationError("Text field cannot be empty.")
|
||||
return value
|
||||
|
||||
|
||||
class MoveDocumentSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for validating input data to move a document within the tree structure.
|
||||
|
||||
Fields:
|
||||
- target_document_id (UUIDField): The ID of the target parent document where the
|
||||
document should be moved. This field is required and must be a valid UUID.
|
||||
- position (ChoiceField): Specifies the position of the document in relation to
|
||||
the target parent's children.
|
||||
Choices:
|
||||
- "first-child": Place the document as the first child of the target parent.
|
||||
- "last-child": Place the document as the last child of the target parent (default).
|
||||
- "left": Place the document as the left sibling of the target parent.
|
||||
- "right": Place the document as the right sibling of the target parent.
|
||||
|
||||
Example:
|
||||
Input payload for moving a document:
|
||||
{
|
||||
"target_document_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"position": "first-child"
|
||||
}
|
||||
|
||||
Notes:
|
||||
- The `target_document_id` is mandatory.
|
||||
- The `position` defaults to "last-child" if not provided.
|
||||
"""
|
||||
|
||||
target_document_id = serializers.UUIDField(required=True)
|
||||
position = serializers.ChoiceField(
|
||||
choices=enums.MoveNodePositionChoices.choices,
|
||||
default=enums.MoveNodePositionChoices.LAST_CHILD,
|
||||
)
|
||||
|
||||
@@ -311,6 +311,7 @@ class DocumentMetadata(drf.metadata.SimpleMetadata):
|
||||
return simple_metadata
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class DocumentViewSet(
|
||||
drf.mixins.CreateModelMixin,
|
||||
drf.mixins.DestroyModelMixin,
|
||||
@@ -490,6 +491,65 @@ class DocumentViewSet(
|
||||
{"id": str(document.id)}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
@drf.decorators.action(detail=True, methods=["post"])
|
||||
@transaction.atomic
|
||||
def move(self, request, *args, **kwargs):
|
||||
"""
|
||||
Move a document to another location within the document tree.
|
||||
|
||||
The user must be an administrator or owner of both the document being moved
|
||||
and the target parent document.
|
||||
"""
|
||||
user = request.user
|
||||
document = self.get_object() # including permission checks
|
||||
|
||||
# Validate the input payload
|
||||
serializer = serializers.MoveDocumentSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
validated_data = serializer.validated_data
|
||||
|
||||
target_document_id = validated_data["target_document_id"]
|
||||
try:
|
||||
target_document = models.Document.objects.get(
|
||||
id=target_document_id, ancestors_deleted_at__isnull=True
|
||||
)
|
||||
except models.Document.DoesNotExist:
|
||||
return drf.response.Response(
|
||||
{"target_document_id": "Target parent document does not exist."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
position = validated_data["position"]
|
||||
message = None
|
||||
|
||||
if position in [
|
||||
enums.MoveNodePositionChoices.FIRST_CHILD,
|
||||
enums.MoveNodePositionChoices.LAST_CHILD,
|
||||
]:
|
||||
if not target_document.get_abilities(user).get("move"):
|
||||
message = (
|
||||
"You do not have permission to move documents "
|
||||
"as a child to this target document."
|
||||
)
|
||||
elif not target_document.is_root():
|
||||
if not target_document.get_parent().get_abilities(user).get("move"):
|
||||
message = (
|
||||
"You do not have permission to move documents "
|
||||
"as a sibling of this target document."
|
||||
)
|
||||
|
||||
if message:
|
||||
return drf.response.Response(
|
||||
{"target_document_id": message},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
document.move(target_document, pos=position)
|
||||
|
||||
return drf.response.Response(
|
||||
{"message": "Document moved successfully."}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["get", "post"],
|
||||
|
||||
Reference in New Issue
Block a user