Skip to content

Commit 9d6ea35

Browse files
committed
♻️(backend) fallback logic
I am adding a title_search with all the logic Signed-off-by: charles <[email protected]>
1 parent 5323429 commit 9d6ea35

File tree

2 files changed

+74
-62
lines changed

2 files changed

+74
-62
lines changed

src/backend/core/api/filters.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class Meta:
5252
fields = ["title"]
5353

5454

55-
class ListDocumentFilter(django_filters.FilterSet):
55+
class ListDocumentFilter(DocumentFilter):
5656
"""
5757
Custom filter for filtering documents.
5858
"""
@@ -130,6 +130,35 @@ def filter_is_masked(self, queryset, name, value):
130130
return queryset_method(link_traces__user=user, link_traces__is_masked=True)
131131

132132

133+
class SubDocumentFilter(DocumentFilter):
134+
"""
135+
Custom filter for filtering sub-documents by path and title.
136+
Used when searching within a specific document subtree.
137+
the parent document can be matched.
138+
139+
Example:
140+
- /api/v1.0/documents/search/?path=0001&q=test
141+
→ Filters documents where path starts with "0001" and title contains "test"
142+
"""
143+
144+
path = django_filters.CharFilter(
145+
required=True, method="filter_by_path", label=_("Path")
146+
)
147+
148+
class Meta:
149+
model = models.Document
150+
fields = ["path", "title"]
151+
152+
# pylint: disable=unused-argument
153+
def filter_by_path(self, queryset, name, value):
154+
"""
155+
Filter documents whose path starts with the provided path.
156+
"""
157+
return queryset.filter(
158+
path__startswith=value, ancestors_deleted_at__isnull=True
159+
)
160+
161+
133162
class UserSearchFilter(django_filters.FilterSet):
134163
"""
135164
Custom filter for searching users.

src/backend/core/api/viewsets.py

Lines changed: 44 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import uuid
1111
from collections import defaultdict
1212
from urllib.parse import unquote, urlencode, urlparse
13+
from urllib.request import Request
1314

1415
from django.conf import settings
1516
from django.contrib.postgres.aggregates import ArrayAgg
@@ -64,7 +65,7 @@
6465
from core.utils import extract_attachments, filter_descendants
6566

6667
from . import permissions, serializers, utils
67-
from .filters import DocumentFilter, ListDocumentFilter, UserSearchFilter
68+
from .filters import DocumentFilter, ListDocumentFilter, SubDocumentFilter, UserSearchFilter
6869
from .throttling import (
6970
DocumentThrottle,
7071
UserListThrottleBurst,
@@ -459,21 +460,21 @@ def list(self, request, *args, **kwargs):
459460
It performs early filtering on model fields, annotates user roles, and removes
460461
descendant documents to keep only the highest ancestors readable by the current user.
461462
"""
462-
user = self.request.user
463+
user = request.user
463464

464465
# Not calling filter_queryset. We do our own cooking.
465466
queryset = self.get_queryset()
466467

467468
filterset = ListDocumentFilter(
468-
self.request.GET, queryset=queryset, request=self.request
469+
request.GET, queryset=queryset, request=request
469470
)
470471
if not filterset.is_valid():
471472
raise drf.exceptions.ValidationError(filterset.errors)
472473
filter_data = filterset.form.cleaned_data
473474

474475
# Filter as early as possible on fields that are available on the model
475-
field = "is_creator_me"
476-
queryset = filterset.filters[field].filter(queryset, filter_data[field])
476+
for field in ["is_creator_me", "title"]:
477+
queryset = filterset.filters[field].filter(queryset, filter_data[field])
477478

478479
queryset = queryset.annotate_user_roles(user)
479480

@@ -956,27 +957,6 @@ def all(self, request, *args, **kwargs):
956957

957958
return self.get_response_for_queryset(queryset)
958959

959-
@drf.decorators.action(
960-
detail=True,
961-
methods=["get"],
962-
ordering=["path"],
963-
)
964-
def descendants(self, request, *args, **kwargs):
965-
"""Handle listing descendants of a document"""
966-
# TODO: remove ? might be dead code
967-
document = self.get_object()
968-
969-
queryset = document.get_descendants().filter(ancestors_deleted_at__isnull=True)
970-
queryset = self.filter_queryset(queryset)
971-
972-
filterset = DocumentFilter(request.GET, queryset=queryset)
973-
if not filterset.is_valid():
974-
raise drf.exceptions.ValidationError(filterset.errors)
975-
976-
queryset = filterset.qs
977-
978-
return self.get_response_for_queryset(queryset)
979-
980960
@drf.decorators.action(
981961
detail=True,
982962
methods=["get"],
@@ -1184,25 +1164,30 @@ def duplicate(self, request, *args, **kwargs):
11841164
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
11851165
)
11861166

1187-
def _search_simple(self, request, text):
1167+
1168+
@drf.decorators.action(detail=False, methods=["get"], url_path="search")
1169+
@method_decorator(refresh_oidc_access_token)
1170+
def search(self, request, *args, **kwargs):
11881171
"""
1189-
Returns a queryset filtered by the content of the document title
1172+
Returns a DRF response containing the filtered, annotated and ordered document list.
1173+
1174+
Applies filtering based on request parameter 'q' from `SearchDocumentSerializer`.
1175+
Depending of the configuration it can be:
1176+
- A fulltext search through the opensearch indexation app "find" if the backend is
1177+
enabled (see SEARCH_INDEXER_CLASS)
1178+
- A filtering by the model field 'title'.
1179+
1180+
The ordering is always by the most recent first.
11901181
"""
1191-
# As the 'list' view we get a prefiltered queryset (deleted docs are excluded)
1192-
queryset = models.Document.objects.all()
1193-
filterset = DocumentFilter({"title": text}, queryset=queryset)
1194-
# TODO: make sure parent in included when searching in sub-docs
1195-
if not filterset.is_valid():
1196-
raise drf.exceptions.ValidationError(filterset.errors)
1182+
params = serializers.SearchDocumentSerializer(data=request.query_params)
1183+
params.is_valid(raise_exception=True)
11971184

1198-
queryset = filterset.filter_queryset(queryset)
1185+
indexer = get_document_indexer()
1186+
if indexer:
1187+
return self._search_with_indexer(indexer, request, params=params)
11991188

1200-
return self.get_response_for_queryset(
1201-
queryset.order_by("-updated_at"),
1202-
context={
1203-
"request": request,
1204-
},
1205-
)
1189+
# The indexer is not configured, we fallback on title search
1190+
return self.title_search(request, params.validated_data, *args, **kwargs)
12061191

12071192
@staticmethod
12081193
def _search_with_indexer(indexer, request, params):
@@ -1231,30 +1216,28 @@ def _search_with_indexer(indexer, request, params):
12311216
}
12321217
)
12331218

1234-
@drf.decorators.action(detail=False, methods=["get"], url_path="search")
1235-
@method_decorator(refresh_oidc_access_token)
1236-
def search(self, request, *args, **kwargs):
1237-
"""
1238-
Returns a DRF response containing the filtered, annotated and ordered document list.
1239-
1240-
Applies filtering based on request parameter 'q' from `SearchDocumentSerializer`.
1241-
Depending of the configuration it can be:
1242-
- A fulltext search through the opensearch indexation app "find" if the backend is
1243-
enabled (see SEARCH_INDEXER_CLASS)
1244-
- A filtering by the model field 'title'.
1219+
def title_search(self, request, validated_data, *args, **kwargs):
1220+
request.GET = request.GET.copy()
1221+
request.GET['title'] = validated_data['q']
1222+
if not "path" in validated_data or not validated_data["path"]:
1223+
return self.list(request, *args, **kwargs)
1224+
else:
1225+
return self._list_sub_docs(request)
12451226

1246-
The ordering is always by the most recent first.
1227+
def _list_sub_docs(self, request):
12471228
"""
1248-
params = serializers.SearchDocumentSerializer(data=request.query_params)
1249-
params.is_valid(raise_exception=True)
1229+
List all documents whose path starts with the provided path parameter.
1230+
Used internally by the search endpoint when path filtering is requested.
1231+
"""
1232+
queryset = self.get_queryset()
12501233

1251-
indexer = get_document_indexer()
1252-
if indexer:
1253-
return self._search_with_indexer(indexer, request, params=params)
1234+
filterset = SubDocumentFilter(request.GET, queryset=queryset)
1235+
if not filterset.is_valid():
1236+
raise drf.exceptions.ValidationError(filterset.errors)
1237+
1238+
queryset = filterset.qs
1239+
return self.get_response_for_queryset(queryset)
12541240

1255-
# The indexer is not configured, we fallback on a simple icontains filter by the
1256-
# model field 'title'.
1257-
return self._search_simple(request, text=params.validated_data["q"])
12581241

12591242
@drf.decorators.action(detail=True, methods=["get"], url_path="versions")
12601243
def versions_list(self, request, *args, **kwargs):

0 commit comments

Comments
 (0)