Skip to content

Commit dd7f6a8

Browse files
✨(backend) add documents/all endpoint with descendants
External dashboards need to find the latest updated documents across the entire hierarchy. Currently this requires many API calls to /documents/ and /documents/{id}/children for each level. This endpoint allows retrieving all accessible documents in a single request, enabling dashboards to efficiently display recently changed documents regardless of their position in the hierarchy. Signed-off-by: ChristopherSpelt <christopherspelt@icloud.com>
1 parent ab271bc commit dd7f6a8

File tree

3 files changed

+483
-0
lines changed

3 files changed

+483
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to
88

99
### Added
1010

11+
- ✨(backend) add documents/all endpoint with descendants #1553
1112
- ✨(frontend) create skeleton component for DocEditor #1491
1213
- ✨(frontend) add an EmojiPicker in the document tree and title #1381
1314

src/backend/core/api/viewsets.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ class DocumentViewSet(
368368
queryset = models.Document.objects.select_related("creator").all()
369369
serializer_class = serializers.DocumentSerializer
370370
ai_translate_serializer_class = serializers.AITranslateSerializer
371+
all_serializer_class = serializers.ListDocumentSerializer
371372
children_serializer_class = serializers.ListDocumentSerializer
372373
descendants_serializer_class = serializers.ListDocumentSerializer
373374
list_serializer_class = serializers.ListDocumentSerializer
@@ -837,6 +838,60 @@ def children(self, request, *args, **kwargs):
837838
},
838839
)
839840

841+
@drf.decorators.action(
842+
detail=False,
843+
methods=["get"],
844+
)
845+
def all(self, request, *args, **kwargs):
846+
"""
847+
Returns all documents (including descendants) that the user has access to.
848+
849+
Unlike the list endpoint which only returns top-level documents, this endpoint
850+
returns all documents including children, grandchildren, etc.
851+
"""
852+
user = self.request.user
853+
854+
accessible_documents = self.get_queryset()
855+
accessible_paths = list(accessible_documents.values_list("path", flat=True))
856+
857+
if not accessible_paths:
858+
return self.get_response_for_queryset(self.queryset.none())
859+
860+
# Build query to include all descendants using path prefix matching
861+
descendants_clause = db.Q()
862+
for path in accessible_paths:
863+
descendants_clause |= db.Q(path__startswith=path)
864+
865+
queryset = self.queryset.filter(
866+
descendants_clause, ancestors_deleted_at__isnull=True
867+
)
868+
869+
# Apply existing filters
870+
filterset = ListDocumentFilter(
871+
self.request.GET, queryset=queryset, request=self.request
872+
)
873+
if not filterset.is_valid():
874+
raise drf.exceptions.ValidationError(filterset.errors)
875+
filter_data = filterset.form.cleaned_data
876+
877+
# Filter as early as possible on fields that are available on the model
878+
for field in ["is_creator_me", "title"]:
879+
queryset = filterset.filters[field].filter(queryset, filter_data[field])
880+
881+
queryset = queryset.annotate_user_roles(user)
882+
883+
# Annotate favorite status and filter if applicable as late as possible
884+
queryset = queryset.annotate_is_favorite(user)
885+
for field in ["is_favorite", "is_masked"]:
886+
queryset = filterset.filters[field].filter(queryset, filter_data[field])
887+
888+
# Apply ordering only now that everything is filtered and annotated
889+
queryset = filters.OrderingFilter().filter_queryset(
890+
self.request, queryset, self
891+
)
892+
893+
return self.get_response_for_queryset(queryset)
894+
840895
@drf.decorators.action(
841896
detail=True,
842897
methods=["get"],

0 commit comments

Comments
 (0)