Skip to content
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

Allow organizing documents in a tree structure #516

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
a12834c
✨(backend) add django-treebeard to allow tree structure on documents
sampaccoud Dec 16, 2024
5d659f6
fixup! ✨(backend) add django-treebeard to allow tree structure on doc…
sampaccoud Dec 30, 2024
8f15a23
🚚(backend) split test files to make place for tests on tree structure
sampaccoud Dec 16, 2024
f5b7934
✨(backend) list only the first visible parent document for a user
sampaccoud Dec 17, 2024
79d8651
✨(backend) retrieve & update a document taking into account ancestors
sampaccoud Dec 17, 2024
31e20e1
fixup! ✨(backend) retrieve & update a document taking into account an…
sampaccoud Dec 30, 2024
eb20ecb
✨(backend) add depth, path and numchild to serialized document
sampaccoud Dec 17, 2024
a31eaba
✨(backend) add API endpoint to list a document's children
sampaccoud Dec 17, 2024
013db7d
✨(backend) add API endpoint to create children for a document
sampaccoud Dec 18, 2024
762809f
♻️(backend) remove content from list serializer and introduce excerpt
sampaccoud Dec 18, 2024
5c2dd70
fixup! ♻️(backend) remove content from list serializer and introduce …
sampaccoud Dec 20, 2024
5a3e2ef
✨(backend) add user roles as field in the document API representation
sampaccoud Jan 2, 2025
7bccf02
✨(backend) add soft delete to documents and refactor db queryset
sampaccoud Jan 2, 2025
f0be8ba
✨(backend) add API endpoint action to restore a soft deleted document
sampaccoud Jan 2, 2025
3201612
✨(backend) add API endpoint to move a document in the document tree
sampaccoud Jan 2, 2025
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
45 changes: 44 additions & 1 deletion src/backend/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
from django.contrib.auth import admin as auth_admin
from django.utils.translation import gettext_lazy as _

from treebeard.admin import TreeAdmin

from . import models


class TemplateAccessInline(admin.TabularInline):
"""Inline admin class for template accesses."""

autocomplete_fields = ["user"]
model = models.TemplateAccess
extra = 0

Expand Down Expand Up @@ -111,14 +114,46 @@ class TemplateAdmin(admin.ModelAdmin):
class DocumentAccessInline(admin.TabularInline):
"""Inline admin class for template accesses."""

autocomplete_fields = ["user"]
model = models.DocumentAccess
extra = 0


@admin.register(models.Document)
class DocumentAdmin(admin.ModelAdmin):
class DocumentAdmin(TreeAdmin):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not mandatory, but as stated in the documentation you might add

form = movenodeform_factory(models.Document)

to add the "tree" representation in the admin list (maybe you did not add it on purpose)

"""Document admin interface declaration."""

fieldsets = (
(
None,
{
"fields": (
"id",
"title",
)
},
),
(
_("Permissions"),
{
"fields": (
"creator",
"link_reach",
"link_role",
)
},
),
(
_("Tree structure"),
{
"fields": (
"path",
"depth",
"numchild",
)
},
),
)
inlines = (DocumentAccessInline,)
list_display = (
"id",
Expand All @@ -128,6 +163,14 @@ class DocumentAdmin(admin.ModelAdmin):
"created_at",
"updated_at",
)
readonly_fields = (
"creator",
"depth",
"id",
"numchild",
"path",
)
search_fields = ("id", "title")


@admin.register(models.Invitation)
Expand Down
3 changes: 2 additions & 1 deletion src/backend/core/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from core.models import DocumentAccess, RoleChoices

ACTION_FOR_METHOD_TO_PERMISSION = {
"versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"}
"versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"},
"children": {"GET": "children_list", "POST": "children_create"},
}


Expand Down
76 changes: 73 additions & 3 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,34 +147,54 @@ class ListDocumentSerializer(BaseResourceSerializer):

is_favorite = serializers.BooleanField(read_only=True)
nb_accesses = serializers.IntegerField(read_only=True)
user_roles = serializers.SerializerMethodField(read_only=True)

class Meta:
model = models.Document
fields = [
"id",
"abilities",
"content",
"created_at",
"creator",
"depth",
"excerpt",
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"numchild",
"path",
AntoLC marked this conversation as resolved.
Show resolved Hide resolved
"title",
"updated_at",
"user_roles",
]
read_only_fields = [
"id",
"abilities",
AntoLC marked this conversation as resolved.
Show resolved Hide resolved
"created_at",
"creator",
"depth",
"excerpt",
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"numchild",
"path",
"updated_at",
"user_roles",
]

def get_user_roles(self, document):
"""
Return roles of the logged-in user for the current document,
taking into account ancestors.
"""
request = self.context.get("request")
if request:
return document.get_roles(request.user)
return []


class DocumentSerializer(ListDocumentSerializer):
"""Serialize documents with all fields for display in detail views."""
Expand All @@ -189,23 +209,32 @@ class Meta:
"content",
"created_at",
"creator",
"depth",
"excerpt",
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"numchild",
"path",
"title",
"updated_at",
"user_roles",
]
read_only_fields = [
"id",
"abilities",
"created_at",
"creator",
"is_avorite",
"depth",
"is_favorite",
"link_role",
"link_reach",
"nb_accesses",
"numchild",
"path",
"updated_at",
"user_roles",
]

def get_fields(self):
Expand Down Expand Up @@ -281,7 +310,7 @@ def create(self, validated_data):
except ConversionError as err:
raise exceptions.APIException(detail="could not convert content") from err

document = models.Document.objects.create(
document = models.Document.add_root(
title=validated_data["title"],
content=document_content,
creator=user,
Expand Down Expand Up @@ -529,3 +558,44 @@ def validate_text(self, value):
if len(value.strip()) == 0:
raise serializers.ValidationError("Text field cannot be empty.")
return value


class MoveDocumentSerializer(serializers.Serializer):
"""
Serializer for validating input data to move a document within the tree structure.

Fields:
- target_document_id (UUIDField): The ID of the target parent document where the
document should be moved. This field is required and must be a valid UUID.
- position (ChoiceField): Specifies the position of the document in relation to
the target parent's children.
Choices:
- "first-child": Place the document as the first child of the target parent.
- "last-child": Place the document as the last child of the target parent (default).
- "left": Place the document as the left sibling of the target parent.
- "right": Place the document as the right sibling of the target parent.

Example:
Input payload for moving a document:
{
"target_document_id": "123e4567-e89b-12d3-a456-426614174000",
"position": "first-child"
}

Notes:
- The `target_document_id` is mandatory.
- The `position` defaults to "last-child" if not provided.
"""

target_document_id = serializers.UUIDField(required=True)
position = serializers.ChoiceField(
choices=[
"first-child",
"last-child",
"first-sibling",
"last-sibling",
"left",
"right",
],
default="last-child",
)
Loading
Loading