From 5a9bd6ffc1680b7b095294216860a821dc78f7f4 Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Mon, 15 Sep 2025 18:04:23 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(backend)=20duplicate=20sub=20docs?= =?UTF-8?q?=20as=20root=20for=20reader=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reader user should be able to duplicate a doc in the doc tree. It should be created a new doc at the root level. --- CHANGELOG.md | 3 ++ src/backend/core/api/viewsets.py | 49 ++++++++++++++----- .../documents/test_api_documents_duplicate.py | 25 ++++++++++ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2662de978b..773a3b36e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to ## [Unreleased] +### Fixed + +- 🐛(backend) duplicate sub docs as root for reader user ## [3.7.0] - 2025-09-12 diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 9b44be6897..1fb95c4eb6 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -941,37 +941,64 @@ def duplicate(self, request, *args, **kwargs): in the payload. """ # Get document while checking permissions - document = self.get_object() + document_to_duplicate = self.get_object() serializer = serializers.DocumentDuplicationSerializer( data=request.data, partial=True ) serializer.is_valid(raise_exception=True) with_accesses = serializer.validated_data.get("with_accesses", False) - is_owner_or_admin = document.get_role(request.user) in models.PRIVILEGED_ROLES + user_role = document_to_duplicate.get_role(request.user) + is_owner_or_admin = user_role in models.PRIVILEGED_ROLES - base64_yjs_content = document.content + base64_yjs_content = document_to_duplicate.content # Duplicate the document instance link_kwargs = ( - {"link_reach": document.link_reach, "link_role": document.link_role} + { + "link_reach": document_to_duplicate.link_reach, + "link_role": document_to_duplicate.link_role, + } if with_accesses else {} ) - extracted_attachments = set(extract_attachments(document.content)) - attachments = list(extracted_attachments & set(document.attachments)) - duplicated_document = document.add_sibling( + extracted_attachments = set(extract_attachments(document_to_duplicate.content)) + attachments = list( + extracted_attachments & set(document_to_duplicate.attachments) + ) + title = capfirst(_("copy of {title}").format(title=document_to_duplicate.title)) + if not document_to_duplicate.is_root() and choices.RoleChoices.get_priority( + user_role + ) < choices.RoleChoices.get_priority(models.RoleChoices.EDITOR): + duplicated_document = models.Document.add_root( + creator=self.request.user, + title=title, + content=base64_yjs_content, + attachments=attachments, + duplicated_from=document_to_duplicate, + **link_kwargs, + ) + models.DocumentAccess.objects.create( + document=duplicated_document, + user=self.request.user, + role=models.RoleChoices.OWNER, + ) + return drf_response.Response( + {"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED + ) + + duplicated_document = document_to_duplicate.add_sibling( "right", - title=capfirst(_("copy of {title}").format(title=document.title)), + title=title, content=base64_yjs_content, attachments=attachments, - duplicated_from=document, + duplicated_from=document_to_duplicate, creator=request.user, **link_kwargs, ) # Always add the logged-in user as OWNER for root documents - if document.is_root(): + if document_to_duplicate.is_root(): accesses_to_create = [ models.DocumentAccess( document=duplicated_document, @@ -983,7 +1010,7 @@ def duplicate(self, request, *args, **kwargs): # If accesses should be duplicated, add other users' accesses as per original document if with_accesses and is_owner_or_admin: original_accesses = models.DocumentAccess.objects.filter( - document=document + document=document_to_duplicate ).exclude(user=request.user) accesses_to_create.extend( diff --git a/src/backend/core/tests/documents/test_api_documents_duplicate.py b/src/backend/core/tests/documents/test_api_documents_duplicate.py index 3a781bb8d4..3882c4287c 100644 --- a/src/backend/core/tests/documents/test_api_documents_duplicate.py +++ b/src/backend/core/tests/documents/test_api_documents_duplicate.py @@ -293,3 +293,28 @@ def test_api_documents_duplicate_non_root_document(role): assert duplicated_accesses.count() == 0 assert duplicated_document.is_sibling_of(child) assert duplicated_document.is_child_of(document) + + +def test_api_documents_duplicate_reader_non_root_document(): + """ + Reader users should be able to duplicate non-root documents but will be + created as a root document. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(users=[(user, "reader")]) + child = factories.DocumentFactory(parent=document) + + assert child.get_role(user) == "reader" + + response = client.post( + f"/api/v1.0/documents/{child.id!s}/duplicate/", format="json" + ) + assert response.status_code == 201 + + duplicated_document = models.Document.objects.get(id=response.json()["id"]) + assert duplicated_document.is_root() + assert duplicated_document.accesses.count() == 1 + assert duplicated_document.accesses.get(user=user).role == "owner"