mirror of
https://github.com/suitenumerique/messages.git
synced 2026-09-27 04:04:54 +02:00
335 lines
11 KiB
Python
335 lines
11 KiB
Python
"""Client serializers for the messages core app."""
|
|
|
|
from django.db.models import Count, Q
|
|
|
|
from drf_spectacular.utils import extend_schema_field
|
|
from rest_framework import serializers
|
|
|
|
from core import models
|
|
|
|
|
|
class UserSerializer(serializers.ModelSerializer):
|
|
"""Serialize users."""
|
|
|
|
class Meta:
|
|
model = models.User
|
|
fields = ["id", "email", "full_name", "short_name"]
|
|
read_only_fields = ["id", "email", "full_name", "short_name"]
|
|
|
|
|
|
class MailboxAvailableSerializer(serializers.ModelSerializer):
|
|
"""Serialize mailboxes."""
|
|
|
|
contact = serializers.SerializerMethodField(read_only=True)
|
|
email = serializers.SerializerMethodField(read_only=True)
|
|
|
|
def get_contact(self, instance):
|
|
"""Return the contact of the mailbox."""
|
|
if instance.contact:
|
|
return instance.contact.name
|
|
|
|
def get_email(self, instance):
|
|
"""Return the email of the mailbox."""
|
|
return str(instance)
|
|
|
|
class Meta:
|
|
model = models.Mailbox
|
|
fields = ["id", "email", "contact"]
|
|
|
|
|
|
class MailboxSerializer(serializers.ModelSerializer):
|
|
"""Serialize mailboxes."""
|
|
|
|
email = serializers.SerializerMethodField(read_only=True)
|
|
role = serializers.SerializerMethodField(read_only=True)
|
|
count_unread_messages = serializers.SerializerMethodField(read_only=True)
|
|
count_messages = serializers.SerializerMethodField(read_only=True)
|
|
|
|
def get_email(self, instance):
|
|
"""Return the email of the mailbox."""
|
|
return str(instance)
|
|
|
|
def get_role(self, instance):
|
|
"""Return the allowed actions of the logged-in user on the instance."""
|
|
request = self.context.get("request")
|
|
if request:
|
|
return instance.accesses.get(user=request.user).role
|
|
return None
|
|
|
|
def get_count_unread_messages(self, instance):
|
|
"""Return the number of unread messages in the mailbox."""
|
|
return instance.thread_accesses.aggregate(
|
|
total=Count(
|
|
"thread__messages", filter=Q(thread__messages__read_at__isnull=True)
|
|
)
|
|
)["total"]
|
|
|
|
def get_count_messages(self, instance):
|
|
"""Return the number of messages in the mailbox."""
|
|
return instance.thread_accesses.aggregate(total=Count("thread__messages"))[
|
|
"total"
|
|
]
|
|
|
|
class Meta:
|
|
model = models.Mailbox
|
|
fields = ["id", "email", "role", "count_unread_messages", "count_messages"]
|
|
|
|
|
|
class ContactSerializer(serializers.ModelSerializer):
|
|
"""Serialize contacts."""
|
|
|
|
class Meta:
|
|
model = models.Contact
|
|
fields = ["id", "name", "email"]
|
|
|
|
|
|
class BlobSerializer(serializers.ModelSerializer):
|
|
"""Serialize blobs."""
|
|
|
|
blobId = serializers.UUIDField(source="id", read_only=True)
|
|
|
|
class Meta:
|
|
model = models.Blob
|
|
fields = [
|
|
"blobId",
|
|
"size",
|
|
"type",
|
|
"sha256",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
|
|
class AttachmentSerializer(serializers.ModelSerializer):
|
|
"""Serialize attachments."""
|
|
|
|
blobId = serializers.UUIDField(source="blob.id", read_only=True)
|
|
type = serializers.CharField(source="content_type", read_only=True)
|
|
|
|
class Meta:
|
|
model = models.Attachment
|
|
fields = [
|
|
"id",
|
|
"blobId",
|
|
"name",
|
|
"size",
|
|
"type",
|
|
"sha256",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
|
|
class ThreadSerializer(serializers.ModelSerializer):
|
|
"""Serialize threads."""
|
|
|
|
messages = serializers.SerializerMethodField(read_only=True)
|
|
sender_names = serializers.ListField(child=serializers.CharField(), read_only=True)
|
|
user_role = serializers.SerializerMethodField()
|
|
accesses = serializers.SerializerMethodField()
|
|
|
|
def get_accesses(self, instance):
|
|
"""Return the accesses for the thread."""
|
|
return [
|
|
{
|
|
"id": access.id,
|
|
"mailbox": {
|
|
"id": access.mailbox.id,
|
|
"email": str(access.mailbox),
|
|
"name": access.mailbox.contact.name
|
|
if access.mailbox.contact
|
|
else None,
|
|
},
|
|
"role": access.role,
|
|
}
|
|
for access in instance.accesses.select_related(
|
|
"mailbox", "mailbox__contact"
|
|
)
|
|
]
|
|
|
|
def get_messages(self, instance):
|
|
"""Return the messages in the thread."""
|
|
# Consider performance for large threads; pagination might be needed here?
|
|
return [str(message.id) for message in instance.messages.order_by("created_at")]
|
|
|
|
def get_user_role(self, instance):
|
|
"""Get current user's role for this thread."""
|
|
request = self.context.get("request")
|
|
mailbox_id = request.query_params.get("mailbox_id")
|
|
if mailbox_id:
|
|
try:
|
|
mailbox = models.Mailbox.objects.get(id=mailbox_id)
|
|
except models.Mailbox.DoesNotExist:
|
|
return None
|
|
if request and hasattr(request, "user") and request.user.is_authenticated:
|
|
try:
|
|
return instance.accesses.get(mailbox=mailbox).role
|
|
except models.ThreadAccess.DoesNotExist:
|
|
return None
|
|
return None
|
|
|
|
class Meta:
|
|
model = models.Thread
|
|
fields = [
|
|
"id",
|
|
"subject",
|
|
"snippet",
|
|
"messages",
|
|
"count_unread",
|
|
"count_trashed",
|
|
"count_draft",
|
|
"count_starred",
|
|
"count_sender",
|
|
"count_messages",
|
|
"messaged_at",
|
|
"sender_names",
|
|
"updated_at",
|
|
"user_role",
|
|
"accesses",
|
|
]
|
|
read_only_fields = fields # Mark all as read-only for safety
|
|
|
|
|
|
class MessageSerializer(serializers.ModelSerializer):
|
|
"""
|
|
Serialize messages, getting parsed details from the Message model.
|
|
Aligns field names with JMAP where appropriate (textBody, htmlBody, to, cc, bcc).
|
|
"""
|
|
|
|
# JMAP-style body fields (from model's parsed data)
|
|
textBody = serializers.SerializerMethodField(read_only=True)
|
|
htmlBody = serializers.SerializerMethodField(read_only=True)
|
|
draftBody = serializers.SerializerMethodField(read_only=True)
|
|
attachments = serializers.SerializerMethodField(read_only=True)
|
|
|
|
# JMAP-style recipient fields (from model's parsed data)
|
|
to = serializers.SerializerMethodField(read_only=True)
|
|
cc = serializers.SerializerMethodField(read_only=True)
|
|
bcc = serializers.SerializerMethodField(read_only=True)
|
|
|
|
sender = ContactSerializer(read_only=True) # Sender contact info
|
|
|
|
# UUID of the parent message
|
|
parent_id = serializers.UUIDField(
|
|
source="parent.id", allow_null=True, read_only=True
|
|
)
|
|
|
|
# UUID of the thread
|
|
thread_id = serializers.UUIDField(
|
|
source="thread.id", allow_null=True, read_only=True
|
|
)
|
|
|
|
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
|
|
def get_textBody(self, instance): # pylint: disable=invalid-name
|
|
"""Return the list of text body parts (JMAP style)."""
|
|
return instance.get_parsed_field("textBody") or []
|
|
|
|
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
|
|
def get_htmlBody(self, instance): # pylint: disable=invalid-name
|
|
"""Return the list of HTML body parts (JMAP style)."""
|
|
return instance.get_parsed_field("htmlBody") or []
|
|
|
|
@extend_schema_field(serializers.CharField())
|
|
def get_draftBody(self, instance): # pylint: disable=invalid-name
|
|
"""Return an arbitrary JSON object representing the draft body."""
|
|
return instance.draft_body
|
|
|
|
@extend_schema_field(AttachmentSerializer(many=True))
|
|
def get_attachments(self, instance):
|
|
"""Return the parsed email attachments or linked attachments for drafts."""
|
|
# First check for directly linked attachments (for drafts)
|
|
if instance.attachments.exists():
|
|
return AttachmentSerializer(instance.attachments.all(), many=True).data
|
|
|
|
# Then get any parsed attachments from the email if available
|
|
parsed_attachments = instance.get_parsed_field("attachments") or []
|
|
|
|
# Convert parsed attachments to a format similar to AttachmentSerializer
|
|
if parsed_attachments:
|
|
return parsed_attachments
|
|
|
|
return []
|
|
|
|
@extend_schema_field(ContactSerializer(many=True))
|
|
def get_to(self, instance):
|
|
"""Return the 'To' recipients."""
|
|
contacts = models.Contact.objects.filter(
|
|
id__in=instance.recipients.filter(
|
|
type=models.MessageRecipientTypeChoices.TO
|
|
).values_list("contact", flat=True)
|
|
)
|
|
return ContactSerializer(contacts, many=True).data
|
|
|
|
@extend_schema_field(ContactSerializer(many=True))
|
|
def get_cc(self, instance):
|
|
"""Return the 'Cc' recipients."""
|
|
contacts = models.Contact.objects.filter(
|
|
id__in=instance.recipients.filter(
|
|
type=models.MessageRecipientTypeChoices.CC
|
|
).values_list("contact", flat=True)
|
|
)
|
|
return ContactSerializer(contacts, many=True).data
|
|
|
|
@extend_schema_field(ContactSerializer(many=True))
|
|
def get_bcc(self, instance):
|
|
"""
|
|
Return the 'Bcc' recipients, only if the requesting user is allowed to see them.
|
|
"""
|
|
request = self.context.get("request")
|
|
# Only show Bcc if it's a mailbox the user has access to and it's a sent message.
|
|
# TODO: add some tests for this
|
|
|
|
if (
|
|
request
|
|
and hasattr(request, "user")
|
|
and request.user.is_authenticated
|
|
and instance.is_sender
|
|
and instance.thread.accesses.filter(
|
|
mailbox__accesses__user=request.user,
|
|
role=models.ThreadAccessRoleChoices.EDITOR,
|
|
).exists()
|
|
):
|
|
contacts = models.Contact.objects.filter(
|
|
id__in=instance.recipients.filter(
|
|
type=models.MessageRecipientTypeChoices.BCC
|
|
).values_list("contact", flat=True)
|
|
)
|
|
return ContactSerializer(contacts, many=True).data
|
|
return [] # Hide Bcc by default
|
|
|
|
class Meta:
|
|
model = models.Message
|
|
fields = [
|
|
"id",
|
|
"parent_id",
|
|
"thread_id",
|
|
"subject",
|
|
"created_at",
|
|
"updated_at",
|
|
"htmlBody",
|
|
"textBody",
|
|
"draftBody",
|
|
"attachments",
|
|
"sender",
|
|
"to",
|
|
"cc",
|
|
"bcc",
|
|
"read_at",
|
|
"sent_at",
|
|
"is_sender",
|
|
"is_draft",
|
|
"is_unread",
|
|
"is_starred",
|
|
"is_trashed",
|
|
]
|
|
read_only_fields = fields # Mark all as read-only
|
|
|
|
|
|
class ThreadAccessSerializer(serializers.ModelSerializer):
|
|
"""Serialize thread access information."""
|
|
|
|
class Meta:
|
|
model = models.ThreadAccess
|
|
fields = ["id", "thread", "mailbox", "role", "created_at", "updated_at"]
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|