from django.contrib.auth import get_user_model from rest_framework import serializers from drf_spectacular.utils import extend_schema_field from apps.projects.models.project import ( Project, KanbanColumn, KanbanTask, ProjectDocument, ) from apps.profiles.models.institution import Institution User = get_user_model() class ProjectUserMiniSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'email', 'fullname', 'avatar'] read_only_fields = fields class ProjectInstitutionMiniSerializer(serializers.ModelSerializer): type_display = serializers.CharField(source='get_type_display', read_only=True) class Meta: model = Institution fields = ['id', 'name', 'slug', 'type', 'type_display', 'avatar', 'city', 'country'] read_only_fields = fields class KanbanTaskSerializer(serializers.ModelSerializer): priority_display = serializers.CharField(source='get_priority_display', read_only=True) column_name = serializers.CharField(source='column.name', read_only=True) column_code = serializers.CharField(source='column.code', read_only=True) assignee = ProjectUserMiniSerializer(read_only=True) class Meta: model = KanbanTask fields = [ 'id', 'project', 'column', 'column_name', 'column_code', 'title', 'description', 'priority', 'priority_display', 'assignee', 'due_date', 'labels', 'order', 'created_at', 'updated_at', ] read_only_fields = ['id', 'project', 'priority_display', 'column_name', 'column_code', 'created_at', 'updated_at'] class KanbanTaskCreateUpdateSerializer(serializers.ModelSerializer): class Meta: model = KanbanTask fields = [ 'column', 'title', 'description', 'priority', 'assignee', 'due_date', 'labels', 'order', ] def validate(self, attrs): project = self.context.get('project') column = attrs.get('column') if project and column and column.project != project: raise serializers.ValidationError({"column": "Selected column does not belong to this project."}) return attrs class KanbanColumnSerializer(serializers.ModelSerializer): tasks = serializers.SerializerMethodField() class Meta: model = KanbanColumn fields = [ 'id', 'project', 'name', 'code', 'order', 'color', 'tasks', ] read_only_fields = ['id', 'project'] @extend_schema_field(KanbanTaskSerializer(many=True)) def get_tasks(self, obj): tasks = obj.tasks.all().select_related('assignee').order_by('order', 'id') return KanbanTaskSerializer(tasks, many=True).data class ProjectDocumentSerializer(serializers.ModelSerializer): doc_type_display = serializers.CharField(source='get_doc_type_display', read_only=True) uploaded_by_email = serializers.EmailField(source='uploaded_by.email', read_only=True, allow_null=True) class Meta: model = ProjectDocument fields = [ 'id', 'project', 'title', 'file', 'doc_type', 'doc_type_display', 'uploaded_by_email', 'uploaded_at', ] read_only_fields = ['id', 'project', 'doc_type_display', 'uploaded_by_email', 'uploaded_at'] def create(self, validated_data): request = self.context.get('request') if request and request.user.is_authenticated: validated_data['uploaded_by'] = request.user return super().create(validated_data) class ProjectListSerializer(serializers.ModelSerializer): status_display = serializers.CharField(source='get_status_display', read_only=True) owner_institution = ProjectInstitutionMiniSerializer(read_only=True) collaborating_institutions_count = serializers.SerializerMethodField() tasks_count = serializers.SerializerMethodField() completed_tasks_count = serializers.SerializerMethodField() class Meta: model = Project fields = [ 'id', 'title', 'slug', 'owner_institution', 'collaborating_institutions_count', 'category', 'status', 'status_display', 'progress_percentage', 'budget', 'team_size', 'cover_image', 'start_date', 'estimated_end_date', 'tasks_count', 'completed_tasks_count', 'created_at', ] read_only_fields = fields @extend_schema_field(serializers.IntegerField) def get_collaborating_institutions_count(self, obj) -> int: return obj.collaborating_institutions.count() @extend_schema_field(serializers.IntegerField) def get_tasks_count(self, obj) -> int: return obj.tasks.count() @extend_schema_field(serializers.IntegerField) def get_completed_tasks_count(self, obj) -> int: return obj.tasks.filter(column__code='done').count() class ProjectDetailSerializer(serializers.ModelSerializer): status_display = serializers.CharField(source='get_status_display', read_only=True) owner_institution = ProjectInstitutionMiniSerializer(read_only=True) collaborating_institutions = ProjectInstitutionMiniSerializer(many=True, read_only=True) columns = KanbanColumnSerializer(many=True, read_only=True) documents = ProjectDocumentSerializer(many=True, read_only=True) can_edit = serializers.SerializerMethodField() created_by_email = serializers.EmailField(source='created_by.email', read_only=True, allow_null=True) class Meta: model = Project fields = [ 'id', 'title', 'slug', 'description', 'owner_institution', 'collaborating_institutions', 'category', 'status', 'status_display', 'progress_percentage', 'budget', 'team_size', 'cover_image', 'start_date', 'estimated_end_date', 'actual_end_date', 'columns', 'documents', 'can_edit', 'created_by_email', 'created_at', 'updated_at', ] read_only_fields = [ 'id', 'slug', 'columns', 'documents', 'can_edit', 'created_by_email', 'created_at', 'updated_at', ] @extend_schema_field(serializers.BooleanField) def get_can_edit(self, obj) -> bool: request = self.context.get('request') if not request or not request.user.is_authenticated: return False return obj.can_user_edit(request.user) class ProjectCreateUpdateSerializer(serializers.ModelSerializer): class Meta: model = Project fields = [ 'title', 'description', 'owner_institution', 'collaborating_institutions', 'category', 'status', 'progress_percentage', 'budget', 'team_size', 'cover_image', 'start_date', 'estimated_end_date', 'actual_end_date', ] def create(self, validated_data): request = self.context.get('request') collaborating = validated_data.pop('collaborating_institutions', []) if request and request.user.is_authenticated: validated_data['created_by'] = request.user project = super().create(validated_data) if collaborating: project.collaborating_institutions.set(collaborating) return project