diff --git a/UpcyProject/settings.py b/UpcyProject/settings.py index 9365978d..c0428f1b 100644 --- a/UpcyProject/settings.py +++ b/UpcyProject/settings.py @@ -14,6 +14,7 @@ from pathlib import Path import environ import os +from dotenv import load_dotenv # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -169,3 +170,11 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +#s3 setting +load_dotenv() + +AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID') +AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY') +AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'cognisle-bucket') +AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'ap-northeast-2') diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 00000000..6bdbe94c --- /dev/null +++ b/core/utils.py @@ -0,0 +1,51 @@ +""" +포트폴리오, 룩북, 서비스 모두 사용할 수 있도록 공통 함수로 따로 제작. +services>services.py>ServicePhotoApi 참고해서 작성하면 됨 +""" + +import io +import boto3 +import uuid +from django.conf import settings +from UpcyProject import settings +from datetime import datetime + + +def get_random_text(length): + return str(uuid.uuid4()).replace('-', '')[:length] + +def s3_file_upload_by_file_data(upload_file, region_name, bucket_name, bucket_path, content_type=None): + bucket_name = bucket_name.replace('/', '') + # 파일 확장자 추출 + if content_type: + content_type = content_type + else: + content_type = upload_file.content_type + + now = datetime.now() + random_str = get_random_text(20) + extension = upload_file.name.split('.')[-1] + + random_file_name = '{}.{}'.format(str(now.strftime('%Y%m%d%H%M%S'))+random_str,extension) + # 파일의 ContentType을 설정 + random_file_name = f"{now.strftime('%Y%m%d%H%M%S')}_{random_str}.{extension}" + upload_file_path_name = f"{bucket_path}/{random_file_name}" + + s3 = boto3.resource('s3', region_name=region_name, aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY) + + try: + # 버킷이 존재하는지 확인 + s3.meta.client.head_bucket(Bucket=bucket_name) + except s3.meta.client.exceptions.NoSuchBucket: + raise Exception(f"The specified bucket does not exist: {bucket_name}") + + file_data = io.BytesIO(upload_file.read()) + + try: + # 파일을 S3에 업로드 + s3.Bucket(bucket_name).put_object(Key=upload_file_path_name, Body=file_data, ContentType=content_type, ACL='public-read') + print(extension) + return f"https://{bucket_name}.s3.{region_name}.amazonaws.com/{upload_file_path_name}" + except Exception as e: + print(f"Failed to upload file to S3: {e}") + return False diff --git a/products/migrations/0007_category_fit_style_texture_product_category_and_more.py b/products/migrations/0007_category_fit_style_texture_product_category_and_more.py index d74490e4..daff0256 100644 --- a/products/migrations/0007_category_fit_style_texture_product_category_and_more.py +++ b/products/migrations/0007_category_fit_style_texture_product_category_and_more.py @@ -48,7 +48,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='product', name='fit', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='products', to='products.fit'), + field=models.ForeignKey(blank=False, null=False, on_delete=django.db.models.deletion.CASCADE, related_name='products', to='products.fit'), ), migrations.AddField( model_name='product', diff --git a/products/services.py b/products/services.py index 5a47556b..2c885e5b 100644 --- a/products/services.py +++ b/products/services.py @@ -26,7 +26,6 @@ def create(self, name : str,keywords : list[str],basic_price : str,option : str, category : str, style : list[str], texture : list[str], fit : list[str], detail:list[str], ) -> Product: product_service=ProductService() - product= product_service.create( reformer=self.user, @@ -46,8 +45,8 @@ def create(self, name : str,keywords : list[str],basic_price : str,option : str, transaction_package=transaction_package, refund=refund, ) - if product is not None: + print('here') ProductPhotoService.process_photos(product=product, product_photos=product_photos ) ProductKeywordService.process_keywords(product=product, keywords=keywords) return product diff --git a/products/views.py b/products/views.py index dda475a0..b83cb774 100644 --- a/products/views.py +++ b/products/views.py @@ -103,7 +103,6 @@ def post(self,request): transaction_package=data.get('transaction_package'), refund=data.get('refund'), ) - if product is not None: return Response({ 'status' : 'success', diff --git a/services/models.py b/services/models.py index 44ab2c87..c0ddf3c5 100644 --- a/services/models.py +++ b/services/models.py @@ -1,5 +1,7 @@ from django.db import models from core.models import TimeStampedModel +from django.conf import settings # s3를 이용한 이미지 업로드용 +from datetime import datetime # s3를 이용한 이미지 업로드용 # Create your models here. class Category(models.Model): @@ -54,11 +56,18 @@ class ServiceKeyword(models.Model): #Service_Photo 모델 만들기 +# get_service_photo_upload_path 함수는 수정 가능성 있음 def get_service_photo_upload_path(instance, filename): return 'services/photo/{}'.format(filename) +# get_service_photo_upload_path 함수가 꼭 필요할까? + +#class ServicePhoto(models.Model): +# image = models.ImageField( +# upload_to=get_service_photo_upload_path, default='service_photo.png') +# service = models.ForeignKey( +# 'Service', related_name='service_photos', on_delete=models.CASCADE, null=True, blank=True) class ServicePhoto(models.Model): - image = models.ImageField( - upload_to=get_service_photo_upload_path, default='service_photo.png') + image = models.URLField(default='service_photo.png') service = models.ForeignKey( - 'Service', related_name='service_photos', on_delete=models.CASCADE, null=True, blank=True) \ No newline at end of file + 'Service', related_name='service_photos', on_delete=models.CASCADE, null=True, blank=True) diff --git a/services/selectors.py b/services/selectors.py index ab623118..e4941a37 100644 --- a/services/selectors.py +++ b/services/selectors.py @@ -34,7 +34,7 @@ class ServiceDto: notice : str = None keywords: list[str] = None - photos: list[str] = None + photos: str = None #원래는 list[str]인데, 일단 service 하나당 photo 하나만 추가할 수 있는 것으로 nickname : str = None market:str = None @@ -192,5 +192,7 @@ def list(search:str, order:str, user:User, ) for service in services] return services_dtos - def likes(self, service:Service, user:User): + def likes(self, service, user:User): + from services.models import Service + from users.models import User return service.likeuser_set.filter(pk=user.pk).exists() \ No newline at end of file diff --git a/services/services.py b/services/services.py index 5e94a08b..2353d86b 100644 --- a/services/services.py +++ b/services/services.py @@ -1,7 +1,6 @@ -import io -import time import uuid - +import boto3 +import io from django.shortcuts import get_list_or_404, get_object_or_404 from django.db import transaction from django.core.files.images import ImageFile @@ -11,6 +10,8 @@ from services.models import Service, ServiceKeyword, ServicePhoto, Category, Style, Fit, Texture, Detail from .selectors import ServiceSelector from users.models import User +from core.utils import s3_file_upload_by_file_data +from UpcyProject import settings class ServiceCoordinatorService: @@ -110,22 +111,81 @@ def create(name : str,basic_price : str, max_price: str, option : str,info : str service.detail.set(detail) return service +class ServicePhotoService: + def __init__(self, file): + self.file = file + + def upload(self): + s3_client = boto3.client( + 's3', + aws_access_key_id = settings.AWS_ACCESS_KEY_ID, + aws_secret_access_key = settings.AWS_SECRET_ACCESS_KEY + ) + extension = self.file.name.split('.')[-1] + url = f'services/photo/{uuid.uuid1().hex}.{extension}' + + file_data = io.BytesIO(self.file.read()) + file_data.seek(0) + + try: + s3_client.upload_fileobj( + file_data, # 파일 데이터 객체 + "cognisle-bucket", + url, + ExtraArgs={ + "ContentType": self.file.content_type + } + ) + return f"https://cognisle-bucket.s3.{settings.AWS_S3_REGION_NAME}.amazonaws.com/{url}" + + except Exception as e: + print(f"Failed to upload file to S3: {e}") + return None + + @staticmethod + def process_photos(service:Service, service_photos: list[str]): + for service_photo in service_photos: + op, photo_url = service_photo.split(',') + + photo_path = photo_url.replace(settings.MEDIA_ROOT,'').lstrip('/') + + try: + service_photo, created = ServicePhoto.objects.get_or_create(image=photo_path) + except Exception as e: + print(f"Error in process_photos: {e}") + service_photo = None + + if op == 'add' and service_photo : + service_photo.service = service + service_photo.full_clean() + service_photo.save() + elif op == 'remove' and service_photo : + service_photo.delete() + +""" class ServicePhotoService: def __init__(self): pass @staticmethod def create(image:InMemoryUploadedFile): - ext = image.name.split(".")[-1] - file_path = '{}.{}'.format(str(time.time())+str(uuid.uuid4().hex),ext) - image = ImageFile(io.BytesIO(image.read()),name=file_path) - service_photo = ServicePhoto(image=image, service=None) + s3_url = s3_file_upload_by_file_data( + upload_file=image, + region_name=settings.AWS_S3_REGION_NAME, + bucket_name=settings.AWS_STORAGE_BUCKET_NAME, + bucket_path='services/photo' # 고유한 경로 지정 + ) + + if not s3_url: + raise Exception("Failed to upload image to S3") + service_photo = ServicePhoto(image=s3_url, service=None) service_photo.full_clean() service_photo.save() - return settings.MEDIA_URL + service_photo.image.name + return service_photo.image #위아래 return 중 뭐가 맞는지 모르겠음 + return settings.MEDIA_URL + service_photo.image.name #위아래 return 중 뭐가 맞는지 모르겠음 @staticmethod def process_photos(service:Service, service_photos: list[str]): for service_photo in service_photos: @@ -139,12 +199,13 @@ def process_photos(service:Service, service_photos: list[str]): print(f"Error in process_photos: {e}") service_photo = None - if op == 'add': + if op == 'add' and service_photo : service_photo.service = service service_photo.full_clean() service_photo.save() - elif op == 'remove': + elif op == 'remove' and service_photo : service_photo.delete() +""" class ServiceKeywordService: def __init__(self): diff --git a/services/views.py b/services/views.py index 16f0a78e..b4460f8a 100644 --- a/services/views.py +++ b/services/views.py @@ -146,14 +146,21 @@ def post(self, request): serializers = self.ServicePhotoCreateInputSerializer(data=request.data) serializers.is_valid(raise_exception=True) data = serializers.validated_data + + image_file = data.get('image') + + + service_photo_service = ServicePhotoService(file=image_file) + + service_photo_url = service_photo_service.upload() - service_photo_url = ServicePhotoService.create( - image=data.get('image') - ) + #service_photo_url = ServicePhotoService.upload( + # image=data.get('image') + #) return Response({ 'status':'success', - 'data':{'location': service_photo_url}, + 'data':{'location': service_photo_url}, }, status = status.HTTP_201_CREATED) class ServiceDetailApi(APIView):