feat(workspaces): add thumbnail upload and lifecycle support
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from decimal import Decimal
|
||||
import json
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -16,7 +17,8 @@ class WorkspaceMemberInputSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class WorkspaceSerializer(BaseModelSerializer):
|
||||
members = WorkspaceMemberInputSerializer(many=True, write_only=True, required=False)
|
||||
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:
|
||||
@@ -24,6 +26,8 @@ class WorkspaceSerializer(BaseModelSerializer):
|
||||
fields = BaseModelSerializer.Meta.fields + (
|
||||
"name",
|
||||
"description",
|
||||
"thumbnail",
|
||||
"clear_thumbnail",
|
||||
"owner",
|
||||
"my_role",
|
||||
"members",
|
||||
@@ -39,8 +43,41 @@ class WorkspaceSerializer(BaseModelSerializer):
|
||||
).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)
|
||||
|
||||
@@ -75,6 +112,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):
|
||||
user = serializers.SerializerMethodField()
|
||||
|
||||
@@ -7,6 +7,7 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser
|
||||
|
||||
from apps.notifications.services import (
|
||||
notify_workspace_membership_added,
|
||||
@@ -45,6 +46,7 @@ from core.paginations.limit_offset import CustomLimitOffsetPagination
|
||||
|
||||
class WorkspaceViewSet(ModelViewSet):
|
||||
serializer_class = WorkspaceSerializer
|
||||
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
||||
pagination_class = CustomLimitOffsetPagination
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter)
|
||||
filterset_class = WorkspaceFilter
|
||||
|
||||
17
apps/workspaces/migrations/0006_workspace_thumbnail.py
Normal file
17
apps/workspaces/migrations/0006_workspace_thumbnail.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("workspaces", "0005_remove_priceunit_priceunit_id_idx_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="workspace",
|
||||
name="thumbnail",
|
||||
field=models.ImageField(blank=True, null=True, upload_to="profile/workspaces/"),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,6 +9,7 @@ User = get_user_model()
|
||||
class Workspace(BaseModel):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
thumbnail = models.ImageField(upload_to="profile/workspaces/", blank=True, null=True)
|
||||
owner = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.PROTECT,
|
||||
|
||||
Reference in New Issue
Block a user