From a4de486cf77cf8d59ca7ac8f95726766b65d6d89 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Thu, 27 Nov 2025 20:55:50 +0530 Subject: [PATCH] [WIKI-811] fix: ensure only non-deleted project pages are retrieved in page queries (#8182) * fix: ensure soft delete handling for pages in PageViewSet methods * refactor: streamline query for project IDs in PageDuplicateEndpoint * refactor: remove soft delete condition from ProjectPage queries in PageViewSet and PageDuplicateEndpoint * refactor: simplify ProjectPage query in PageViewSet for improved readability * refactor: replace filter with get for Page queries in PageViewSet and PageDuplicateEndpoint to enhance clarity * refactor: replace filter with get for Page queries in PagesDescriptionViewSet to improve efficiency --- apps/api/plane/app/views/page/base.py | 95 +++++++++++++++++++++------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/apps/api/plane/app/views/page/base.py b/apps/api/plane/app/views/page/base.py index b8946d22a6..50daf440ad 100644 --- a/apps/api/plane/app/views/page/base.py +++ b/apps/api/plane/app/views/page/base.py @@ -149,14 +149,24 @@ class PageViewSet(BaseViewSet): def partial_update(self, request, slug, project_id, page_id): try: - page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) if page.is_locked: return Response({"error": "Page is locked"}, status=status.HTTP_400_BAD_REQUEST) parent = request.data.get("parent", None) if parent: - _ = Page.objects.get(pk=parent, workspace__slug=slug, projects__id=project_id) + _ = Page.objects.get( + pk=parent, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) # Only update access if the page owner is the requesting user if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id: @@ -230,14 +240,24 @@ class PageViewSet(BaseViewSet): return Response(data, status=status.HTTP_200_OK) def lock(self, request, slug, project_id, page_id): - page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) page.is_locked = True page.save() return Response(status=status.HTTP_204_NO_CONTENT) def unlock(self, request, slug, project_id, page_id): - page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) page.is_locked = False page.save() @@ -246,7 +266,12 @@ class PageViewSet(BaseViewSet): def access(self, request, slug, project_id, page_id): access = request.data.get("access", 0) - page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) # Only update access if the page owner is the requesting user if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id: @@ -277,7 +302,12 @@ class PageViewSet(BaseViewSet): return Response(pages, status=status.HTTP_200_OK) def archive(self, request, slug, project_id, page_id): - page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) # only the owner or admin can archive the page if ( @@ -303,7 +333,12 @@ class PageViewSet(BaseViewSet): return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK) def unarchive(self, request, slug, project_id, page_id): - page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) # only the owner or admin can un archive the page if ( @@ -327,7 +362,12 @@ class PageViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) def destroy(self, request, slug, project_id, page_id): - page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id) + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) if page.archived_at is None: return Response( @@ -350,7 +390,12 @@ class PageViewSet(BaseViewSet): ) # remove parent from all the children - _ = Page.objects.filter(parent_id=page_id, projects__id=project_id, workspace__slug=slug).update(parent=None) + _ = Page.objects.filter( + parent_id=page_id, + projects__id=project_id, + workspace__slug=slug, + project_pages__deleted_at__isnull=True, + ).update(parent=None) page.delete() # Delete the user favorite page @@ -451,12 +496,14 @@ class PagesDescriptionViewSet(BaseViewSet): def retrieve(self, request, slug, project_id, page_id): page = ( - Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id) - .filter(Q(owned_by=self.request.user) | Q(access=0)) - .first() + Page.objects.get( + Q(owned_by=self.request.user) | Q(access=0), + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) ) - if page is None: - return Response({"error": "Page not found"}, status=404) binary_data = page.description_binary def stream_data(): @@ -471,14 +518,15 @@ class PagesDescriptionViewSet(BaseViewSet): def partial_update(self, request, slug, project_id, page_id): page = ( - Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id) - .filter(Q(owned_by=self.request.user) | Q(access=0)) - .first() + Page.objects.get( + Q(owned_by=self.request.user) | Q(access=0), + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) ) - if page is None: - return Response({"error": "Page not found"}, status=404) - if page.is_locked: return Response( { @@ -529,7 +577,12 @@ class PageDuplicateEndpoint(BaseAPIView): permission_classes = [ProjectPagePermission] def post(self, request, slug, project_id, page_id): - page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first() + page = Page.objects.get( + pk=page_id, + workspace__slug=slug, + projects__id=project_id, + project_pages__deleted_at__isnull=True, + ) # check for permission if page.access == Page.PRIVATE_ACCESS and page.owned_by_id != request.user.id: