152 lines
5.6 KiB
Python
152 lines
5.6 KiB
Python
from django.contrib.auth import get_user_model
|
|
from rest_framework import serializers
|
|
from drf_spectacular.utils import extend_schema_field
|
|
from .models import Chat, ChatReadStatus, Message, MessageFile, MessageHistory, MessageReaction
|
|
|
|
|
|
|
|
class MessageSenderSerializer(serializers.ModelSerializer):
|
|
avatar = serializers.SerializerMethodField()
|
|
|
|
def get_avatar(self, obj):
|
|
from django.conf import settings
|
|
if obj.avatar:
|
|
return settings.MEDIA_URL + obj.avatar.name
|
|
return None
|
|
|
|
class Meta:
|
|
model = get_user_model()
|
|
fields = ['id', 'username', 'avatar']
|
|
read_only_fields = ['id', 'username']
|
|
|
|
|
|
class MessageFileSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MessageFile
|
|
fields = ['id', 'file', 'media_type', 'uploaded_at']
|
|
read_only_fields = ['uploaded_at']
|
|
|
|
|
|
class MessageReactionSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MessageReaction
|
|
fields = ['id', 'user', 'emoji', 'created_at']
|
|
read_only_fields = ['user', 'created_at']
|
|
|
|
|
|
class MessageHistorySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MessageHistory
|
|
fields = ['id', 'old_content', 'archived_at']
|
|
read_only_fields = ['archived_at']
|
|
|
|
|
|
class ReplyToSerializer(serializers.ModelSerializer):
|
|
sender = MessageSenderSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = Message
|
|
fields = ['id', 'content', 'sender']
|
|
read_only_fields = ['id', 'content', 'sender']
|
|
|
|
|
|
class MessageSerializer(serializers.ModelSerializer):
|
|
sender = MessageSenderSerializer(read_only=True)
|
|
# reply_to is a SerializerMethodField so we can bypass ActiveManager and
|
|
# still surface a tombstone when the original message is soft-deleted.
|
|
reply_to = serializers.SerializerMethodField()
|
|
media_files = MessageFileSerializer(many=True, read_only=True)
|
|
reactions = MessageReactionSerializer(many=True, read_only=True)
|
|
|
|
@extend_schema_field(ReplyToSerializer)
|
|
def get_reply_to(self, obj):
|
|
"""
|
|
Fetch the reply-to message via all_objects so soft-deleted originals
|
|
are still accessible. Returns content=None when the message is deleted,
|
|
which the frontend renders as a '(zpráva smazána)' tombstone.
|
|
"""
|
|
reply_to_id = obj.reply_to_id
|
|
if not reply_to_id:
|
|
return None
|
|
try:
|
|
msg = Message.all_objects.select_related('sender').get(pk=reply_to_id)
|
|
except Message.DoesNotExist:
|
|
return None
|
|
|
|
sender_data = None
|
|
if msg.sender:
|
|
from django.conf import settings
|
|
avatar = (settings.MEDIA_URL + msg.sender.avatar.name) if msg.sender.avatar else None
|
|
sender_data = {'id': msg.sender.id, 'username': msg.sender.username, 'avatar': avatar}
|
|
else:
|
|
sender_data = {'id': 0, 'username': '…', 'avatar': None}
|
|
|
|
return {
|
|
'id': msg.id,
|
|
# content=None signals the frontend to show the deleted tombstone
|
|
'content': None if msg.is_deleted else msg.content,
|
|
'sender': sender_data,
|
|
}
|
|
|
|
class Meta:
|
|
model = Message
|
|
fields = [
|
|
'id', 'chat', 'sender', 'reply_to',
|
|
'content', 'is_edited', 'edited_at',
|
|
'created_at', 'updated_at',
|
|
'media_files', 'reactions',
|
|
]
|
|
read_only_fields = ['sender', 'chat', 'reply_to', 'is_edited', 'edited_at', 'created_at', 'updated_at']
|
|
|
|
|
|
class MessageSendSerializer(serializers.Serializer):
|
|
"""Used for the HTTP send endpoint (text + optional files)."""
|
|
chat = serializers.PrimaryKeyRelatedField(queryset=Chat.objects.all())
|
|
content = serializers.CharField(required=False, allow_blank=True, default='')
|
|
reply_to = serializers.PrimaryKeyRelatedField(
|
|
queryset=Message.objects.all(), required=False, allow_null=True
|
|
)
|
|
# files come via request.FILES - declared here for schema documentation only
|
|
files = serializers.ListField(
|
|
child=serializers.FileField(allow_empty_file=False, use_url=False),
|
|
required=False,
|
|
default=list,
|
|
write_only=True,
|
|
)
|
|
|
|
def validate(self, attrs):
|
|
if not attrs.get('content') and not attrs.get('files'):
|
|
raise serializers.ValidationError('A message must have content or at least one file.')
|
|
return attrs
|
|
|
|
|
|
class ChatSerializer(serializers.ModelSerializer):
|
|
unread_count = serializers.SerializerMethodField(read_only=True)
|
|
members_detail = MessageSenderSerializer(source='members', many=True, read_only=True)
|
|
|
|
@extend_schema_field(serializers.IntegerField())
|
|
def get_unread_count(self, obj):
|
|
request = self.context.get('request')
|
|
if not request or not request.user.is_authenticated:
|
|
return 0
|
|
last_read = ChatReadStatus.objects.filter(
|
|
user=request.user, chat=obj
|
|
).values_list('last_read_at', flat=True).first()
|
|
if last_read is None:
|
|
return obj.messages.filter(is_deleted=False).count()
|
|
return obj.messages.filter(created_at__gt=last_read, is_deleted=False).count()
|
|
|
|
class Meta:
|
|
model = Chat
|
|
fields = [
|
|
'id', 'chat_type', 'owner', 'name',
|
|
'icon', 'banner', 'members', 'moderators',
|
|
'hub', 'created_at', 'updated_at', 'unread_count',
|
|
'members_detail',
|
|
]
|
|
read_only_fields = ['owner', 'created_at', 'updated_at', 'unread_count', 'members_detail']
|
|
|
|
|
|
class ChatMemberSerializer(serializers.Serializer):
|
|
user_id = serializers.IntegerField(help_text='PK of the user to add or remove.')
|