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.
51 lines
2 KiB
Python
51 lines
2 KiB
Python
import uuid
|
|
from datetime import timedelta
|
|
|
|
from django.conf import settings
|
|
from drf_spectacular.utils import extend_schema
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from apps.core.serializers import MediaPresignInputSerializer, MediaPresignOutputSerializer
|
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
|
from utils.clients.minio_client import minio_client
|
|
from utils.exceptions import ServiceUnavailable
|
|
|
|
|
|
class MediaPresignView(APIView):
|
|
"""Presigned MinIO upload URL for product/store images (icon, logo, cover, ...).
|
|
|
|
The client uploads the file bytes directly to MinIO with the returned
|
|
upload_url, then submits the returned object_key as the field value when
|
|
creating/updating the owning resource (e.g. Store.logo, Product.image).
|
|
"""
|
|
|
|
schema_tags = ['Media']
|
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
required_alternate_scopes = {
|
|
"POST": [[]],
|
|
}
|
|
|
|
@extend_schema(request=MediaPresignInputSerializer, responses={201: MediaPresignOutputSerializer})
|
|
def post(self, request):
|
|
serializer = MediaPresignInputSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
filename = serializer.validated_data['filename']
|
|
extension = filename.rsplit('.', 1)[-1] if '.' in filename else 'bin'
|
|
object_key = f'uploads/{uuid.uuid4()}.{extension}'
|
|
|
|
try:
|
|
upload_url = minio_client.presigned_put_object(
|
|
settings.MINIO_MEDIA_FILES_BUCKET,
|
|
object_key,
|
|
expires=timedelta(minutes=30),
|
|
)
|
|
except Exception:
|
|
raise ServiceUnavailable('خطا در دریافت لینک آپلود. لطفاً دوباره تلاش کنید.')
|
|
|
|
return Response(
|
|
MediaPresignOutputSerializer({'upload_url': upload_url, 'object_key': object_key}).data,
|
|
status=status.HTTP_201_CREATED,
|
|
)
|