@@ -879,134 +879,127 @@ class MoveDocumentSerializer(serializers.Serializer):
879879 )
880880
881881
882- class ThreadSerializer (serializers .ModelSerializer ):
883- """Serialize threads in a backward compatible shape for current frontend.
882+ class ReactionSerializer (serializers .ModelSerializer ):
883+ """Serialize reactions."""
884+
885+ users = UserLightSerializer (many = True , read_only = True )
886+
887+ class Meta :
888+ model = models .Reaction
889+ fields = [
890+ "id" ,
891+ "emoji" ,
892+ "created_at" ,
893+ "updated_at" ,
894+ "users" ,
895+ ]
896+ read_only_fields = ["id" , "created_at" , "updated_at" ]
884897
885- We expose a flatten representation where ``content`` maps to the first
886- comment's body. Creating a thread requires a ``content`` field which is
887- stored as the first comment.
888- """
898+
899+ class CommentSerializer (serializers .ModelSerializer ):
900+ """Serialize comments (nested under a thread) with reactions and abilities."""
889901
890902 user = UserLightSerializer (read_only = True )
891- abilities = serializers .SerializerMethodField (read_only = True )
892- content = serializers .JSONField (write_only = True , required = True )
893- document = serializers .PrimaryKeyRelatedField (read_only = True )
903+ abilities = serializers .SerializerMethodField ()
904+ reactions = ReactionSerializer (many = True , read_only = True )
894905
895906 class Meta :
896- model = models .Thread
907+ model = models .Comment
897908 fields = [
898909 "id" ,
899- "content" , # write: body of initial comment; read: not returned (we override to_representation)
910+ "user" ,
911+ "body" ,
900912 "created_at" ,
901913 "updated_at" ,
902- "user" ,
903- "document" ,
914+ "reactions" ,
904915 "abilities" ,
905916 ]
906917 read_only_fields = [
907918 "id" ,
919+ "user" ,
908920 "created_at" ,
909921 "updated_at" ,
910- "user" ,
911- "document" ,
922+ "reactions" ,
912923 "abilities" ,
913924 ]
914925
915- def to_representation (self , instance ):
916- rep = super ().to_representation (instance )
917- # Provide first comment body as ``content`` to match previous API.
918- first_comment = instance .first_comment
919- rep ["content" ] = first_comment .body if first_comment else None
920- return rep
926+ def validate (self , attrs ):
927+ """Validate comment data."""
928+
929+ request = self .context .get ("request" )
930+ user = getattr (request , "user" , None )
921931
922- def get_abilities (self , thread ) -> dict : # type: ignore[override]
932+ attrs ["thread_id" ] = self .context ["thread_id" ]
933+ attrs ["user_id" ] = user .id if user else None
934+ return attrs
935+
936+ def get_abilities (self , obj ):
937+ """Return comment's abilities."""
923938 request = self .context .get ("request" )
924939 if request :
925- return thread .get_abilities (request .user )
940+ return obj .get_abilities (request .user )
926941 return {}
927942
928- def create (self , validated_data ):
929- request = self .context .get ("request" )
930- document_id = self .context .get ("resource_id" )
931- content = validated_data .pop ("content" )
932- document = models .Document .objects .get (pk = document_id )
933- user = request .user if request else None
934- thread = models .Thread .objects .create (document = document , user = user )
935- models .Comment .objects .create (thread = thread , user = user , body = content )
936- return thread
937-
938- def update (self , instance , validated_data ): # pragma: no cover - not used yet
939- # Allow updating first comment body for backward compatibility.
940- content = validated_data .get ("content" )
941- if content is not None :
942- first = instance .first_comment
943- if first :
944- first .body = content
945- first .save (update_fields = ["body" , "updated_at" ])
946- return instance
947-
948-
949- class CommentInThreadSerializer (serializers .ModelSerializer ):
950- """Serialize comments (nested under a thread) with reactions and abilities."""
951943
952- user = UserLightSerializer (read_only = True )
953- reactions = serializers .SerializerMethodField ()
954- abilities = serializers .SerializerMethodField ()
944+ class ThreadSerializer (serializers .ModelSerializer ):
945+ """Serialize threads in a backward compatible shape for current frontend.
946+
947+ We expose a flatten representation where ``content`` maps to the first
948+ comment's body. Creating a thread requires a ``content`` field which is
949+ stored as the first comment.
950+ """
951+
952+ creator = UserLightSerializer (read_only = True )
953+ abilities = serializers .SerializerMethodField (read_only = True )
954+ body = serializers .JSONField (write_only = True , required = True )
955+ document = serializers .PrimaryKeyRelatedField (read_only = True )
956+ comments = CommentSerializer (many = True , read_only = True )
955957
956958 class Meta :
957- model = models .Comment
959+ model = models .Thread
958960 fields = [
959961 "id" ,
960- "user" ,
961962 "body" ,
962963 "created_at" ,
963964 "updated_at" ,
964- "reactions" ,
965+ "creator" ,
966+ "document" ,
965967 "abilities" ,
968+ "comments" ,
966969 ]
967- read_only_fields = fields
968-
969- def get_reactions (self , obj ):
970- # Collect all users for reactions in a single query
971- from django .contrib .auth import get_user_model
972- User = get_user_model ()
973- reactions = list (obj .reactions .all ())
974- user_ids = set ()
975- for r in reactions :
976- user_ids .update (r .user_ids or [])
977- users_by_id = {
978- u .id : u
979- for u in User .objects .filter (id__in = user_ids ).only (
980- "id" , "email" , "full_name" , "short_name" , "language"
981- )
982- }
983- # Serialize users with UserLightSerializer semantics (full_name/short_name logic)
984- user_serializer = UserLightSerializer
985- return [
986- {
987- "emoji" : r .emoji ,
988- "created_at" : r .created_at ,
989- "users" : [
990- user_serializer (users_by_id [uid ]).data
991- for uid in r .user_ids
992- if uid in users_by_id
993- ],
994- }
995- for r in reactions
970+ read_only_fields = [
971+ "id" ,
972+ "created_at" ,
973+ "updated_at" ,
974+ "creator" ,
975+ "document" ,
976+ "abilities" ,
977+ "comments" ,
996978 ]
997979
998- def get_abilities (self , obj ):
980+ def validate (self , attrs ):
981+ """Validate thread data."""
982+ request = self .context .get ("request" )
983+ user = getattr (request , "user" , None )
984+
985+ attrs ["document_id" ] = self .context ["resource_id" ]
986+ attrs ["creator_id" ] = user .id if user else None
987+
988+ return attrs
989+
990+ def get_abilities (self , thread ):
991+ """Return thread's abilities."""
999992 request = self .context .get ("request" )
1000993 if request :
1001- return obj .get_abilities (request .user )
994+ return thread .get_abilities (request .user )
1002995 return {}
1003996
1004997
1005998class ThreadFullSerializer (serializers .ModelSerializer ):
1006999 """Full thread representation with nested comments."""
10071000
1008- user = UserLightSerializer (read_only = True )
1009- comments = serializers . SerializerMethodField ( )
1001+ creator = UserLightSerializer (read_only = True )
1002+ comments = CommentSerializer ( many = True , read_only = True )
10101003 abilities = serializers .SerializerMethodField ()
10111004
10121005 class Meta :
@@ -1015,79 +1008,19 @@ class Meta:
10151008 "id" ,
10161009 "created_at" ,
10171010 "updated_at" ,
1018- "user " ,
1011+ "creator " ,
10191012 "resolved" ,
1020- "resolved_updated_at " ,
1013+ "resolved_at " ,
10211014 "resolved_by" ,
10221015 "metadata" ,
10231016 "comments" ,
10241017 "abilities" ,
10251018 ]
10261019 read_only_fields = fields
10271020
1028- def get_comments (self , instance ):
1029- qs = instance .comments .select_related ("user" ).prefetch_related ("reactions" )
1030- return CommentInThreadSerializer (qs , many = True , context = self .context ).data
1031-
10321021 def get_abilities (self , instance ):
1022+ """Return thread's abilities."""
10331023 request = self .context .get ("request" )
10341024 if request :
10351025 return instance .get_abilities (request .user )
10361026 return {}
1037-
1038-
1039- class CreateThreadSerializer (serializers .Serializer ):
1040- body = serializers .JSONField (required = True )
1041- metadata = serializers .JSONField (required = False )
1042-
1043- def create (self , validated_data ):
1044- request = self .context .get ("request" )
1045- document = self .context .get ("document" )
1046- thread = models .Thread .objects .create (
1047- document = document ,
1048- user = request .user if request else None ,
1049- metadata = validated_data .get ("metadata" , {}),
1050- )
1051- models .Comment .objects .create (
1052- thread = thread ,
1053- user = request .user if request else None ,
1054- body = validated_data ["body" ],
1055- metadata = validated_data .get ("metadata" , {}),
1056- )
1057- return thread
1058-
1059-
1060- class CreateCommentSerializer (serializers .Serializer ):
1061- body = serializers .JSONField (required = True )
1062- metadata = serializers .JSONField (required = False )
1063-
1064- def create (self , validated_data ):
1065- request = self .context .get ("request" )
1066- thread = self .context .get ("thread" )
1067- return models .Comment .objects .create (
1068- thread = thread ,
1069- user = request .user if request else None ,
1070- body = validated_data ["body" ],
1071- metadata = validated_data .get ("metadata" , {}),
1072- )
1073-
1074-
1075- class UpdateCommentSerializer (serializers .ModelSerializer ):
1076- class Meta :
1077- model = models .Comment
1078- fields = ["body" ]
1079-
1080-
1081- class ReactionCreateSerializer (serializers .Serializer ):
1082- emoji = serializers .CharField (max_length = 32 )
1083-
1084- def save (self , ** kwargs ): # pylint: disable=unused-argument
1085- request = self .context .get ("request" )
1086- comment = self .context .get ("comment" )
1087- emoji = self .validated_data ["emoji" ]
1088- reaction , _created = models .Reaction .objects .get_or_create (
1089- comment = comment , emoji = emoji
1090- )
1091- if request and request .user :
1092- reaction .add_user (request .user )
1093- return reaction
0 commit comments