mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
✨(backend) add a breadcrumb in the search response
In the search response, we want to display a breadcrumb for every document returned. For this we added a "parents" property containing a list documents, all are the parents of the current document (ordered by their depth). With this we can easily create a breadcrumb.
This commit is contained in:
@@ -16,6 +16,7 @@ and this project adheres to
|
||||
### Changed
|
||||
|
||||
- ♻️(backend) allow global search in sub documents
|
||||
- ✨(backend) add a breadcrumb in the search response
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -167,6 +167,17 @@ class ListDocumentSerializer(serializers.ModelSerializer):
|
||||
return instance.ancestors_deleted_at
|
||||
|
||||
|
||||
class SearchDocumentSerializer(ListDocumentSerializer):
|
||||
"""Serialize items for search."""
|
||||
|
||||
parents = ListDocumentSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Document
|
||||
fields = ListDocumentSerializer.Meta.fields + ["parents"]
|
||||
read_only_fields = ListDocumentSerializer.Meta.read_only_fields + ["parents"]
|
||||
|
||||
|
||||
class DocumentLightSerializer(serializers.ModelSerializer):
|
||||
"""Minial document serializer for nesting in document accesses."""
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ class DocumentViewSet(
|
||||
list_serializer_class = serializers.ListDocumentSerializer
|
||||
trashbin_serializer_class = serializers.ListDocumentSerializer
|
||||
tree_serializer_class = serializers.ListDocumentSerializer
|
||||
search_serializer_class = serializers.ListDocumentSerializer
|
||||
search_serializer_class = serializers.SearchDocumentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Get queryset performing all annotation and filtering on the document tree structure."""
|
||||
@@ -611,6 +611,48 @@ class DocumentViewSet(
|
||||
serializer = self.get_serializer(queryset, many=True, context=context)
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
def _compute_parents(self, documents):
|
||||
"""
|
||||
Compute parents for the documents by analyzing their paths and fetching missing parents.
|
||||
|
||||
Nota bene: current implementation may appear naive, but it has been
|
||||
optimized to avoid n+1 database queries issue. At this time, we only
|
||||
perform a single database request when parents are missing.
|
||||
"""
|
||||
documents = list(documents)
|
||||
steplen = models.Document.steplen
|
||||
|
||||
# Build parents dictionary and collect missing parent paths
|
||||
parents = {document.path: document for document in documents}
|
||||
missing_parent_paths = set()
|
||||
|
||||
for document in documents:
|
||||
path = document.path
|
||||
for i in range(steplen, len(path), steplen):
|
||||
parent_path = path[:i]
|
||||
if parent_path not in parents:
|
||||
missing_parent_paths.add(parent_path)
|
||||
|
||||
# Fetch missing ancestors from database
|
||||
if missing_parent_paths:
|
||||
for parent in (
|
||||
models.Document.objects.annotate_user_roles(self.request.user)
|
||||
.annotate_is_favorite(self.request.user)
|
||||
.filter(path__in=missing_parent_paths)
|
||||
.iterator()
|
||||
):
|
||||
parents[parent.path] = parent
|
||||
|
||||
# Set parents for each item
|
||||
for document in documents:
|
||||
path = document.path
|
||||
document_parents = []
|
||||
for i in range(steplen, len(path), steplen):
|
||||
document_parents.append(parents[path[:i]])
|
||||
document.parents = document_parents
|
||||
|
||||
return documents
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
Returns a DRF response containing the filtered, annotated and ordered document list.
|
||||
@@ -1499,6 +1541,17 @@ class DocumentViewSet(
|
||||
}
|
||||
)
|
||||
|
||||
def _get_response_for_search_queryset(self, queryset):
|
||||
page = self.paginate_queryset(queryset)
|
||||
documents_queryset = page if page else queryset
|
||||
documents = self._compute_parents(documents_queryset)
|
||||
serializer = self.get_serializer(documents, many=True)
|
||||
|
||||
if page is None:
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
def _search_using_database(self, request, validated_data, *args, **kwargs):
|
||||
"""
|
||||
Fallback search method when no indexer is configured.
|
||||
@@ -1581,7 +1634,7 @@ class DocumentViewSet(
|
||||
raise drf.exceptions.ValidationError(filterset.errors)
|
||||
|
||||
queryset = filterset.qs
|
||||
return self.get_response_for_queryset(queryset)
|
||||
return self._get_response_for_search_queryset(queryset)
|
||||
|
||||
@drf.decorators.action(detail=True, methods=["get"], url_path="versions")
|
||||
def versions_list(self, request, *args, **kwargs):
|
||||
|
||||
@@ -88,7 +88,9 @@ def test_api_documents_search_simple_search_anonymous(settings):
|
||||
assert response.json()["count"] == 0
|
||||
|
||||
|
||||
def test_api_documents_search_fall_back_on_simple_search(settings):
|
||||
def test_api_documents_search_fall_back_on_simple_search(
|
||||
settings, django_assert_num_queries
|
||||
):
|
||||
"""
|
||||
When indexer is not configured the search should be made in the database on the
|
||||
title field.
|
||||
@@ -125,7 +127,8 @@ def test_api_documents_search_fall_back_on_simple_search(settings):
|
||||
client.force_login(user)
|
||||
|
||||
q = "alpha"
|
||||
response = client.get("/api/v1.0/documents/search/", data={"q": q})
|
||||
with django_assert_num_queries(13):
|
||||
response = client.get("/api/v1.0/documents/search/", data={"q": q})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@@ -371,7 +374,7 @@ def test_api_documents_search_simple_search_only_match_in_depth(settings):
|
||||
}
|
||||
|
||||
|
||||
def test_api_documents_saerch_with_title_also_matching_link_traces(settings):
|
||||
def test_api_documents_search_with_title_also_matching_link_traces(settings):
|
||||
"""Test that link_traces can also be present in the search result."""
|
||||
assert get_document_indexer() is None
|
||||
assert settings.OIDC_STORE_REFRESH_TOKEN is False
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user