from __future__ import annotations from rest_framework import serializers from apps.groups.models import ( GroupBook, MemberProgress, ReadingGroup, ReadingGroupMembership, ReadingSchedule, Section, ) from apps.books.serializers import EBookListSerializer class ReadingGroupMembershipSerializer(serializers.ModelSerializer): user_email = serializers.CharField(source="user.email", read_only=True) user_username = serializers.CharField(source="user.username", read_only=True) class Meta: model = ReadingGroupMembership fields = [ "id", "user", "user_email", "user_username", "role", "joined_at", ] read_only_fields = ["id", "joined_at"] class ReadingGroupListSerializer(serializers.ModelSerializer): member_count = serializers.SerializerMethodField() admin_email = serializers.CharField(source="admin.email", read_only=True) class Meta: model = ReadingGroup fields = [ "id", "name", "description", "admin", "admin_email", "member_count", "created_at", "updated_at", ] def get_member_count(self, obj: ReadingGroup) -> int: return obj.memberships.count() class ReadingGroupDetailSerializer(serializers.ModelSerializer): admin_email = serializers.CharField(source="admin.email", read_only=True) members = ReadingGroupMembershipSerializer(source="memberships", many=True, read_only=True) class Meta: model = ReadingGroup fields = [ "id", "name", "description", "admin", "admin_email", "members", "created_at", "updated_at", ] class ReadingGroupCreateSerializer(serializers.ModelSerializer): class Meta: model = ReadingGroup fields = ["name", "description"] class AddMemberSerializer(serializers.Serializer): user_id = serializers.IntegerField() def validate_user_id(self, value: int) -> int: from django.conf import settings User = settings.AUTH_USER_MODEL if not User.objects.filter(id=value).exists(): raise serializers.ValidationError("User not found.") return value class SectionSerializer(serializers.ModelSerializer): class Meta: model = Section fields = [ "id", "title", "order", "start_chapter_index", "end_chapter_index", "estimated_reading_minutes", "created_at", ] read_only_fields = ["id", "created_at"] class SectionAdjustSerializer(serializers.Serializer): """Payload for manual section adjustments (merge/split).""" operation = serializers.ChoiceField(choices=["merge", "split"]) section_ids = serializers.ListField( child=serializers.IntegerField(), min_length=1, help_text="For merge: list of section IDs to merge. For split: [section_id] to split." ) split_at = serializers.IntegerField( required=False, default=2, min_value=2, help_text="Number of new sections when splitting" ) class ReadingScheduleSerializer(serializers.ModelSerializer): section_details = serializers.SerializerMethodField() class Meta: model = ReadingSchedule fields = [ "id", "meeting_number", "week_date", "section_ids", "section_details", "created_at", ] read_only_fields = ["id", "created_at"] def get_section_details(self, obj: ReadingSchedule) -> list[dict]: sections = Section.objects.filter( id__in=obj.section_ids, group_book=obj.group_book ).order_by("order") return SectionSerializer(sections, many=True).data class MemberProgressSerializer(serializers.ModelSerializer): user_email = serializers.CharField(source="user.email", read_only=True) user_username = serializers.CharField(source="user.username", read_only=True) current_section_title = serializers.SerializerMethodField() class Meta: model = MemberProgress fields = [ "id", "user", "user_email", "user_username", "current_section", "current_section_title", "completed_sections", "updated_at", ] read_only_fields = ["id", "updated_at"] def get_current_section_title(self, obj: MemberProgress) -> str | None: if obj.current_section: return obj.current_section.title return None class GroupBookListSerializer(serializers.ModelSerializer): ebook = EBookListSerializer(read_only=True) uploaded_by_email = serializers.CharField(source="uploaded_by.email", read_only=True) section_count = serializers.SerializerMethodField() class Meta: model = GroupBook fields = [ "id", "title", "status", "ebook", "uploaded_by", "uploaded_by_email", "section_count", "created_at", "updated_at", ] def get_section_count(self, obj: GroupBook) -> int: return obj.sections.count() class GroupBookDetailSerializer(serializers.ModelSerializer): ebook = EBookListSerializer(read_only=True) uploaded_by_email = serializers.CharField(source="uploaded_by.email", read_only=True) sections = SectionSerializer(many=True, read_only=True) schedules = ReadingScheduleSerializer(source="schedules", many=True, read_only=True) class Meta: model = GroupBook fields = [ "id", "group", "title", "status", "ebook", "uploaded_by", "uploaded_by_email", "sections", "schedules", "created_at", "updated_at", ] class GroupBookCreateSerializer(serializers.Serializer): ebook_id = serializers.IntegerField() title = serializers.CharField(max_length=512, required=False) def validate_ebook_id(self, value: int) -> int: from apps.books.models import EBook if not EBook.objects.filter(id=value).exists(): raise serializers.ValidationError("EBook not found.") # Check format is EPUB ebook = EBook.objects.get(id=value) if ebook.format != "epub": raise serializers.ValidationError("Only EPUB format is supported for group books.") return value def validate(self, data: dict) -> dict: request = self.context.get("request") if request and not data.get("title"): from apps.books.models import EBook ebook = EBook.objects.get(id=data["ebook_id"]) data["title"] = ebook.title return data