from __future__ import annotations from rest_framework import serializers from apps.groups.models import Group, GroupInvite, GroupMember, GroupRole, JoinRequest, JoinRequestStatus class GroupMemberSerializer(serializers.ModelSerializer): user_id = serializers.IntegerField(source="user.id", read_only=True) user_email = serializers.CharField(source="user.email", read_only=True) user_username = serializers.CharField(source="user.username", read_only=True) class Meta: model = GroupMember fields = [ "id", "user_id", "user_email", "user_username", "role", "joined_at", ] class GroupListSerializer(serializers.ModelSerializer): member_count = serializers.SerializerMethodField() user_role = serializers.SerializerMethodField() class Meta: model = Group fields = [ "id", "name", "description", "created_by", "member_count", "user_role", "created_at", "updated_at", ] def get_member_count(self, obj: Group) -> int: return getattr(obj, "_member_count", obj.memberships.count()) def get_user_role(self, obj: Group) -> str | None: request = self.context.get("request") if not request or not request.user.is_authenticated: return None membership = getattr(obj, "_user_membership", None) if membership is None: try: membership = obj.memberships.get(user=request.user) except GroupMember.DoesNotExist: return None return membership.role class GroupDetailSerializer(serializers.ModelSerializer): members = GroupMemberSerializer(source="memberships", many=True, read_only=True) member_count = serializers.SerializerMethodField() user_role = serializers.SerializerMethodField() created_by_email = serializers.CharField(source="created_by.email", read_only=True) created_by_username = serializers.CharField(source="created_by.username", read_only=True) class Meta: model = Group fields = [ "id", "name", "description", "created_by", "created_by_email", "created_by_username", "members", "member_count", "user_role", "created_at", "updated_at", ] def get_member_count(self, obj: Group) -> int: return getattr(obj, "_member_count", obj.memberships.count()) def get_user_role(self, obj: Group) -> str | None: request = self.context.get("request") if not request or not request.user.is_authenticated: return None membership = getattr(obj, "_user_membership", None) if membership is None: try: membership = obj.memberships.get(user=request.user) except GroupMember.DoesNotExist: return None return membership.role class GroupCreateSerializer(serializers.ModelSerializer): class Meta: model = Group fields = ["name", "description"] def validate_name(self, value: str) -> str: if not value.strip(): raise serializers.ValidationError("Group name cannot be empty.") if len(value.strip()) < 2: raise serializers.ValidationError("Group name must be at least 2 characters.") return value.strip() def create(self, validated_data: dict) -> Group: user = self.context["request"].user group = Group.objects.create(created_by=user, **validated_data) GroupMember.objects.create(group=group, user=user, role=GroupRole.ADMIN) return group class GroupUpdateSerializer(serializers.ModelSerializer): class Meta: model = Group fields = ["name", "description"] def validate_name(self, value: str) -> str: if not value.strip(): raise serializers.ValidationError("Group name cannot be empty.") if len(value.strip()) < 2: raise serializers.ValidationError("Group name must be at least 2 characters.") return value.strip() class GroupInviteSerializer(serializers.ModelSerializer): created_by_email = serializers.CharField(source="created_by.email", read_only=True) group_name = serializers.CharField(source="group.name", read_only=True) join_url = serializers.SerializerMethodField() class Meta: model = GroupInvite fields = [ "id", "group", "group_name", "code", "created_by", "created_by_email", "max_uses", "use_count", "is_active", "join_url", "created_at", ] read_only_fields = ["id", "group", "code", "created_by", "use_count", "created_at"] def get_join_url(self, obj: GroupInvite) -> str: request = self.context.get("request") if request: return f"{request.build_absolute_uri('/')[:-1]}/groups/join/{obj.code}" return f"/groups/join/{obj.code}" class GroupInviteCreateSerializer(serializers.ModelSerializer): class Meta: model = GroupInvite fields = ["max_uses"] def validate_max_uses(self, value: int) -> int: if value < 0: raise serializers.ValidationError("Max uses cannot be negative.") return value def create(self, validated_data: dict) -> GroupInvite: group = self.context["group"] user = self.context["request"].user return GroupInvite.objects.create( group=group, created_by=user, **validated_data, ) class JoinRequestSerializer(serializers.ModelSerializer): user_id = serializers.IntegerField(source="user.id", read_only=True) user_email = serializers.CharField(source="user.email", read_only=True) user_username = serializers.CharField(source="user.username", read_only=True) group_name = serializers.CharField(source="group.name", read_only=True) invite_code = serializers.UUIDField(source="invite.code", read_only=True, default=None) class Meta: model = JoinRequest fields = [ "id", "group", "group_name", "user", "user_id", "user_email", "user_username", "invite", "invite_code", "status", "created_at", ] read_only_fields = ["id", "group", "user", "invite", "created_at"] class JoinViaInviteSerializer(serializers.Serializer): """Validates and processes joining a group via an invite code.""" code = serializers.UUIDField() def validate_code(self, value: str) -> str: try: invite = GroupInvite.objects.select_related("group").get(code=value) except GroupInvite.DoesNotExist: raise serializers.ValidationError("Invalid invite code.") if not invite.is_active: raise serializers.ValidationError("This invite is no longer active.") if invite.max_uses > 0 and invite.use_count >= invite.max_uses: raise serializers.ValidationError("This invite has reached its maximum uses.") return value class RoleUpdateSerializer(serializers.Serializer): role = serializers.ChoiceField(choices=GroupRole.choices) def validate_role(self, value: str) -> str: if value == GroupRole.MEMBER: group = self.context["group"] admin_count = group.memberships.filter(role=GroupRole.ADMIN).count() if admin_count <= 1: raise serializers.ValidationError( "Cannot remove the last admin. Transfer admin role first or dissolve the group." ) return value