from decimal import Decimal from rest_framework import serializers from core.serializers.base import BaseModelSerializer from apps.projects.models import Project from apps.workspaces.models import PriceUnit def validate_thumbnail(value): if value is None: return value max_bytes = 2 * 1024 * 1024 if getattr(value, "size", 0) > max_bytes: raise serializers.ValidationError("Image size must be 2MB or less.") content_type = (getattr(value, "content_type", "") or "").lower() allowed_types = {"image/jpeg", "image/png", "image/webp"} if content_type and content_type not in allowed_types: raise serializers.ValidationError("Unsupported image type. Use JPG, PNG, or WebP.") return value class ProjectSerializer(BaseModelSerializer): class Meta: model = Project fields = BaseModelSerializer.Meta.fields + ( "workspace", "name", "client", "description", "thumbnail", "is_archived", "color", ) read_only_fields = fields def to_representation(self, instance): representation = super().to_representation(instance) request = self.context.get("request") if instance.thumbnail: thumbnail_url = instance.thumbnail.url representation["thumbnail"] = request.build_absolute_uri(thumbnail_url) if request else thumbnail_url else: representation["thumbnail"] = None if instance.client: representation['client'] = { 'id': instance.client.id, 'name': instance.client.name, 'thumbnail': ( request.build_absolute_uri(instance.client.thumbnail.url) if request and instance.client.thumbnail else instance.client.thumbnail.url if instance.client.thumbnail else None ), } return representation class ProjectCreateSerializer(serializers.Serializer): workspace = serializers.UUIDField() name = serializers.CharField(max_length=255) client = serializers.UUIDField(required=False, allow_null=True) description = serializers.CharField(required=False, allow_blank=True, default="") thumbnail = serializers.ImageField(required=False, allow_null=True) color = serializers.CharField(max_length=7, required=False, allow_blank=True, default="") def validate_thumbnail(self, value): return validate_thumbnail(value) class ProjectUpdateSerializer(serializers.Serializer): name = serializers.CharField(max_length=255, required=False) client = serializers.UUIDField(required=False, allow_null=True) description = serializers.CharField(required=False, allow_blank=True) thumbnail = serializers.ImageField(required=False, allow_null=True) clear_thumbnail = serializers.BooleanField(required=False, default=False) color = serializers.CharField(max_length=7, required=False, allow_blank=True) is_archived = serializers.BooleanField(required=False) def validate_thumbnail(self, value): return validate_thumbnail(value) class ProjectAccessQuerySerializer(serializers.Serializer): workspace = serializers.UUIDField() user = serializers.UUIDField() class ProjectAccessMutationSerializer(serializers.Serializer): workspace = serializers.UUIDField() user = serializers.UUIDField() project_ids = serializers.ListField( child=serializers.UUIDField(), allow_empty=False, ) class ProjectAccessRateMutationSerializer(serializers.Serializer): workspace = serializers.UUIDField() user = serializers.UUIDField() project = serializers.UUIDField() hourly_rate = serializers.DecimalField( max_digits=10, decimal_places=2, min_value=Decimal("0.01"), required=False, allow_null=True, ) currency = serializers.CharField(max_length=3, required=False, default="USD") def validate_currency(self, value): code = value.upper() if not PriceUnit.objects.filter(code=code, is_deleted=False).exists(): raise serializers.ValidationError("Selected price unit is invalid.") return code