From cd3e4e4a0bdec6bd93c283882efc6a9e4ec11db6 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Mon, 27 Jul 2026 16:31:41 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20expose=20restriction=20tar?= =?UTF-8?q?gets=20in=20the=20items=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrictions expose their target's id, title and a can_access flag so the frontend can grey out entries pointing to folders the user cannot open. The children listing prefetches the viewer accesses to keep the query count flat, and the tree includes restriction entries. --- src/backend/core/api/serializers.py | 47 +++++ src/backend/core/api/viewsets.py | 20 +- .../items/test_api_items_children_list.py | 18 ++ .../core/tests/items/test_api_items_list.py | 3 + .../items/test_api_items_restrictions.py | 177 ++++++++++++++++++ .../tests/items/test_api_items_retrieve.py | 13 ++ .../core/tests/items/test_api_items_search.py | 6 + .../items/test_api_items_search_fulltext.py | 4 + .../tests/items/test_api_items_trashbin.py | 1 + .../core/tests/items/test_api_items_tree.py | 13 ++ 10 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 src/backend/core/tests/items/test_api_items_restrictions.py diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 1d805624..af269a94 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -11,6 +11,7 @@ from os.path import splitext from urllib.parse import quote from django.conf import settings +from django.db.models import Q from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -221,6 +222,48 @@ class ItemAccessLightSerializer(ItemAccessSerializer): ] +class RestrictionTargetSerializer(serializers.ModelSerializer): + """Serialize the restricted folder a restriction points to.""" + + deleted = serializers.SerializerMethodField() + can_access = serializers.SerializerMethodField() + is_restricted = serializers.SerializerMethodField() + + class Meta: + model = models.Item + fields = ["id", "title", "is_restricted", "deleted", "can_access"] + read_only_fields = ["id", "title", "is_restricted", "deleted", "can_access"] + + def get_deleted(self, target) -> bool: + """Return whether the target is in the trash.""" + return target.deleted_at is not None + + def get_is_restricted(self, target) -> bool: # pylint: disable=unused-argument + """Return True: the target of an existing restriction is restricted by definition.""" + return True + + def get_can_access(self, target) -> bool: + """Return whether the request user can open the target.""" + request = self.context.get("request") + user = request.user if request else None + if user is not None and user.is_authenticated: + accesses = getattr(target, "viewer_accesses", None) + if accesses is None: + has_access = models.ItemAccess.objects.filter( + Q(user=user) | Q(team__in=user.teams), + item=target, + ).exists() + else: + has_access = bool(accesses) + if has_access: + return True + return target.link_reach == LinkReachChoices.PUBLIC or ( + target.link_reach == LinkReachChoices.AUTHENTICATED + and user is not None + and user.is_authenticated + ) + + class ListItemSerializer(serializers.ModelSerializer): """Serialize items with limited fields for display in lists.""" @@ -234,6 +277,7 @@ class ListItemSerializer(serializers.ModelSerializer): creator = UserLightSerializer(read_only=True) hard_delete_at = serializers.SerializerMethodField(read_only=True) is_wopi_supported = serializers.SerializerMethodField() + target = RestrictionTargetSerializer(read_only=True, allow_null=True) class Meta: model = models.Item @@ -255,6 +299,7 @@ class ListItemSerializer(serializers.ModelSerializer): "numchild", "numchild_folder", "path", + "target", "title", "updated_at", "user_role", @@ -288,6 +333,7 @@ class ListItemSerializer(serializers.ModelSerializer): "link_reach", "nb_accesses", "path", + "target", "updated_at", "user_role", "type", @@ -487,6 +533,7 @@ class ItemSerializer(ListItemSerializer): "numchild", "numchild_folder", "path", + "target", "title", "updated_at", "user_role", diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index c21ffa57..fd6ea0ec 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1144,10 +1144,20 @@ class ItemViewSet( # GET: List children queryset = ( item.children() - .select_related("creator") + .select_related("creator", "target") .annotate_has_restriction() .filter(deleted_at__isnull=True) ) + if request.user.is_authenticated: + queryset = queryset.prefetch_related( + db.Prefetch( + "target__accesses", + queryset=models.ItemAccess.objects.filter( + db.Q(user=request.user) | db.Q(team__in=request.user.teams) + ), + to_attr="viewer_accesses", + ) + ) queryset = self._filter_suspicious_items(queryset, request.user) queryset = self._exclude_pending_items(queryset) queryset = self.filter_queryset(queryset) @@ -1244,9 +1254,13 @@ class ItemViewSet( paths_links_mapping[str(ancestor.path)] = ancestors_links.copy() tree = ( - self.queryset.select_related("creator") + self.queryset.select_related("creator", "target") .annotate_has_restriction() - .filter(clause, type=models.ItemTypeChoices.FOLDER, deleted_at__isnull=True) + .filter( + clause, + type__in=[models.ItemTypeChoices.FOLDER, models.ItemTypeChoices.RESTRICTION], + deleted_at__isnull=True, + ) .order_by("created_at") ) diff --git a/src/backend/core/tests/items/test_api_items_children_list.py b/src/backend/core/tests/items/test_api_items_children_list.py index 58476e04..2d824b59 100644 --- a/src/backend/core/tests/items/test_api_items_children_list.py +++ b/src/backend/core/tests/items/test_api_items_children_list.py @@ -48,6 +48,7 @@ def test_api_items_children_list_anonymous_public_standalone(): "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -93,6 +94,7 @@ def test_api_items_children_list_anonymous_public_standalone(): "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -178,6 +180,7 @@ def test_api_items_children_list_anonymous_public_parent(): "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -217,6 +220,7 @@ def test_api_items_children_list_anonymous_public_parent(): "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -310,6 +314,7 @@ def test_api_items_children_list_authenticated_unrelated_public_or_authenticated "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -355,6 +360,7 @@ def test_api_items_children_list_authenticated_unrelated_public_or_authenticated "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -437,6 +443,7 @@ def test_api_items_children_list_authenticated_public_or_authenticated_parent( "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -482,6 +489,7 @@ def test_api_items_children_list_authenticated_public_or_authenticated_parent( "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -589,6 +597,7 @@ def test_api_items_children_list_authenticated_related_direct(): "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -634,6 +643,7 @@ def test_api_items_children_list_authenticated_related_direct(): "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -719,6 +729,7 @@ def test_api_items_children_list_authenticated_related_parent(): "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -764,6 +775,7 @@ def test_api_items_children_list_authenticated_related_parent(): "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -906,6 +918,7 @@ def test_api_items_children_list_authenticated_related_team_members( "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -951,6 +964,7 @@ def test_api_items_children_list_authenticated_related_team_members( "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -1034,6 +1048,7 @@ def test_api_items_children_list_filter_type(): "id": str(child1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child1.link_reach, "link_role": child1.link_role, "numchild": 0, @@ -1088,6 +1103,7 @@ def test_api_items_children_list_filter_type(): "id": str(child2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": child2.link_reach, "link_role": child2.link_role, "numchild": 0, @@ -1396,6 +1412,7 @@ def test_api_items_children_list_computed_link_reach_and_role(): "id": str(item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "restricted", "link_role": "reader", "numchild": 1, @@ -1445,6 +1462,7 @@ def test_api_items_children_list_computed_link_reach_and_role(): "id": str(child.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "public", "link_role": "editor", "numchild": 0, diff --git a/src/backend/core/tests/items/test_api_items_list.py b/src/backend/core/tests/items/test_api_items_list.py index b4898f17..0fadd238 100644 --- a/src/backend/core/tests/items/test_api_items_list.py +++ b/src/backend/core/tests/items/test_api_items_list.py @@ -107,6 +107,7 @@ def test_api_items_list_format(): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": item3.link_reach, "link_role": item3.link_role, "nb_accesses": 0, @@ -146,6 +147,7 @@ def test_api_items_list_format(): "depth": 1, "is_favorite": True, "is_restricted": False, + "target": None, "link_reach": item2.link_reach, "link_role": item2.link_role, "nb_accesses": 3, @@ -185,6 +187,7 @@ def test_api_items_list_format(): "depth": 1, "is_favorite": True, "is_restricted": False, + "target": None, "link_reach": item.link_reach, "link_role": item.link_role, "nb_accesses": 3, diff --git a/src/backend/core/tests/items/test_api_items_restrictions.py b/src/backend/core/tests/items/test_api_items_restrictions.py new file mode 100644 index 00000000..495cafb7 --- /dev/null +++ b/src/backend/core/tests/items/test_api_items_restrictions.py @@ -0,0 +1,177 @@ +"""Tests for the restriction target details in the items API.""" + +from django.db import connection +from django.test.utils import CaptureQueriesContext + +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +def _create_restricted_folder(parent, user): + """Create a folder under parent and restrict it as user.""" + folder = factories.ItemFactory(parent=parent, type=models.ItemTypeChoices.FOLDER) + factories.UserItemAccessFactory(item=folder, user=user, role="owner") + return folder.restrict(user) + + +def test_api_items_restrictions_children_list_exposes_target(): + """The children listing exposes the restriction target, greyed for excluded users.""" + parent_owner = factories.UserFactory() + owner = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(parent_owner, "owner")], + ) + folder = _create_restricted_folder(parent, owner) + restriction = folder.restriction + + client = APIClient() + client.force_login(parent_owner) + + response = client.get(f"/api/v1.0/items/{parent.id!s}/children/") + + assert response.status_code == 200 + results = {result["id"]: result for result in response.json()["results"]} + payload = results[str(restriction.id)] + assert payload["type"] == "restriction" + assert payload["target"] == { + "id": str(folder.id), + "title": folder.title, + "is_restricted": True, + "deleted": False, + "can_access": False, + } + + +def test_api_items_restrictions_children_list_target_accessible(): + """The target is accessible for a user holding an explicit access on it.""" + user = factories.UserFactory() + owner = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(user, "reader")], + ) + folder = _create_restricted_folder(parent, owner) + factories.UserItemAccessFactory(item=folder, user=user, role="reader") + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/items/{parent.id!s}/children/") + + assert response.status_code == 200 + results = {result["id"]: result for result in response.json()["results"]} + assert results[str(folder.restriction.id)]["target"]["can_access"] is True + + +def test_api_items_restrictions_children_list_target_accessible_via_link(): + """A public link reach on the target grants access through the restriction.""" + parent_owner = factories.UserFactory() + owner = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(parent_owner, "owner")], + ) + folder = _create_restricted_folder(parent, owner) + models.Item.objects.filter(pk=folder.pk).update(link_reach="public") + + client = APIClient() + client.force_login(parent_owner) + + response = client.get(f"/api/v1.0/items/{parent.id!s}/children/") + + assert response.status_code == 200 + results = {result["id"]: result for result in response.json()["results"]} + assert results[str(folder.restriction.id)]["target"]["can_access"] is True + + +def test_api_items_restrictions_retrieve_exposes_target(): + """Retrieving a restriction exposes its target.""" + user = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(user, "owner")], + ) + folder = _create_restricted_folder(parent, user) + restriction = folder.restriction + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/items/{restriction.id!s}/") + + assert response.status_code == 200 + assert response.json()["target"]["id"] == str(folder.id) + + +def test_api_items_restrictions_non_restriction_target_is_none(): + """Regular items expose a null target.""" + user = factories.UserFactory() + folder = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(user, "owner")], + ) + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/items/{folder.id!s}/") + + assert response.status_code == 200 + assert response.json()["target"] is None + + +def test_api_items_restrictions_children_list_constant_queries(): + """The number of queries does not grow with the number of restrictions listed.""" + parent_owner = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(parent_owner, "owner")], + ) + _create_restricted_folder(parent, factories.UserFactory()) + + client = APIClient() + client.force_login(parent_owner) + + # Warm the nb_accesses cache so both measures run in the same conditions + client.get(f"/api/v1.0/items/{parent.id!s}/children/") + + with CaptureQueriesContext(connection) as single: + response = client.get(f"/api/v1.0/items/{parent.id!s}/children/") + assert response.status_code == 200 + + for _ in range(3): + _create_restricted_folder(parent, factories.UserFactory()) + + with CaptureQueriesContext(connection) as many: + response = client.get(f"/api/v1.0/items/{parent.id!s}/children/") + assert response.status_code == 200 + assert len(response.json()["results"]) == 4 + + assert len(many) == len(single) + + +def test_api_items_restrictions_tree_includes_restrictions(): + """The tree endpoint includes restriction entries so excluded users see them.""" + parent_owner = factories.UserFactory() + parent = factories.ItemFactory( + type=models.ItemTypeChoices.FOLDER, + users=[(parent_owner, "owner")], + ) + sibling = factories.ItemFactory(parent=parent, type=models.ItemTypeChoices.FOLDER) + folder = _create_restricted_folder(parent, factories.UserFactory()) + restriction = folder.restriction + + client = APIClient() + client.force_login(parent_owner) + + response = client.get(f"/api/v1.0/items/{sibling.id!s}/tree/") + + assert response.status_code == 200 + tree = response.json() + children_ids = {child["id"] for child in tree["children"]} + assert str(restriction.id) in children_ids diff --git a/src/backend/core/tests/items/test_api_items_retrieve.py b/src/backend/core/tests/items/test_api_items_retrieve.py index 7151987a..d687824a 100644 --- a/src/backend/core/tests/items/test_api_items_retrieve.py +++ b/src/backend/core/tests/items/test_api_items_retrieve.py @@ -44,6 +44,7 @@ def test_api_items_retrieve_anonymous_public_standalone(): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "public", "link_role": item.link_role, "nb_accesses": 0, @@ -102,6 +103,7 @@ def test_api_items_retrieve_anonymous_public_parent(): "depth": 3, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": item.link_reach, "link_role": item.link_role, "nb_accesses": 0, @@ -207,6 +209,7 @@ def test_api_items_retrieve_authenticated_unrelated_public_or_authenticated(reac "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": reach, "link_role": item.link_role, "nb_accesses": 0, @@ -271,6 +274,7 @@ def test_api_items_retrieve_authenticated_public_or_authenticated_parent(reach): "depth": 3, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": item.link_reach, "link_role": item.link_role, "nb_accesses": 0, @@ -413,6 +417,7 @@ def test_api_items_retrieve_authenticated_related_direct(): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": item.link_reach, "link_role": item.link_role, "nb_accesses": 2, @@ -481,6 +486,7 @@ def test_api_items_retrieve_authenticated_related_parent(): "depth": 3, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "restricted", "link_role": item.link_role, "nb_accesses": 2, @@ -659,6 +665,7 @@ def test_api_items_retrieve_authenticated_related_team_members(teams, role, mock "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "restricted", "link_role": item.link_role, "nb_accesses": 5, @@ -735,6 +742,7 @@ def test_api_items_retrieve_authenticated_related_team_administrators(teams, rol "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "restricted", "link_role": item.link_role, "nb_accesses": 5, @@ -811,6 +819,7 @@ def test_api_items_retrieve_authenticated_related_team_owners(teams, mock_user_t "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "restricted", "link_role": item.link_role, "nb_accesses": 5, @@ -1211,6 +1220,7 @@ def test_api_items_retrieve_file_with_url_property(upload_state): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "public", "link_role": item.link_role, "nb_accesses": 1, @@ -1285,6 +1295,7 @@ def test_api_items_retrieve_file_with_url_property_non_previewable(upload_state) "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "public", "link_role": item.link_role, "nb_accesses": 1, @@ -1349,6 +1360,7 @@ def test_api_items_retrieve_file_with_url_property_with_spaces(): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "public", "link_role": item.link_role, "nb_accesses": 1, @@ -1495,6 +1507,7 @@ def test_api_items_retrieve_file_analysing_not_creator(): "depth": 1, "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": "public", "link_role": item.link_role, diff --git a/src/backend/core/tests/items/test_api_items_search.py b/src/backend/core/tests/items/test_api_items_search.py index 593dc5f1..1570c5a6 100644 --- a/src/backend/core/tests/items/test_api_items_search.py +++ b/src/backend/core/tests/items/test_api_items_search.py @@ -73,6 +73,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(top_parent.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": top_parent.link_reach, "link_role": top_parent.link_role, @@ -113,6 +114,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(parent.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": parent.link_reach, "link_role": parent.link_role, @@ -142,6 +144,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(top_parent.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": top_parent.link_reach, "link_role": top_parent.link_role, @@ -193,6 +196,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(children.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": children.link_reach, "link_role": children.link_role, @@ -222,6 +226,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(top_parent.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": top_parent.link_reach, "link_role": top_parent.link_role, @@ -261,6 +266,7 @@ def test_api_items_search_authenticated_without_filters(): "id": str(parent.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": parent.link_reach, "link_role": parent.link_role, diff --git a/src/backend/core/tests/items/test_api_items_search_fulltext.py b/src/backend/core/tests/items/test_api_items_search_fulltext.py index 42d4185b..3ab933a9 100644 --- a/src/backend/core/tests/items/test_api_items_search_fulltext.py +++ b/src/backend/core/tests/items/test_api_items_search_fulltext.py @@ -83,6 +83,7 @@ def test_api_items_search_authenticated_fulltext_query(indexer_settings): "id": str(item_b.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": item_b.link_reach, "link_role": item_b.link_role, @@ -121,6 +122,7 @@ def test_api_items_search_authenticated_fulltext_query(indexer_settings): "hard_delete_at": None, "id": str(folder.id), "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": folder.link_reach, "link_role": folder.link_role, @@ -162,6 +164,7 @@ def test_api_items_search_authenticated_fulltext_query(indexer_settings): "id": str(item_c.id), "is_favorite": False, "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": item_c.link_reach, "link_role": item_c.link_role, @@ -200,6 +203,7 @@ def test_api_items_search_authenticated_fulltext_query(indexer_settings): "hard_delete_at": None, "id": str(folder.id), "is_restricted": False, + "target": None, "is_wopi_supported": False, "link_reach": folder.link_reach, "link_role": folder.link_role, diff --git a/src/backend/core/tests/items/test_api_items_trashbin.py b/src/backend/core/tests/items/test_api_items_trashbin.py index cf447800..49aacb19 100644 --- a/src/backend/core/tests/items/test_api_items_trashbin.py +++ b/src/backend/core/tests/items/test_api_items_trashbin.py @@ -104,6 +104,7 @@ def test_api_items_trashbin_format(settings): "description": None, "hard_delete_at": ((now + timedelta(days=30)).isoformat()), "is_restricted": False, + "target": None, "is_wopi_supported": False, } diff --git a/src/backend/core/tests/items/test_api_items_tree.py b/src/backend/core/tests/items/test_api_items_tree.py index f2eb0472..8a099e0e 100644 --- a/src/backend/core/tests/items/test_api_items_tree.py +++ b/src/backend/core/tests/items/test_api_items_tree.py @@ -159,6 +159,7 @@ def test_items_api_anonymous_to_a_public_tree_structure(): "id": str(level2_1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level2_1.link_reach, "link_role": level2_1.link_role, "nb_accesses": 0, @@ -199,6 +200,7 @@ def test_items_api_anonymous_to_a_public_tree_structure(): "id": str(level2_2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level2_2.link_reach, "link_role": level2_2.link_role, "nb_accesses": 0, @@ -233,6 +235,7 @@ def test_items_api_anonymous_to_a_public_tree_structure(): "id": str(level1_2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level1_2.link_reach, "link_role": level1_2.link_role, "nb_accesses": 0, @@ -393,6 +396,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level2_1.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level2_1.item.link_reach, "link_role": level2_1.item.link_role, "nb_accesses": 3, @@ -442,6 +446,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level3_1.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level3_1.item.link_reach, "link_role": level3_1.item.link_role, "nb_accesses": 4, @@ -481,6 +486,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level2_2.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level2_2.item.link_reach, "link_role": level2_2.item.link_role, "nb_accesses": 3, @@ -515,6 +521,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level1_1.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level1_1.item.link_reach, "link_role": level1_1.item.link_role, "nb_accesses": 2, @@ -555,6 +562,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level1_2.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level1_2.item.link_reach, "link_role": level1_2.item.link_role, "nb_accesses": 2, @@ -595,6 +603,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(level1_3.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": level1_3.item.link_reach, "link_role": level1_3.item.link_role, "nb_accesses": 2, @@ -629,6 +638,7 @@ def test_items_api_tree_authenticated_direct_access(django_assert_num_queries): "id": str(root.item.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": root.item.link_reach, "link_role": root.item.link_role, "nb_accesses": 1, @@ -746,6 +756,7 @@ def test_api_items_tree_authenticated_with_access_authenticated(): "id": str(level1_1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "authenticated", "link_role": level1_1.link_role, "nb_accesses": 0, @@ -791,6 +802,7 @@ def test_api_items_tree_authenticated_with_access_authenticated(): "id": str(level2_1.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "authenticated", "link_role": level2_1.link_role, "nb_accesses": 0, @@ -831,6 +843,7 @@ def test_api_items_tree_authenticated_with_access_authenticated(): "id": str(level2_2.id), "is_favorite": False, "is_restricted": False, + "target": None, "link_reach": "authenticated", "link_role": level2_2.link_role, "nb_accesses": 0,