✨(backend) add "tree" action on document API endpoint
We want to display the tree structure to which a document belongs on the left side panel of its detail view. For this, we need an endpoint to retrieve the list view of the document's ancestors opened. By opened, we mean that when display the document, we also need to display its siblings. When displaying the parent of the current document, we also need to display the siblings of the parent...
This commit is contained in:
+2
-1
@@ -10,7 +10,8 @@ and this project adheres to
|
||||
|
||||
## Added
|
||||
|
||||
- ✨(backend) allow forcing page size within limits
|
||||
- ✨(backend) new "tree" action on document detail endpoint #645
|
||||
- ✨(backend) allow forcing page size within limits #645
|
||||
- 💄(frontend) add error pages #643
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -128,26 +128,13 @@ class TemplateAccessSerializer(BaseAccessSerializer):
|
||||
read_only_fields = ["id", "abilities"]
|
||||
|
||||
|
||||
class BaseResourceSerializer(serializers.ModelSerializer):
|
||||
"""Serialize documents."""
|
||||
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
accesses = TemplateAccessSerializer(many=True, read_only=True)
|
||||
|
||||
def get_abilities(self, document) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return document.get_abilities(request.user)
|
||||
return {}
|
||||
|
||||
|
||||
class ListDocumentSerializer(BaseResourceSerializer):
|
||||
class ListDocumentSerializer(serializers.ModelSerializer):
|
||||
"""Serialize documents with limited fields for display in lists."""
|
||||
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
nb_accesses = serializers.IntegerField(read_only=True)
|
||||
user_roles = serializers.SerializerMethodField(read_only=True)
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Document
|
||||
@@ -185,6 +172,18 @@ class ListDocumentSerializer(BaseResourceSerializer):
|
||||
"user_roles",
|
||||
]
|
||||
|
||||
def get_abilities(self, document) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
|
||||
if request:
|
||||
paths_links_mapping = self.context.get("paths_links_mapping", None)
|
||||
return document.get_abilities(
|
||||
request.user, paths_links_mapping=paths_links_mapping
|
||||
)
|
||||
|
||||
return {}
|
||||
|
||||
def get_user_roles(self, document):
|
||||
"""
|
||||
Return roles of the logged-in user for the current document,
|
||||
@@ -359,7 +358,7 @@ class ServerCreateDocumentSerializer(serializers.Serializer):
|
||||
raise NotImplementedError("Update is not supported for this serializer.")
|
||||
|
||||
|
||||
class LinkDocumentSerializer(BaseResourceSerializer):
|
||||
class LinkDocumentSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serialize link configuration for documents.
|
||||
We expose it separately from document in order to simplify and secure access control.
|
||||
@@ -429,9 +428,12 @@ class FileUploadSerializer(serializers.Serializer):
|
||||
return attrs
|
||||
|
||||
|
||||
class TemplateSerializer(BaseResourceSerializer):
|
||||
class TemplateSerializer(serializers.ModelSerializer):
|
||||
"""Serialize templates."""
|
||||
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
accesses = TemplateAccessSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Template
|
||||
fields = [
|
||||
@@ -445,6 +447,13 @@ class TemplateSerializer(BaseResourceSerializer):
|
||||
]
|
||||
read_only_fields = ["id", "accesses", "abilities"]
|
||||
|
||||
def get_abilities(self, document) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return document.get_abilities(request.user)
|
||||
return {}
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class DocumentGenerationSerializer(serializers.Serializer):
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
@@ -424,10 +425,11 @@ class DocumentViewSet(
|
||||
]
|
||||
queryset = models.Document.objects.all()
|
||||
serializer_class = serializers.DocumentSerializer
|
||||
ai_translate_serializer_class = serializers.AITranslateSerializer
|
||||
children_serializer_class = serializers.ListDocumentSerializer
|
||||
list_serializer_class = serializers.ListDocumentSerializer
|
||||
trashbin_serializer_class = serializers.ListDocumentSerializer
|
||||
children_serializer_class = serializers.ListDocumentSerializer
|
||||
ai_translate_serializer_class = serializers.AITranslateSerializer
|
||||
tree_serializer_class = serializers.ListDocumentSerializer
|
||||
|
||||
def annotate_is_favorite(self, queryset):
|
||||
"""
|
||||
@@ -523,7 +525,7 @@ class DocumentViewSet(
|
||||
queryset = queryset.filter(path__in=root_paths)
|
||||
|
||||
# Annotate the queryset with an attribute marking instances as highest ancestor
|
||||
# in order to save some time while computing abilities in the instance
|
||||
# in order to save some time while computing abilities on the instance
|
||||
queryset = queryset.annotate(
|
||||
is_highest_ancestor_for_user=db.Value(
|
||||
True, output_field=db.BooleanField()
|
||||
@@ -766,6 +768,71 @@ class DocumentViewSet(
|
||||
queryset = self.annotate_user_roles(queryset)
|
||||
return self.get_response_for_queryset(queryset)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["get"],
|
||||
ordering=["path"],
|
||||
)
|
||||
def tree(self, request, *args, **kwargs):
|
||||
"""
|
||||
List ancestors tree above the document.
|
||||
What we need to display is the tree structure opened for the current document.
|
||||
"""
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
||||
current_document = (
|
||||
self.queryset.filter(**filter_kwargs).only("depth", "path").first()
|
||||
)
|
||||
|
||||
if current_document is None:
|
||||
raise Http404
|
||||
|
||||
ancestors = (
|
||||
(current_document.get_ancestors() | self.queryset.filter(**filter_kwargs))
|
||||
.filter(ancestors_deleted_at__isnull=True)
|
||||
.order_by("path")
|
||||
)
|
||||
|
||||
# Get the highest readable ancestor
|
||||
highest_readable = ancestors.readable_per_se(request.user).only("depth").first()
|
||||
if highest_readable is None:
|
||||
raise (
|
||||
drf.exceptions.PermissionDenied()
|
||||
if request.user.is_authenticated
|
||||
else drf.exceptions.NotAuthenticated()
|
||||
)
|
||||
|
||||
ancestors_links_definitions = defaultdict(set)
|
||||
children_clause = db.Q()
|
||||
for ancestor in ancestors:
|
||||
if ancestor.depth < highest_readable.depth:
|
||||
continue
|
||||
|
||||
ancestors_links_definitions[ancestor.link_reach].add(ancestor.link_role)
|
||||
children_clause |= db.Q(
|
||||
path__startswith=ancestor.path, depth=ancestor.depth + 1
|
||||
)
|
||||
|
||||
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
|
||||
|
||||
queryset = ancestors.filter(depth__gte=highest_readable.depth) | children
|
||||
queryset = queryset.order_by("path")
|
||||
queryset = self.annotate_user_roles(queryset)
|
||||
queryset = self.annotate_is_favorite(queryset)
|
||||
|
||||
# Pass ancestors' links definitions to the serializer as a context variable
|
||||
# in order to allow saving time while computing abilities on the instance
|
||||
serializer = self.get_serializer(
|
||||
queryset,
|
||||
many=True,
|
||||
context={
|
||||
"request": request,
|
||||
"ancestors_links_definitions": ancestors_links_definitions,
|
||||
},
|
||||
)
|
||||
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
@drf.decorators.action(detail=True, methods=["get"], url_path="versions")
|
||||
def versions_list(self, request, *args, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -367,6 +367,51 @@ class BaseAccess(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class DocumentQuerySet(models.QuerySet):
|
||||
"""
|
||||
Custom queryset for the Document model, providing additional methods
|
||||
to filter documents based on user permissions.
|
||||
"""
|
||||
|
||||
def readable_per_se(self, user):
|
||||
"""
|
||||
Filters the queryset to return documents that the given user has
|
||||
permission to read.
|
||||
:param user: The user for whom readable documents are to be fetched.
|
||||
:return: A queryset of documents readable by the user.
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return self.filter(
|
||||
models.Q(accesses__user=user)
|
||||
| models.Q(accesses__team__in=user.teams)
|
||||
| ~models.Q(link_reach=LinkReachChoices.RESTRICTED)
|
||||
)
|
||||
|
||||
return self.filter(models.Q(link_reach=LinkReachChoices.PUBLIC))
|
||||
|
||||
|
||||
class DocumentManager(models.Manager):
|
||||
"""
|
||||
Custom manager for the Document model, enabling the use of the custom
|
||||
queryset methods directly from the model manager.
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Overrides the default get_queryset method to return a custom queryset.
|
||||
:return: An instance of DocumentQuerySet.
|
||||
"""
|
||||
return DocumentQuerySet(self.model, using=self._db)
|
||||
|
||||
def readable_per_se(self, user):
|
||||
"""
|
||||
Filters documents based on user permissions using the custom queryset.
|
||||
:param user: The user for whom readable documents are to be fetched.
|
||||
:return: A queryset of documents readable by the user.
|
||||
"""
|
||||
return self.get_queryset().readable_per_se(user)
|
||||
|
||||
|
||||
class Document(MP_Node, BaseModel):
|
||||
"""Pad document carrying the content."""
|
||||
|
||||
@@ -399,6 +444,8 @@ class Document(MP_Node, BaseModel):
|
||||
|
||||
path = models.CharField(max_length=7 * 36, unique=True, db_collation="C")
|
||||
|
||||
objects = DocumentManager()
|
||||
|
||||
class Meta:
|
||||
db_table = "impress_document"
|
||||
ordering = ("path",)
|
||||
@@ -680,6 +727,7 @@ class Document(MP_Node, BaseModel):
|
||||
"restore": is_owner,
|
||||
"retrieve": can_get,
|
||||
"media_auth": can_get,
|
||||
"tree": can_get,
|
||||
"update": can_update,
|
||||
"versions_destroy": is_owner_or_admin,
|
||||
"versions_list": has_access_role,
|
||||
|
||||
@@ -15,7 +15,7 @@ pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_api_documents_children_list_anonymous_public_standalone():
|
||||
"""Anonymous users should be allowed to retrieve the children of a public documents."""
|
||||
"""Anonymous users should be allowed to retrieve the children of a public document."""
|
||||
document = factories.DocumentFactory(link_reach="public")
|
||||
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document)
|
||||
factories.UserDocumentAccessFactory(document=child1)
|
||||
|
||||
@@ -44,6 +44,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
|
||||
"partial_update": document.link_role == "editor",
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": document.link_role == "editor",
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -100,6 +101,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
|
||||
"partial_update": grand_parent.link_role == "editor",
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": grand_parent.link_role == "editor",
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -189,6 +191,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
|
||||
"partial_update": document.link_role == "editor",
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": document.link_role == "editor",
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -252,6 +255,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
|
||||
"partial_update": grand_parent.link_role == "editor",
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": grand_parent.link_role == "editor",
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -424,6 +428,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
|
||||
"partial_update": access.role != "reader",
|
||||
"restore": access.role == "owner",
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": access.role != "reader",
|
||||
"versions_destroy": access.role in ["administrator", "owner"],
|
||||
"versions_list": True,
|
||||
|
||||
@@ -87,6 +87,7 @@ def test_api_documents_trashbin_format():
|
||||
"partial_update": True,
|
||||
"restore": True,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": True,
|
||||
"versions_destroy": True,
|
||||
"versions_list": True,
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
"""
|
||||
Tests for Documents API endpoint in impress's core app: retrieve
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_api_documents_tree_list_anonymous_public_standalone(django_assert_num_queries):
|
||||
"""Anonymous users should be allowed to retrieve the tree of a public document."""
|
||||
parent = factories.DocumentFactory(link_reach="public")
|
||||
document, sibling = factories.DocumentFactory.create_batch(2, parent=parent)
|
||||
child = factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
with django_assert_num_queries(8):
|
||||
APIClient().get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
with django_assert_num_queries(4):
|
||||
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": parent.get_abilities(AnonymousUser()),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 1,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(AnonymousUser()),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 0,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(AnonymousUser()),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": sibling.get_abilities(AnonymousUser()),
|
||||
"created_at": sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(sibling.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": sibling.excerpt,
|
||||
"id": str(sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": sibling.link_reach,
|
||||
"link_role": sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": sibling.path,
|
||||
"title": sibling.title,
|
||||
"updated_at": sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_api_documents_tree_list_anonymous_public_parent():
|
||||
"""
|
||||
Anonymous users should be allowed to retrieve the tree of a document who
|
||||
has a public ancestor but only up to the highest public ancestor.
|
||||
"""
|
||||
great_grand_parent = factories.DocumentFactory(
|
||||
link_reach=random.choice(["authenticated", "restricted"])
|
||||
)
|
||||
grand_parent = factories.DocumentFactory(
|
||||
link_reach="public", parent=great_grand_parent
|
||||
)
|
||||
factories.DocumentFactory(link_reach="public", parent=great_grand_parent)
|
||||
factories.DocumentFactory(
|
||||
link_reach=random.choice(["authenticated", "restricted"]),
|
||||
parent=great_grand_parent,
|
||||
)
|
||||
|
||||
parent = factories.DocumentFactory(
|
||||
parent=grand_parent, link_reach=random.choice(["authenticated", "restricted"])
|
||||
)
|
||||
parent_sibling = factories.DocumentFactory(parent=grand_parent)
|
||||
document = factories.DocumentFactory(
|
||||
link_reach=random.choice(["authenticated", "restricted"]), parent=parent
|
||||
)
|
||||
document_sibling = factories.DocumentFactory(parent=parent)
|
||||
child = factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": grand_parent.get_abilities(AnonymousUser()),
|
||||
"created_at": grand_parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(grand_parent.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": grand_parent.excerpt,
|
||||
"id": str(grand_parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": grand_parent.link_reach,
|
||||
"link_role": grand_parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": grand_parent.path,
|
||||
"title": grand_parent.title,
|
||||
"updated_at": grand_parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": parent.get_abilities(AnonymousUser()),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(AnonymousUser()),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 0,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(AnonymousUser()),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 5,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document_sibling.get_abilities(AnonymousUser()),
|
||||
"created_at": document_sibling.created_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"creator": str(document_sibling.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document_sibling.excerpt,
|
||||
"id": str(document_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document_sibling.link_reach,
|
||||
"link_role": document_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": document_sibling.path,
|
||||
"title": document_sibling.title,
|
||||
"updated_at": document_sibling.updated_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": parent_sibling.get_abilities(AnonymousUser()),
|
||||
"created_at": parent_sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent_sibling.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent_sibling.excerpt,
|
||||
"id": str(parent_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent_sibling.link_reach,
|
||||
"link_role": parent_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": parent_sibling.path,
|
||||
"title": parent_sibling.title,
|
||||
"updated_at": parent_sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reach", ["restricted", "authenticated"])
|
||||
def test_api_documents_tree_list_anonymous_restricted_or_authenticated(reach):
|
||||
"""
|
||||
Anonymous users should not be able to retrieve the tree of a document that is not public.
|
||||
"""
|
||||
parent = factories.DocumentFactory(link_reach=reach)
|
||||
document = factories.DocumentFactory(parent=parent, link_reach=reach)
|
||||
factories.DocumentFactory(parent=parent)
|
||||
factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reach", ["public", "authenticated"])
|
||||
def test_api_documents_tree_list_authenticated_unrelated_public_or_authenticated(
|
||||
reach, django_assert_num_queries
|
||||
):
|
||||
"""
|
||||
Authenticated users should be able to retrieve the tree of a public/authenticated
|
||||
document to which they are not related.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach=reach)
|
||||
document, sibling = factories.DocumentFactory.create_batch(2, parent=parent)
|
||||
child = factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
with django_assert_num_queries(9):
|
||||
client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
with django_assert_num_queries(5):
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": parent.get_abilities(user),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 1,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(user),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 0,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(user),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": sibling.get_abilities(user),
|
||||
"created_at": sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(sibling.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": sibling.excerpt,
|
||||
"id": str(sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": sibling.link_reach,
|
||||
"link_role": sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": sibling.path,
|
||||
"title": sibling.title,
|
||||
"updated_at": sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reach", ["public", "authenticated"])
|
||||
def test_api_documents_tree_list_authenticated_public_or_authenticated_parent(
|
||||
reach,
|
||||
):
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve the tree of a document who
|
||||
has a public or authenticated ancestor.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
great_grand_parent = factories.DocumentFactory(link_reach="restricted")
|
||||
grand_parent = factories.DocumentFactory(
|
||||
link_reach=reach, parent=great_grand_parent
|
||||
)
|
||||
factories.DocumentFactory(
|
||||
link_reach=random.choice(["public", "authenticated"]), parent=great_grand_parent
|
||||
)
|
||||
factories.DocumentFactory(
|
||||
link_reach="restricted",
|
||||
parent=great_grand_parent,
|
||||
)
|
||||
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
parent_sibling = factories.DocumentFactory(parent=grand_parent)
|
||||
document = factories.DocumentFactory(link_reach="restricted", parent=parent)
|
||||
document_sibling = factories.DocumentFactory(parent=parent)
|
||||
child = factories.DocumentFactory(
|
||||
link_reach=random.choice(["public", "authenticated"]), parent=document
|
||||
)
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": grand_parent.get_abilities(user),
|
||||
"created_at": grand_parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(grand_parent.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": grand_parent.excerpt,
|
||||
"id": str(grand_parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": grand_parent.link_reach,
|
||||
"link_role": grand_parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": grand_parent.path,
|
||||
"title": grand_parent.title,
|
||||
"updated_at": grand_parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": parent.get_abilities(user),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 0,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(user),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 0,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(user),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 5,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": document_sibling.get_abilities(user),
|
||||
"created_at": document_sibling.created_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"creator": str(document_sibling.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document_sibling.excerpt,
|
||||
"id": str(document_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document_sibling.link_reach,
|
||||
"link_role": document_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": document_sibling.path,
|
||||
"title": document_sibling.title,
|
||||
"updated_at": document_sibling.updated_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"user_roles": [],
|
||||
},
|
||||
{
|
||||
"abilities": parent_sibling.get_abilities(user),
|
||||
"created_at": parent_sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent_sibling.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent_sibling.excerpt,
|
||||
"id": str(parent_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent_sibling.link_reach,
|
||||
"link_role": parent_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 0,
|
||||
"path": parent_sibling.path,
|
||||
"title": parent_sibling.title,
|
||||
"updated_at": parent_sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_api_documents_tree_list_authenticated_unrelated_restricted():
|
||||
"""
|
||||
Authenticated users should not be allowed to retrieve the tree of a document that is
|
||||
restricted and to which they are not related.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="restricted")
|
||||
document, _sibling = factories.DocumentFactory.create_batch(
|
||||
2, link_reach="restricted", parent=parent
|
||||
)
|
||||
factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/tree/",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
|
||||
def test_api_documents_tree_list_authenticated_related_direct():
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve the tree of a document
|
||||
to which they are directly related whatever the role.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="restricted")
|
||||
access = factories.UserDocumentAccessFactory(document=parent, user=user)
|
||||
factories.UserDocumentAccessFactory(document=parent)
|
||||
|
||||
document, sibling = factories.DocumentFactory.create_batch(2, parent=parent)
|
||||
child = factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/tree/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": parent.get_abilities(user),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 1,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 2,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(user),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 2,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(user),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": sibling.get_abilities(user),
|
||||
"created_at": sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(sibling.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": sibling.excerpt,
|
||||
"id": str(sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": sibling.link_reach,
|
||||
"link_role": sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": sibling.path,
|
||||
"title": sibling.title,
|
||||
"updated_at": sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_api_documents_tree_list_authenticated_related_parent():
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve the tree of a document if they
|
||||
are related to one of its ancestors whatever the role.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
great_grand_parent = factories.DocumentFactory(link_reach="restricted")
|
||||
grand_parent = factories.DocumentFactory(
|
||||
link_reach="restricted", parent=great_grand_parent
|
||||
)
|
||||
access = factories.UserDocumentAccessFactory(document=grand_parent, user=user)
|
||||
factories.UserDocumentAccessFactory(document=grand_parent)
|
||||
factories.DocumentFactory(link_reach="restricted", parent=great_grand_parent)
|
||||
factories.DocumentFactory(link_reach="public", parent=great_grand_parent)
|
||||
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
parent_sibling = factories.DocumentFactory(
|
||||
parent=grand_parent, link_reach="restricted"
|
||||
)
|
||||
document = factories.DocumentFactory(link_reach="restricted", parent=parent)
|
||||
document_sibling = factories.DocumentFactory(link_reach="restricted", parent=parent)
|
||||
child = factories.DocumentFactory(link_reach="restricted", parent=document)
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": grand_parent.get_abilities(user),
|
||||
"created_at": grand_parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(grand_parent.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": grand_parent.excerpt,
|
||||
"id": str(grand_parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": grand_parent.link_reach,
|
||||
"link_role": grand_parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 2,
|
||||
"path": grand_parent.path,
|
||||
"title": grand_parent.title,
|
||||
"updated_at": grand_parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": parent.get_abilities(user),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 2,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(user),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 2,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(user),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 5,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": document_sibling.get_abilities(user),
|
||||
"created_at": document_sibling.created_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"creator": str(document_sibling.creator.id),
|
||||
"depth": 4,
|
||||
"excerpt": document_sibling.excerpt,
|
||||
"id": str(document_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document_sibling.link_reach,
|
||||
"link_role": document_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": document_sibling.path,
|
||||
"title": document_sibling.title,
|
||||
"updated_at": document_sibling.updated_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": parent_sibling.get_abilities(user),
|
||||
"created_at": parent_sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent_sibling.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": parent_sibling.excerpt,
|
||||
"id": str(parent_sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent_sibling.link_reach,
|
||||
"link_role": parent_sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": parent_sibling.path,
|
||||
"title": parent_sibling.title,
|
||||
"updated_at": parent_sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_api_documents_tree_list_authenticated_related_team_none(mock_user_teams):
|
||||
"""
|
||||
Authenticated users should not be able to retrieve the tree of a restricted document
|
||||
related to teams in which the user is not.
|
||||
"""
|
||||
mock_user_teams.return_value = []
|
||||
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="restricted")
|
||||
document, _sibling = factories.DocumentFactory.create_batch(
|
||||
2, link_reach="restricted", parent=parent
|
||||
)
|
||||
factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
factories.TeamDocumentAccessFactory(document=document, team="myteam")
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
|
||||
def test_api_documents_tree_list_authenticated_related_team_members(
|
||||
mock_user_teams,
|
||||
):
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve the tree of a document to which they
|
||||
are related via a team whatever the role.
|
||||
"""
|
||||
mock_user_teams.return_value = ["myteam"]
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="restricted")
|
||||
document, sibling = factories.DocumentFactory.create_batch(
|
||||
2, link_reach="restricted", parent=parent
|
||||
)
|
||||
child = factories.DocumentFactory(link_reach="public", parent=document)
|
||||
|
||||
access = factories.TeamDocumentAccessFactory(document=parent, team="myteam")
|
||||
factories.TeamDocumentAccessFactory(document=parent, team="another-team")
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/tree/")
|
||||
|
||||
# pylint: disable=R0801
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"abilities": parent.get_abilities(user),
|
||||
"created_at": parent.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(parent.creator.id),
|
||||
"depth": 1,
|
||||
"excerpt": parent.excerpt,
|
||||
"id": str(parent.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": parent.link_reach,
|
||||
"link_role": parent.link_role,
|
||||
"numchild": 2,
|
||||
"nb_accesses": 2,
|
||||
"path": parent.path,
|
||||
"title": parent.title,
|
||||
"updated_at": parent.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": document.get_abilities(user),
|
||||
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(document.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": document.excerpt,
|
||||
"id": str(document.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": document.link_reach,
|
||||
"link_role": document.link_role,
|
||||
"numchild": 1,
|
||||
"nb_accesses": 2,
|
||||
"path": document.path,
|
||||
"title": document.title,
|
||||
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": child.get_abilities(user),
|
||||
"created_at": child.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(child.creator.id),
|
||||
"depth": 3,
|
||||
"excerpt": child.excerpt,
|
||||
"id": str(child.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": child.link_reach,
|
||||
"link_role": child.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": child.path,
|
||||
"title": child.title,
|
||||
"updated_at": child.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
{
|
||||
"abilities": sibling.get_abilities(user),
|
||||
"created_at": sibling.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"creator": str(sibling.creator.id),
|
||||
"depth": 2,
|
||||
"excerpt": sibling.excerpt,
|
||||
"id": str(sibling.id),
|
||||
"is_favorite": False,
|
||||
"link_reach": sibling.link_reach,
|
||||
"link_role": sibling.link_role,
|
||||
"numchild": 0,
|
||||
"nb_accesses": 2,
|
||||
"path": sibling.path,
|
||||
"title": sibling.title,
|
||||
"updated_at": sibling.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"user_roles": [access.role],
|
||||
},
|
||||
]
|
||||
@@ -166,6 +166,7 @@ def test_models_documents_get_abilities_forbidden(
|
||||
"partial_update": False,
|
||||
"restore": False,
|
||||
"retrieve": False,
|
||||
"tree": False,
|
||||
"update": False,
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -217,6 +218,7 @@ def test_models_documents_get_abilities_reader(
|
||||
"partial_update": False,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": False,
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -265,6 +267,7 @@ def test_models_documents_get_abilities_editor(
|
||||
"partial_update": True,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": True,
|
||||
"versions_destroy": False,
|
||||
"versions_list": False,
|
||||
@@ -303,6 +306,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
|
||||
"partial_update": True,
|
||||
"restore": True,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": True,
|
||||
"versions_destroy": True,
|
||||
"versions_list": True,
|
||||
@@ -342,6 +346,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
|
||||
"partial_update": True,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": True,
|
||||
"versions_destroy": True,
|
||||
"versions_list": True,
|
||||
@@ -380,6 +385,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
|
||||
"partial_update": True,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": True,
|
||||
"versions_destroy": False,
|
||||
"versions_list": True,
|
||||
@@ -425,6 +431,7 @@ def test_models_documents_get_abilities_reader_user(
|
||||
"partial_update": access_from_link,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": access_from_link,
|
||||
"versions_destroy": False,
|
||||
"versions_list": True,
|
||||
@@ -468,6 +475,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
|
||||
"partial_update": False,
|
||||
"restore": False,
|
||||
"retrieve": True,
|
||||
"tree": True,
|
||||
"update": False,
|
||||
"versions_destroy": False,
|
||||
"versions_list": True,
|
||||
|
||||
Reference in New Issue
Block a user