될때까지

((WnB)) 호스트 집 이미지 등록 AWS S3, boto3 사용하여 구현하기 본문

프로젝트/wecode2차 : WnB

((WnB)) 호스트 집 이미지 등록 AWS S3, boto3 사용하여 구현하기

랖니 2022. 10. 23. 21:38
728x90

호스트가 집을 등록하려면 집 이미지도 첨부하게 된다. 첨부한 이미지 파일들은 어디에 저장해야할까?

WnB 프로젝트에서는 AWS S3를 사용해서 저장했다. S3가 무엇이고 왜 사용하는지 간단하게 알아보자.

 

S3란?

Simple Storage Service의 약자로 인터넷용 스토리지 서비스를 말한다. 
이미지 파일들을 저장할 수 있는 공간으로 버킷(bucket)이라는 최상위 디렉토리에 저장된다.
S3의 버킷은 무한대의 객체(저장되는 데이터 단위를 일컫음)를 저장할 수 있기 때문에 확장 및 축소에 신경을 쓰지 않아도 되서 관리에 용이하다는 장점이 있다.

난 기존에 만들었던 버킷을 사용할것이기 때문에, 버킷 생성 관련 자료는 >>해당 유튜브 참고 <<

 

boto3

boto3는 AWS에서 제공하는 파이썬 용 SDK(software development kit)다. boto3를 이용하면 S3를 사용할 수 있다.

boto3 설치하기

poetry add boto3

boto3를 사용할 때 client, resource, session 3가지 방식이 있는데 이 중 client와 resource가 가장 많이 사용된다고 한다.
resource는 객체지향적 인터페이스로 자원에 대해 조금 더 직관적이다. 따라서 resource로 작성된 코드가 더 단순하고 읽기 쉽다.
하지만 모든 리소스에 대해 사용할 수 없으므로 그럴때는 client를 사용해야한다. 자세한 내용은 잘 정리해둔 블로그 참고 >>클릭<<

먼저 my_settings.py에 아래 정보들을 적어준다.
(IAM 생성 시 다운로드 받았던 .csv파일에 있음)

# my_settings.py

AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID'   # AWS_ACCESS_KEY_ID

AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY'  # AWS_SECRET_ACCESS_KEY
 
AWS_BUCKET_NAME = 'AWS_BUCKET_NAME'  # 생성한 버킷 이름

AWS_REGION = 'ap-northeast-2'

 

그다음 이미지 업로드 코드를 작성할 views.py에서 설치한 boto3를 불러온다.
그리고 아래와 같이 코드를 작성하면 된다.

import boto3
# hosts > views.py
import boto3

class RegisterRoomView(View):
    @signin_decorator
    def post(self, request):
        data         = request.POST

		...생략...

        s3 = boto3.resource('s3', 
                             AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, 
                             AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY)

        s3.Bucket(AWS_BUCKET_NAME).put_object(Key = '파일이름', body=파일, ContentType = '파일타입')

Bucket()을 이용하여 AWS S3에 만든 버킷(괄호안에 버킷 이름)에 접근할 수 있고,
put_object를 사용함으로써 파일을 업로드할 수 있다.

그럼 여기서 body에 담을 실제 파일을 가져오자. 
파일을 전달받으려면 request에 있는 FILES메소드를 사용하고 1개가 아닌 여러개의 파일이 올라오니까 getlist를 사용하면 된다.

# hosts > views.py
import boto3

class RegisterRoomView(View):
    @signin_decorator
    def post(self, request):
        data         = request.POST
        
        try:
            files = request.FILES.getlist('files')   # <- 이 코드 추가

            ...생략...

            s3 = boto3.resource('s3', 
                                 AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID, 
                                 AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY)

            s3.Bucket(AWS_BUCKET_NAME).put_object(Key = '파일이름', body=파일, ContentType = '파일타입')

여기서 버킷에 폴더를 만들고 파일들을 정리하면 더 깔끔할 것 같다. 저장될 파일의 이름을 지정해주자

# hosts > views.py

    ... 생략...
    # 집 이미지 등록하기
    for file in files:  # 여러개의 파일들이 있기 때문에 반복문을 돌면서 1개씩 s3에 저장해야함
        s3 = boto3.resource('s3', 
                            aws_access_key_id     = AWS_ACCESS_KEY_ID,
                            aws_secret_access_key = AWS_SECRET_ACCESS_KEY)

        # S3에 object저장하기
        s3.Bucket(AWS_BUCKET_NAME).put_object(Key         = str(host.id) + f'/{file}',    # 호스트 id별로 폴더를 생성해서 관리
                                              Body        = file,
                                              ContentType = 'jpg')

        # DB에 저장하면 끝
        Image.objects.create(                        # Image에는 url과 room이 있음
            url     = IMAGE_URL + f'/{host.id}/{file}', # 저장할 url 경로 형식 지정
            room_id = room.id
        )

이대로 코드를 작성하고 포스트맨으로 테스트를 했다.
포스트맨으로 file을 올리는 방법은 text를 file로 바꿔주면 된다.

올리고 AWS S3 Bucket을 확인해보자.
호스트의 id인 5번 폴더가 생성됐고 그 안에 사진파일이 잘 저장된 걸 확인할 수 있다.
다운로드 받았던 파일의 이름 그대로 올려줬는데 파일이름이 중복될 수도 있으니
해당 부분을 uuid를 사용하여 중복없이 올라가도록 수정해주자.

    # 집 이미지 등록하기
    for file in files:  
        file._set_name(str(uuid.uuid4()))    # <- 해당 코드를 추가하면 된다.
        s3 = boto3.resource('s3', 
                            aws_access_key_id     = AWS_ACCESS_KEY_ID,
                            aws_secret_access_key = AWS_SECRET_ACCESS_KEY)

        # S3에 object저장하기
        s3.Bucket(AWS_BUCKET_NAME).put_object(Key         = str(host.id) + f'/{file}',
                                              Body        = file,
                                              ContentType = 'jpg')

        # S3에 저장된 경로는 "https://AWS_BUCKET_NAME.s3.AWS_REGION.amazonaws.com/+파일이름"이다.
        # 해당 경로를 사용해 DB에 저장하기
        Image.objects.create(                    
            url     = IMAGE_URL + f'/{host.id}/{file}', 
            room_id = room.id
        )

그래서 완성된 코드는 아래와 같다!.

class RegisterRoomView(View):
    @signin_decorator
    def post(self, request):
        data         = request.POST

        try:
            host = Host.objects.get(user=request.user)   
            
            name              = data['room_name']
            address           = data['state/province/region']
            detail_address    = data['detail_address']
            price             = data['price']
            description       = data['description']
            latitude          = data['latitude']
            longitude         = data['longitude']
            maximum_occupancy = data['maximum_occupancy']
            bedroom           = data['bedroom']
            bathroom          = data['bathroom']
            bed               = data['bed']
            category          = data['category_id']
            room_type         = data['room_type_id']
            facility_ids      = data.getlist('facility_ids')  # 리스트로 담김 ['1', '2', '4']
            files             = request.FILES.getlist('files')  # 이미지 첨부

			...생략...
  
            with transaction.atomic():
                room, is_created = Room.objects.get_or_create(
                    name = name,
                    defaults = {
                        "address"           : address,
                        "detail_address"    : detail_address,
                        "price"             : price,
                        "description"       : description,
                        "latitude"          : latitude,
                        "longitude"         : longitude,
                        "maximum_occupancy" : maximum_occupancy,
                        "bedroom"           : bedroom,
                        "bathroom"          : bathroom,
                        "bed"               : bed,
                        "host"              : host,
                        "category"          : Category.objects.get(id=category),
                        "room_type"         : RoomType.objects.get(id=room_type)
                    }
                )
                
                ...생략...
                
                # 집 이미지 등록하기
                for file in files:  # 여러개의 파일들을 반복을 돌면서 1개씩 올려야함
                    file._set_name(str(uuid.uuid4()))
                    s3 = boto3.resource('s3', 
                                        aws_access_key_id     = AWS_ACCESS_KEY_ID,
                                        aws_secret_access_key = AWS_SECRET_ACCESS_KEY)
                    
                    # S3에 object저장하기
                    s3.Bucket(AWS_BUCKET_NAME).put_object(Key         = str(host.id) + f'/{file}',    # 호스트 id별로 폴더 생성해서 관리
                                                          Body        = file,
                                                          ContentType = 'jpg')
                                        
                    # DB에 저장하면 끝
                    Image.objects.create(                        # Image에는 url과 room이 있음
                        url     = IMAGE_URL + f'/{host.id}/{file}', # 저장할 url 경로 형식 지정
                        room_id = room.id
                    )
                    
                # 저장된 이미지 파일들 가져와서 내보내주기
                images = Image.objects.filter(room_id = room.id).all()
                       
                room_info = {
                    "id"        : room.id,
                    "room_name" : room.name,
                    "address"   : room.address + room.detail_address,
                    "price"     : room.price,
                    "bedroom"   : room.bedroom,
                    "bathroom"  : room.bathroom,
                    "bed"       : room.bed,
                    "host_name" : room.host.user.last_name + room.host.user.first_name,
                    "category"  : room.category.name,
                    "room_type" : room.room_type.name,
                    "facilities": [room_facility.room_facility.name for room_facility in RoomFacility.objects.filter(room = room.id).all()],
                    "images"    : [image.url for image in images]
                }
            
                return JsonResponse({'message':'SUCCESS', 'data':{'room_info':room_info}}, status=201)         

        except Host.DoesNotExist:
            return JsonResponse({'message':'ONLY_HOST_CAN_REGISTER_HOUSE'}, status=403)
        
        except KeyError:
            return JsonResponse({'message':'KEY_ERROR'}, status=400)

s3버킷에 저장된 파일을 눌러도 이미지가 잘 보이고, DB에 저장된 경로를 url에 입력해도 사진이 제대로 보인다면 성공이다!

728x90