You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

289 lines
9.9 KiB

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 validate(self, attrs):
request = self.context.get('request')
if request and request.user and request.user.is_authenticated:
user = request.user
if getattr(user, 'is_super_admin', False) or getattr(user, 'is_superuser', False):
return attrs
if not self.instance:
# Project creation: user must be permitted to create on behalf of owner institution
owner = attrs.get('owner_institution')
if owner:
if getattr(user, 'is_regional_admin', False):
if hasattr(user, 'can_manage_region') and not user.can_manage_region(owner.region):
raise serializers.ValidationError({
"owner_institution": "You do not have administrative authority over institutions in this region."
})
elif not owner.is_editor(user):
raise serializers.ValidationError({
"owner_institution": "You are not an authorized editor or administrator of this institution."
})
else:
# Project update: only validate owner if it is explicitly being modified
if 'owner_institution' in attrs:
new_owner = attrs['owner_institution']
if getattr(user, 'is_regional_admin', False):
if hasattr(user, 'can_manage_region') and not user.can_manage_region(new_owner.region):
raise serializers.ValidationError({
"owner_institution": "You do not have administrative authority over institutions in this region."
})
elif not new_owner.is_editor(user):
raise serializers.ValidationError({
"owner_institution": "You are not an authorized editor or administrator of this institution."
})
return attrs
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