The prior commit wired MinIO as Django's default storage backend but kept plain ImageField multipart uploads through the Django backend and public-bucket direct URLs — not how campaign/advertising/promotions do it. Rework to match: image fields (ProductCategory.icon, Product.image, StoreCategory.icon, Store.logo, Store.cover_image) now store an opaque MinIO object_key (CharField) instead of a Django-managed file. - utils/clients/minio_client.py — raw Minio SDK client, same shape as the other services. - apps/core/views/media.py — POST /api/media/presign/ mints a 30-minute presigned PUT URL + object_key; the client uploads bytes directly to MinIO, then submits the object_key on the owning resource. - apps/core/media.py — presigned_media_url() helper; every serializer with an image field now also exposes a `<field>_url` computed from a fresh 1-hour presigned GET, rather than trusting a stored/direct URL. - FRONTEND_GUIDE.md updated to document the new upload flow and field shapes (object_key vs `_url`), replacing the stale "no upload endpoint yet" note. Verified against a live local MinIO container: presign → direct PUT upload → object_key stored on the model → serializer mints a working presigned GET URL → URL fetches the uploaded bytes. Also exercised the actual DRF view (POST /api/media/presign/) end-to-end, including the 401 on an unauthenticated request.
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
from rest_framework import serializers
|
|
|
|
from apps.core.media import presigned_media_url
|
|
from apps.stores.serializers import StoreListSerializer
|
|
|
|
from .models import Product, ProductCategory
|
|
|
|
|
|
class ProductCategorySerializer(serializers.ModelSerializer):
|
|
icon_url = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = ProductCategory
|
|
fields = ('uuid', 'name', 'parent', 'icon', 'icon_url', 'order')
|
|
|
|
def get_icon_url(self, obj):
|
|
return presigned_media_url(obj.icon)
|
|
|
|
|
|
class ProductListSerializer(serializers.ModelSerializer):
|
|
is_out_of_stock = serializers.BooleanField(read_only=True)
|
|
image_url = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = (
|
|
'uuid', 'name', 'image', 'image_url', 'price', 'unit_type', 'unit_value',
|
|
'is_active', 'is_out_of_stock',
|
|
)
|
|
|
|
def get_image_url(self, obj):
|
|
return presigned_media_url(obj.image)
|
|
|
|
|
|
class ProductDetailSerializer(ProductListSerializer):
|
|
category = ProductCategorySerializer(read_only=True)
|
|
store = StoreListSerializer(read_only=True)
|
|
|
|
class Meta(ProductListSerializer.Meta):
|
|
fields = ProductListSerializer.Meta.fields + ('description', 'category', 'store')
|
|
|
|
|
|
class SellerProductSerializer(serializers.ModelSerializer):
|
|
"""Read/write serializer for the seller's own products (S06/S07/S08)."""
|
|
|
|
category = ProductCategorySerializer(read_only=True)
|
|
category_uuid = serializers.PrimaryKeyRelatedField(
|
|
source='category', queryset=ProductCategory.objects.all(), write_only=True,
|
|
)
|
|
is_out_of_stock = serializers.BooleanField(read_only=True)
|
|
is_low_stock = serializers.BooleanField(read_only=True)
|
|
image_url = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = (
|
|
'uuid', 'name', 'description', 'image', 'image_url', 'category', 'category_uuid',
|
|
'price', 'unit_type', 'unit_value', 'stock_quantity', 'low_stock_threshold',
|
|
'is_active', 'sold_count', 'is_out_of_stock', 'is_low_stock', 'created_at',
|
|
)
|
|
read_only_fields = ('sold_count',)
|
|
|
|
def get_image_url(self, obj):
|
|
return presigned_media_url(obj.image)
|
|
|
|
def create(self, validated_data):
|
|
validated_data['store'] = self.context['request'].user.store
|
|
return super().create(validated_data)
|
|
|
|
|
|
class StockAdjustSerializer(serializers.Serializer):
|
|
stock_quantity = serializers.IntegerField(min_value=0)
|