Skip to content

[DRAFT] Feature/add page tree #697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to

## Added

- ✨(backend) limit link reach/role select options depending on ancestors #645
- ✨(backend) add new "descendants" action to document API endpoint #645
- ✨(backend) new "tree" action on document detail endpoint #645
- ✨(backend) allow forcing page size within limits #645
- 💄(frontend) add error pages #643

## Changed
Expand All @@ -22,6 +26,7 @@ and this project adheres to
## Fixed

- ♻️(frontend) improve table pdf rendering
- 🐛(backend) refactor to fix filtering on children and descendants views #645

## [2.2.0] - 2025-02-10

Expand Down
17 changes: 14 additions & 3 deletions src/backend/core/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,26 @@ class DocumentFilter(django_filters.FilterSet):
Custom filter for filtering documents.
"""

title = django_filters.CharFilter(
field_name="title", lookup_expr="icontains", label=_("Title")
)

class Meta:
model = models.Document
fields = ["title"]


class ListDocumentFilter(DocumentFilter):
"""
Custom filter for filtering documents.
"""

is_creator_me = django_filters.BooleanFilter(
method="filter_is_creator_me", label=_("Creator is me")
)
is_favorite = django_filters.BooleanFilter(
method="filter_is_favorite", label=_("Favorite")
)
title = django_filters.CharFilter(
field_name="title", lookup_expr="icontains", label=_("Title")
)

class Meta:
model = models.Document
Expand Down
62 changes: 40 additions & 22 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,14 @@ class Meta:
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)
nb_accesses_ancestors = serializers.IntegerField(read_only=True)
nb_accesses_direct = serializers.IntegerField(read_only=True)
user_roles = serializers.SerializerMethodField(read_only=True)
abilities = serializers.SerializerMethodField(read_only=True)

class Meta:
model = models.Document
Expand All @@ -161,7 +149,8 @@ class Meta:
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"nb_accesses_ancestors",
"nb_accesses_direct",
"numchild",
"path",
"title",
Expand All @@ -178,13 +167,30 @@ class Meta:
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"nb_accesses_ancestors",
"nb_accesses_direct",
"numchild",
"path",
"updated_at",
"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)
# Retrieve ancestor links from paths_links_mapping (if provided)
ancestors_links = (
paths_links_mapping.get(document.path[: -document.steplen])
if paths_links_mapping
else None
)
return document.get_abilities(request.user, ancestors_links=ancestors_links)

return {}

def get_user_roles(self, document):
"""
Return roles of the logged-in user for the current document,
Expand Down Expand Up @@ -214,7 +220,8 @@ class Meta:
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"nb_accesses_ancestors",
"nb_accesses_direct",
"numchild",
"path",
"title",
Expand All @@ -230,7 +237,8 @@ class Meta:
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"nb_accesses_ancestors",
"nb_accesses_direct",
"numchild",
"path",
"updated_at",
Expand Down Expand Up @@ -359,7 +367,7 @@ def update(self, instance, validated_data):
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.
Expand Down Expand Up @@ -429,9 +437,12 @@ def validate(self, attrs):
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 = [
Expand All @@ -445,6 +456,13 @@ class Meta:
]
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):
Expand Down
29 changes: 29 additions & 0 deletions src/backend/core/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,35 @@
from rest_framework.throttling import BaseThrottle


def nest_tree(flat_list, steplen):
"""
Convert a flat list of serialized documents into a nested tree making advantage
of the`path` field and its step length.
"""
node_dict = {}
roots = []

# Sort the flat list by path to ensure parent nodes are processed first
flat_list.sort(key=lambda x: x["path"])

for node in flat_list:
node["children"] = [] # Initialize children list
node_dict[node["path"]] = node

# Determine parent path
parent_path = node["path"][:-steplen]

if parent_path in node_dict:
node_dict[parent_path]["children"].append(node)
else:
roots.append(node) # Collect root nodes

if len(roots) > 1:
raise ValueError("More than one root element detected.")

return roots[0] if roots else None


def filter_root_paths(paths, skip_sorting=False):
"""
Filters root paths from a list of paths representing a tree structure.
Expand Down
Loading