Skip to content

Commit efc90fa

Browse files
committed
adapt back with blocknote interface
1 parent 0a53ca5 commit efc90fa

16 files changed

+727
-192
lines changed

src/backend/core/api/serializers.py

Lines changed: 183 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -823,17 +823,24 @@ class MoveDocumentSerializer(serializers.Serializer):
823823
)
824824

825825

826-
class CommentSerializer(serializers.ModelSerializer):
827-
"""Serialize comments."""
826+
class ThreadSerializer(serializers.ModelSerializer):
827+
"""Serialize threads in a backward compatible shape for current frontend.
828+
829+
We expose a flatten representation where ``content`` maps to the first
830+
comment's body. Creating a thread requires a ``content`` field which is
831+
stored as the first comment.
832+
"""
828833

829834
user = UserLightSerializer(read_only=True)
830835
abilities = serializers.SerializerMethodField(read_only=True)
836+
content = serializers.JSONField(write_only=True, required=True)
837+
document = serializers.PrimaryKeyRelatedField(read_only=True)
831838

832839
class Meta:
833-
model = models.Comment
840+
model = models.Thread
834841
fields = [
835842
"id",
836-
"content",
843+
"content", # write: body of initial comment; read: not returned (we override to_representation)
837844
"created_at",
838845
"updated_at",
839846
"user",
@@ -849,19 +856,182 @@ class Meta:
849856
"abilities",
850857
]
851858

852-
def get_abilities(self, comment) -> dict:
853-
"""Return abilities of the logged-in user on the instance."""
859+
def to_representation(self, instance):
860+
rep = super().to_representation(instance)
861+
# Provide first comment body as ``content`` to match previous API.
862+
first_comment = instance.first_comment
863+
rep["content"] = first_comment.body if first_comment else None
864+
return rep
865+
866+
def get_abilities(self, thread) -> dict: # type: ignore[override]
854867
request = self.context.get("request")
855868
if request:
856-
return comment.get_abilities(request.user)
869+
return thread.get_abilities(request.user)
857870
return {}
858871

859-
def validate(self, attrs):
860-
"""Validate invitation data."""
872+
def create(self, validated_data):
861873
request = self.context.get("request")
862-
user = getattr(request, "user", None)
874+
document_id = self.context.get("resource_id")
875+
content = validated_data.pop("content")
876+
document = models.Document.objects.get(pk=document_id)
877+
user = request.user if request else None
878+
thread = models.Thread.objects.create(document=document, user=user)
879+
models.Comment.objects.create(thread=thread, user=user, body=content)
880+
return thread
881+
882+
def update(self, instance, validated_data): # pragma: no cover - not used yet
883+
# Allow updating first comment body for backward compatibility.
884+
content = validated_data.get("content")
885+
if content is not None:
886+
first = instance.first_comment
887+
if first:
888+
first.body = content
889+
first.save(update_fields=["body", "updated_at"])
890+
return instance
891+
892+
893+
class CommentInThreadSerializer(serializers.ModelSerializer):
894+
"""Serialize comments (nested under a thread) with reactions and abilities."""
863895

864-
attrs["document_id"] = self.context["resource_id"]
865-
attrs["user_id"] = user.id if user else None
896+
user = UserLightSerializer(read_only=True)
897+
reactions = serializers.SerializerMethodField()
898+
abilities = serializers.SerializerMethodField()
866899

867-
return attrs
900+
class Meta:
901+
model = models.Comment
902+
fields = [
903+
"id",
904+
"user",
905+
"body",
906+
"created_at",
907+
"updated_at",
908+
"reactions",
909+
"abilities",
910+
]
911+
read_only_fields = fields
912+
913+
def get_reactions(self, obj):
914+
# Collect all users for reactions in a single query
915+
from django.contrib.auth import get_user_model
916+
User = get_user_model()
917+
reactions = list(obj.reactions.all())
918+
user_ids = set()
919+
for r in reactions:
920+
user_ids.update(r.user_ids or [])
921+
users_by_id = {
922+
u.id: u
923+
for u in User.objects.filter(id__in=user_ids).only(
924+
"id", "email", "full_name", "short_name", "language"
925+
)
926+
}
927+
# Serialize users with UserLightSerializer semantics (full_name/short_name logic)
928+
user_serializer = UserLightSerializer
929+
return [
930+
{
931+
"emoji": r.emoji,
932+
"created_at": r.created_at,
933+
"users": [
934+
user_serializer(users_by_id[uid]).data
935+
for uid in r.user_ids
936+
if uid in users_by_id
937+
],
938+
}
939+
for r in reactions
940+
]
941+
942+
def get_abilities(self, obj):
943+
request = self.context.get("request")
944+
if request:
945+
return obj.get_abilities(request.user)
946+
return {}
947+
948+
949+
class ThreadFullSerializer(serializers.ModelSerializer):
950+
"""Full thread representation with nested comments."""
951+
952+
user = UserLightSerializer(read_only=True)
953+
comments = serializers.SerializerMethodField()
954+
abilities = serializers.SerializerMethodField()
955+
956+
class Meta:
957+
model = models.Thread
958+
fields = [
959+
"id",
960+
"created_at",
961+
"updated_at",
962+
"user",
963+
"resolved",
964+
"resolved_updated_at",
965+
"resolved_by",
966+
"metadata",
967+
"comments",
968+
"abilities",
969+
]
970+
read_only_fields = fields
971+
972+
def get_comments(self, instance):
973+
qs = instance.comments.select_related("user").prefetch_related("reactions")
974+
return CommentInThreadSerializer(qs, many=True, context=self.context).data
975+
976+
def get_abilities(self, instance):
977+
request = self.context.get("request")
978+
if request:
979+
return instance.get_abilities(request.user)
980+
return {}
981+
982+
983+
class CreateThreadSerializer(serializers.Serializer):
984+
body = serializers.JSONField(required=True)
985+
metadata = serializers.JSONField(required=False)
986+
987+
def create(self, validated_data):
988+
request = self.context.get("request")
989+
document = self.context.get("document")
990+
thread = models.Thread.objects.create(
991+
document=document,
992+
user=request.user if request else None,
993+
metadata=validated_data.get("metadata", {}),
994+
)
995+
models.Comment.objects.create(
996+
thread=thread,
997+
user=request.user if request else None,
998+
body=validated_data["body"],
999+
metadata=validated_data.get("metadata", {}),
1000+
)
1001+
return thread
1002+
1003+
1004+
class CreateCommentSerializer(serializers.Serializer):
1005+
body = serializers.JSONField(required=True)
1006+
metadata = serializers.JSONField(required=False)
1007+
1008+
def create(self, validated_data):
1009+
request = self.context.get("request")
1010+
thread = self.context.get("thread")
1011+
return models.Comment.objects.create(
1012+
thread=thread,
1013+
user=request.user if request else None,
1014+
body=validated_data["body"],
1015+
metadata=validated_data.get("metadata", {}),
1016+
)
1017+
1018+
1019+
class UpdateCommentSerializer(serializers.ModelSerializer):
1020+
class Meta:
1021+
model = models.Comment
1022+
fields = ["body"]
1023+
1024+
1025+
class ReactionCreateSerializer(serializers.Serializer):
1026+
emoji = serializers.CharField(max_length=32)
1027+
1028+
def save(self, **kwargs): # pylint: disable=unused-argument
1029+
request = self.context.get("request")
1030+
comment = self.context.get("comment")
1031+
emoji = self.validated_data["emoji"]
1032+
reaction, _created = models.Reaction.objects.get_or_create(
1033+
comment=comment, emoji=emoji
1034+
)
1035+
if request and request.user:
1036+
reaction.add_user(request.user)
1037+
return reaction

0 commit comments

Comments
 (0)