feat(workspaces): add thumbnail upload and lifecycle support

This commit is contained in:
2026-04-28 11:38:35 +03:30
parent 76f02dc259
commit 315f2ca728
4 changed files with 91 additions and 17 deletions

View File

@@ -1,4 +1,5 @@
from decimal import Decimal
import json
from rest_framework import serializers
@@ -10,37 +11,73 @@ from apps.workspaces.models import PriceUnit, Workspace, WorkspaceMembership, Wo
from core.serializers.mini import UserMiniSerializer
class WorkspaceMemberInputSerializer(serializers.Serializer):
class WorkspaceMemberInputSerializer(serializers.Serializer):
user_id = serializers.UUIDField()
role = serializers.ChoiceField(choices=WorkspaceMembership.Role.choices, default=WorkspaceMembership.Role.MEMBER)
class WorkspaceSerializer(BaseModelSerializer):
members = WorkspaceMemberInputSerializer(many=True, write_only=True, required=False)
my_role = serializers.SerializerMethodField()
class WorkspaceSerializer(BaseModelSerializer):
members = serializers.JSONField(write_only=True, required=False)
clear_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
my_role = serializers.SerializerMethodField()
class Meta:
model = Workspace
fields = BaseModelSerializer.Meta.fields + (
"name",
"description",
"owner",
"my_role",
"members",
"name",
"description",
"thumbnail",
"clear_thumbnail",
"owner",
"my_role",
"members",
)
read_only_fields = BaseModelSerializer.Meta.fields + (
"owner",
)
def get_my_role(self, obj):
def get_my_role(self, obj):
membership = WorkspaceMembership.objects.filter(
workspace=obj,
user=self.context["request"].user,
).first()
return getattr(membership, "role", None)
).first()
return getattr(membership, "role", None)
def validate_thumbnail(self, 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
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
if instance.thumbnail:
thumbnail_url = instance.thumbnail.url
data["thumbnail"] = request.build_absolute_uri(thumbnail_url) if request else thumbnail_url
else:
data["thumbnail"] = None
data.pop("clear_thumbnail", None)
return data
def create(self, validated_data):
members_data = validated_data.pop('members', [])
members_data = validated_data.pop("members", [])
if isinstance(members_data, str):
try:
members_data = json.loads(members_data)
except json.JSONDecodeError as exc:
raise serializers.ValidationError({"members": "Invalid members format."}) from exc
if members_data:
members_serializer = WorkspaceMemberInputSerializer(data=members_data, many=True)
members_serializer.is_valid(raise_exception=True)
members_data = members_serializer.validated_data
validated_data.pop("clear_thumbnail", None)
workspace = super().create(validated_data)
@@ -74,6 +111,23 @@ class WorkspaceSerializer(BaseModelSerializer):
)
return workspace
def update(self, instance, validated_data):
clear_thumbnail = validated_data.pop("clear_thumbnail", False)
old_thumbnail_name = instance.thumbnail.name if instance.thumbnail else None
if clear_thumbnail and instance.thumbnail:
instance.thumbnail.delete(save=False)
instance.thumbnail = None
updated_workspace = super().update(instance, validated_data)
if old_thumbnail_name and updated_workspace.thumbnail and updated_workspace.thumbnail.name != old_thumbnail_name:
storage = updated_workspace.thumbnail.storage
if storage.exists(old_thumbnail_name):
storage.delete(old_thumbnail_name)
return updated_workspace
class WorkspaceMembershipSerializer(BaseModelSerializer):