from rest_framework import serializers from apps.groups.models import GroupMembership, MemberProgress, ReadingGroup class GroupMembershipSerializer(serializers.ModelSerializer): user_email = serializers.CharField(source="user.email", read_only=True) user_id = serializers.IntegerField(source="user.id", read_only=True) role = serializers.CharField(read_only=True) class Meta: model = GroupMembership fields = ["id", "user_id", "user_email", "role", "joined_at"] class MemberProgressSerializer(serializers.ModelSerializer): user_email = serializers.CharField(source="user.email", read_only=True) user_id = serializers.IntegerField(source="user.id", read_only=True) total_sections = serializers.SerializerMethodField() section_label = serializers.SerializerMethodField() class Meta: model = MemberProgress fields = [ "id", "user_id", "user_email", "current_section", "total_sections", "section_label", "percentage", "time_spent_seconds", "last_position", "is_public", "updated_at", ] read_only_fields = ["id", "user_id", "user_email", "updated_at"] extra_kwargs = { "percentage": {"required": False, "min_value": 0.0, "max_value": 100.0}, } def get_total_sections(self, obj: MemberProgress) -> int: return obj.total_sections() def get_section_label(self, obj: MemberProgress) -> str: ts = obj.total_sections() if ts: return f"Section {obj.current_section} of {ts}" return "No sections" def validate_percentage(self, value: float) -> float: if value < 0.0 or value > 100.0: raise serializers.ValidationError("Percentage must be between 0.0 and 100.0.") return value class MemberProgressPublicSerializer(serializers.ModelSerializer): """Limited view for other group members — section label only, no exact position.""" user_email = serializers.CharField(source="user.email", read_only=True) user_id = serializers.IntegerField(source="user.id", read_only=True) total_sections = serializers.SerializerMethodField() section_label = serializers.SerializerMethodField() class Meta: model = MemberProgress fields = [ "user_id", "user_email", "current_section", "total_sections", "section_label", "percentage", "time_spent_seconds", "updated_at", ] def get_total_sections(self, obj: MemberProgress) -> int: return obj.total_sections() def get_section_label(self, obj: MemberProgress) -> str: ts = obj.total_sections() if ts: return f"Section {obj.current_section} of {ts}" return "No sections" class MemberProgressUpdateSerializer(serializers.Serializer): current_section = serializers.IntegerField(min_value=0) percentage = serializers.FloatField(required=False, min_value=0.0, max_value=100.0) time_spent_delta = serializers.IntegerField(default=0, min_value=0) last_position = serializers.JSONField(required=False, default=dict) is_public = serializers.BooleanField(required=False) class ReadingGroupListSerializer(serializers.ModelSerializer): created_by_email = serializers.CharField(source="created_by.email", read_only=True) ebook_title = serializers.CharField(source="ebook.title", read_only=True) ebook_author = serializers.CharField(source="ebook.author", read_only=True) member_count = serializers.SerializerMethodField() my_progress = serializers.SerializerMethodField() class Meta: model = ReadingGroup fields = [ "id", "name", "ebook", "ebook_title", "ebook_author", "created_by_email", "description", "member_count", "my_progress", "created_at", "updated_at", ] def get_member_count(self, obj: ReadingGroup) -> int: return obj.memberships.count() def get_my_progress(self, obj: ReadingGroup) -> dict | None: request = self.context.get("request") if not request or not request.user.is_authenticated: return None try: mp = MemberProgress.objects.get(group=obj, user=request.user) except MemberProgress.DoesNotExist: return None return { "current_section": mp.current_section, "percentage": mp.percentage, "time_spent_seconds": mp.time_spent_seconds, "section_label": MemberProgressSerializer().get_section_label(mp), } class ReadingGroupDetailSerializer(ReadingGroupListSerializer): members = serializers.SerializerMethodField() class Meta(ReadingGroupListSerializer.Meta): fields = ReadingGroupListSerializer.Meta.fields + ["members"] def get_members(self, obj: ReadingGroup) -> list[dict]: memberships = obj.memberships.select_related("user").prefetch_related("progress") result: list[dict] = [] for m in memberships: entry: dict = { "id": m.id, "user_id": m.user.id, "user_email": m.user.email, "role": m.role, "joined_at": m.joined_at, } try: mp = m.progress except MemberProgress.DoesNotExist: entry["progress"] = None else: if mp.is_public: entry["progress"] = MemberProgressPublicSerializer(mp).data else: entry["progress"] = {"is_public": False, "note": "Private"} result.append(entry) return result class ReadingGroupCreateSerializer(serializers.ModelSerializer): class Meta: model = ReadingGroup fields = ["name", "ebook", "description"] class AdminProgressSummarySerializer(serializers.Serializer): group_id = serializers.IntegerField() group_name = serializers.CharField() ebook_title = serializers.CharField() total_members = serializers.IntegerField() members_started = serializers.IntegerField() members_finished = serializers.IntegerField() average_percentage = serializers.FloatField() average_time_spent_hours = serializers.FloatField() member_details = serializers.ListField(child=serializers.JSONField())