diff --git a/CHANGELOG.md b/CHANGELOG.md index ea46e8e1..46e174b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to ## Added +- ✨(backend) annotate number of accesses on documents in list view #411 - ✨(backend) allow users to mark/unmark documents as favorite #411 - 🌐(backend) add German translation #259 - 🌐(frontend) add German translation #255 diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 9298ae5a..589346b7 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -141,6 +141,7 @@ class ListDocumentSerializer(BaseResourceSerializer): """Serialize documents with limited fields for display in lists.""" is_favorite = serializers.BooleanField(read_only=True) + nb_accesses = serializers.IntegerField(read_only=True) class Meta: model = models.Document @@ -152,6 +153,7 @@ class ListDocumentSerializer(BaseResourceSerializer): "is_favorite", "link_role", "link_reach", + "nb_accesses", "title", "updated_at", ] @@ -162,6 +164,7 @@ class ListDocumentSerializer(BaseResourceSerializer): "is_favorite", "link_role", "link_reach", + "nb_accesses", "updated_at", ] @@ -181,6 +184,7 @@ class DocumentSerializer(ListDocumentSerializer): "is_favorite", "link_role", "link_reach", + "nb_accesses", "title", "updated_at", ] @@ -191,6 +195,7 @@ class DocumentSerializer(ListDocumentSerializer): "is_avorite", "link_role", "link_reach", + "nb_accesses", "updated_at", ] diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index dfe41be6..3cca5263 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1,4 +1,5 @@ """API endpoints""" +# pylint: disable=too-many-lines import re import uuid @@ -10,6 +11,7 @@ from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import ValidationError from django.core.files.storage import default_storage from django.db.models import ( + Count, Exists, Min, OuterRef, @@ -334,6 +336,9 @@ class DocumentViewSet( queryset = super().get_queryset() user = self.request.user + # Annotate the number of accesses associated with each document + queryset = queryset.annotate(nb_accesses=Count("accesses", distinct=True)) + if not user.is_authenticated: # If the user is not authenticated, annotate `is_favorite` as False return queryset.annotate(is_favorite=Value(False)) @@ -360,6 +365,7 @@ class DocumentViewSet( """Restrict resources returned by the list endpoint""" queryset = self.filter_queryset(self.get_queryset()) user = self.request.user + if user.is_authenticated: queryset = queryset.filter( Q(accesses__user=user) diff --git a/src/backend/core/tests/documents/test_api_documents_list.py b/src/backend/core/tests/documents/test_api_documents_list.py index 9e71ef0a..d185f4e7 100644 --- a/src/backend/core/tests/documents/test_api_documents_list.py +++ b/src/backend/core/tests/documents/test_api_documents_list.py @@ -39,9 +39,11 @@ def test_api_documents_list_format(): client = APIClient() client.force_login(user) + other_users = factories.UserFactory.create_batch(3) document = factories.DocumentFactory( users=[user, *factories.UserFactory.create_batch(2)], - favorited_by=[user, *factories.UserFactory.create_batch(2)], + favorited_by=[user, *other_users], + link_traces=other_users, ) response = client.get("/api/v1.0/documents/") @@ -63,6 +65,7 @@ def test_api_documents_list_format(): "is_favorite": True, "link_reach": document.link_reach, "link_role": document.link_role, + "nb_accesses": 3, "title": document.title, "updated_at": document.updated_at.isoformat().replace("+00:00", "Z"), } @@ -92,9 +95,7 @@ def test_api_documents_list_authenticated_direct(django_assert_num_queries): expected_ids = {str(document.id) for document in documents} with django_assert_num_queries(3): - response = client.get( - "/api/v1.0/documents/", - ) + response = client.get("/api/v1.0/documents/") assert response.status_code == 200 results = response.json()["results"] diff --git a/src/backend/core/tests/documents/test_api_documents_retrieve.py b/src/backend/core/tests/documents/test_api_documents_retrieve.py index adad3780..2fd253ac 100644 --- a/src/backend/core/tests/documents/test_api_documents_retrieve.py +++ b/src/backend/core/tests/documents/test_api_documents_retrieve.py @@ -38,6 +38,7 @@ def test_api_documents_retrieve_anonymous_public(): "versions_list": False, "versions_retrieve": False, }, + "nb_accesses": 0, "content": document.content, "created_at": document.created_at.isoformat().replace("+00:00", "Z"), "is_favorite": False, @@ -102,6 +103,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated( "is_favorite": False, "link_reach": reach, "link_role": document.link_role, + "nb_accesses": 0, "title": document.title, "updated_at": document.updated_at.isoformat().replace("+00:00", "Z"), } @@ -188,6 +190,7 @@ def test_api_documents_retrieve_authenticated_related_direct(): "is_favorite": False, "link_reach": document.link_reach, "link_role": document.link_role, + "nb_accesses": 2, "title": document.title, "updated_at": document.updated_at.isoformat().replace("+00:00", "Z"), } @@ -272,6 +275,7 @@ def test_api_documents_retrieve_authenticated_related_team_members( assert response.json() == { "id": str(document.id), "abilities": document.get_abilities(user), + "nb_accesses": 5, "content": document.content, "created_at": document.created_at.isoformat().replace("+00:00", "Z"), "is_favorite": False, @@ -326,6 +330,7 @@ def test_api_documents_retrieve_authenticated_related_team_administrators( assert response.json() == { "id": str(document.id), "abilities": document.get_abilities(user), + "nb_accesses": 5, "content": document.content, "created_at": document.created_at.isoformat().replace("+00:00", "Z"), "is_favorite": False, @@ -381,6 +386,7 @@ def test_api_documents_retrieve_authenticated_related_team_owners( assert response.json() == { "id": str(document.id), "abilities": document.get_abilities(user), + "nb_accesses": 5, "content": document.content, "created_at": document.created_at.isoformat().replace("+00:00", "Z"), "is_favorite": False, diff --git a/src/backend/core/tests/documents/test_api_documents_update.py b/src/backend/core/tests/documents/test_api_documents_update.py index e333e49d..f37907be 100644 --- a/src/backend/core/tests/documents/test_api_documents_update.py +++ b/src/backend/core/tests/documents/test_api_documents_update.py @@ -216,7 +216,7 @@ def test_api_documents_update_authenticated_editor_administrator_or_owner( document = models.Document.objects.get(pk=document.pk) document_values = serializers.DocumentSerializer(instance=document).data for key, value in document_values.items(): - if key in ["id", "created_at", "link_reach", "link_role"]: + if key in ["id", "created_at", "link_reach", "link_role", "nb_accesses"]: assert value == old_document_values[key] elif key == "updated_at": assert value > old_document_values[key] @@ -255,7 +255,7 @@ def test_api_documents_update_authenticated_owners(via, mock_user_teams): document = models.Document.objects.get(pk=document.pk) document_values = serializers.DocumentSerializer(instance=document).data for key, value in document_values.items(): - if key in ["id", "created_at", "link_reach", "link_role"]: + if key in ["id", "created_at", "link_reach", "link_role", "nb_accesses"]: assert value == old_document_values[key] elif key == "updated_at": assert value > old_document_values[key]