오늘도 개발
[2차 프로젝트 내일의 집] AWS s3로 글쓰기 api 구현하기 - 2. Django 세팅(boto3)과 코드 작성 본문
[2차 프로젝트 내일의 집] AWS s3로 글쓰기 api 구현하기 - 2. Django 세팅(boto3)과 코드 작성
Sueeeeeee 2022. 8. 8. 18:541. Django 설정
1) boto3 설치
pip3 install boto3
2) django-storages 설치
pip3 install django-storages
3) settings.py 설정 추가
INSTALLED_APPS = [
...
'storages' # django-storages 추가
]
# 설정한 iam 사용자의 access key와 secret access key 사용
AWS_ACCESS_KEY_ID = MY_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY = MY_AWS_SECRET_ACCESS_KEY
2. 포스트맨 요청 준비
코드를 짜기에 앞서 포스트맨에서 어떻게 요청을 보내야 할 지 몰라서 헤맸다.
사진 파일과 json 데이터를 같이 넣어서 요청해본 적이 없기 때문이다.
검색해본 결과 form-data를 선택하고 아래와 같이 입력하면 된다고 한다.
(CONTENT TYPE에 application/json을 지정해야 json으로 간다.)

이렇게 요청한 후 다음과 같이 django에서 파일과 json 데이터를 꺼내 쓰려고 했다.
files = request.FILES.getlist('photo')
data = json.loads(request.body)
하지만 이렇게 쓰면 다음과 같은 오류가 난다.
파이썬이 request의 data stream에 있는 파일을 읽었는데 그 이후에 json으로 바디에 접근할 수는 없다는 것 같다.
RawPostDataException(
django.http.request.RawPostDataException:
You cannot access body after reading from request's data stream)
data = json.loads(request.body)를 먼저 쓰면 다음과 같은 오류가 난다.
UnicodeDecodeError:
'utf-8' codec can't decode byte 0x89 in position 149: invalid start byte
이 때 json.loads()를 사용하지 않고 다음과 같이 불러왔더니 오류가 나지 않았다.
files = request.FILES.getlist('photo')
data = request.POST.get('body')
하지만 data가 json이 아니라 스트링이라 값을 꺼내 쓰기 불편했다.
그래서 request.POST.get('body')를 json으로 변환해주었다.
data = json.loads(request.POST.get('body'))
files = request.FILES.getlist('photo')
3. API에 boto3 사용해서 사진 업로드하기
<my_settings.py>
# 버킷 url은 이 형태로 고정이다. 이 뒤에 views.py에서 설정할 key를 붙이면 사진의 고유한 url이 된다.
IMAGE_URL = f'https://{버킷명}.s3.ap-northeast-2.amazonaws.com/'
<views.py>
boto3는 s3를 사용할 수 있게 도와주는 모듈.
uuid는 고유한 스트링을 만들어주는 모듈.
파일명을 uuid로 만든 고유한 스트링으로 대체한 뒤 업로드해서
어떤 리소스도 덮어써서 저장되지 않게 해줄 것이다.
예를 들어 1번 유저가 a.jpeg라는 사진을 올린 후, 2번 유저가 a.jpeg라는 사진을 올리는 상황을 가정해보자.
두 사진의 파일명을 바꿔주는 로직이 없어서
두 사진 모두 a.jpeg라는 이름으로 s3에 올라가면 2번 유저의 사진이 1번 유저의 사진을 덮어쓸 것이다.
따라서 uuid를 사용해서 어떤 유저가 어떤 파일명으로 된 파일을 올리든 고유한 파일명을 갖게 만들어주어야 한다.
이를 바탕으로 파일을 업로드하는 코드를 만들어보았다.
import json
import boto3
import uuid
from django.http import JsonResponse
from django.views import View
from django.conf import settings
from posts.models import Post, Photo, Tag
class PostWriteView(View):
def post(self, request):
# s3를 사용하기 위한 설정
s3_client = boto3.resource(
's3',
aws_access_key_id = settings.MY_AWS_ACCESS_KEY_ID,
aws_secret_access_key = settings.MY_AWS_SECRET_ACCESS_KEY
)
# 모든 파일 리스트로 가져오기
files = request.FILES.getlist('photo')
# 유저 아이디 가져오기
user_id = request.user.id
for file in files:
# 업로드할 파일명 설정
# uuid로 생성한 고유한 스트링으로 파일명 변경
file._set_name(str(uuid.uuid4()))
# Key 설정 (폴더명/리소스 이름)
# 이미 존재하는 폴더명이면 바로 파일 추가, 존재하지 않는 폴더명이면 폴더 생성 후 파일 추가
key = f'posts/{user_id}{str(uuid.uuid4())}'
# s3 버킷에 업로드(key를 붙여 올릴 파일의 url을 생성, file을 jpeg 형식으로 넣음)
s3_client.Bucket('second-project-nhouse').put_object(
Key=key, Body=file, ContentType='jpeg')
# 이미지가 업로드 된 url. key를 붙여야 리소스 간 구분이 됨.
photo_url = settings.IMAGE_URL + key
4. 업로드한 사진 url과 json 데이터 가공해서 DB에 넣기
파일을 업로드할 수 있는 기능이 완성되었으므로
업로드한 파일의 url과 json 데이터의 각 항목을 DB에 저장하는 기능을 만들어보았다.
josn 데이터는 다음과 같은 형식으로 받는다고 가정한다.
각 사진마다 태그와 설명글이 달릴 수 있고, 각 태그는 x좌표/y좌표/상품 아이디를 값으로 가진다.
{
"living_type" : "원룸",
"room_size" : "10평 미만",
"family_type" : "싱글라이프",
"work_type" : "홈스타일링",
"contents" : [
{
"description" : "새로 꾸민 거실",
"tags" : [
{
"point_x" : 2.34,
"point_y" : 4.56,
"product_id" : 3
},
{
"point_x" : 2.34,
"point_y" : 4.56,
"product_id" : 7
}]
},
{
"description" : "은은한 조명",
"tags" : [
{
"point_x" : 20.44,
"point_y" : 424.56,
"product_id" : 10
},
{
"point_x" : 5.34,
"point_y" : 42.90,
"product_id" : 14
}]
}]
}
DB에 저장까지하는 코드는 이렇게 만들었다.
일단 기능이 돌아가는 데 초점을 두고 빠르게 진행했기 때문에 개선할 부분이 많다.
다음 편에서는 리팩토링 과정을 다뤄야겠다.
import json
import boto3
import uuid
from django.http import JsonResponse
from django.views import View
from django.conf import settings
from posts.models import Post, Photo, Tag
class PostWriteView(View):
@login_decorator
def post(self, request):
try:
data = json.loads(request.POST.get('body'))
living_type = data.get('living_type')
room_size = data.get('room_size')
family_type = data.get('family_type')
work_type = data.get('work_type')
contents = data.get('contents')
user_id = request.user.id
# Post 오브젝트 먼저 생성
post = Post.objects.create(
living_type = living_type,
room_size = room_size,
family_type = family_type,
work_type = work_type,
user_id = user_id
)
# s3 업로드 준비
s3_client = boto3.resource(
's3',
aws_access_key_id = settings.MY_AWS_ACCESS_KEY_ID,
aws_secret_access_key = settings.MY_AWS_SECRET_ACCESS_KEY
)
files = request.FILES.getlist('photo')
# s3에 모든 사진 업로드하기
# 각 사진 업로드 하고 나면
# 1) 해당 사진의 url과 설명을 DB의 photos 테이블에 저장
# 2) 해당 사진의 태그 정보를 DB의 tags 저장
for i in range(len(files) - 1):
files[i]._set_name(str(uuid.uuid4()))
key = f'posts/{user_id}{str(uuid.uuid4())}'
s3_client.Bucket('second-project-nhouse').put_object(
Key=key, Body=files[i], ContentType='jpeg')
photo_url = settings.IMAGE_URL + key
current_content = contents[i]
photo = Photo.objects.create(
description = current_content['description'],
url = photo_url,
post = post
)
for tag in current_content['tags']:
Tag.objects.create(
point_x = tag['point_x'],
point_y = tag['point_y'],
photo = photo,
product_id = tag['product_id']
)
return JsonResponse({'results': 'SUCCESS'}, status=201)
except KeyError:
return JsonResponse({'message':'KEY_ERROR'}, status=400)'TIL & 프로젝트 회고' 카테고리의 다른 글
| [2차 프로젝트 내일의 집] 카카오 api로 SNS 로그인/회원가입 리팩토링 (0) | 2022.08.10 |
|---|---|
| [2차 프로젝트 내일의 집] AWS s3로 글쓰기 api 구현하기 - 3. 리팩토링 (0) | 2022.08.09 |
| [2차 프로젝트 내일의 집] AWS s3로 글쓰기 api 구현하기 - 1. AWS 세팅(IAM 설정, S3 설정) (0) | 2022.08.08 |
| [2차 프로젝트 내일의 집] 상품 상세 api - Django ORM 최적화하기 심화 (0) | 2022.08.08 |
| [2차 프로젝트 내일의 집] 포스트 목록 api - Django ORM 최적화하기 (0) | 2022.08.07 |