안녕하세요! 개발자 최영철입니다.
이번 포스트에서는 어떻게 홈페이지를 만들 것인지 기획하고 DB를 구성하는 이야기를 하려고 합니다.
먼저 홈페이지에 무엇이 있으면 좋을까 생각했습니다.
지금 만드는 홈페이지는 학습과 동시에 개인 포토폴리오 사이트로 활용할 예정이었기 때문에 본인의 프로필, 사용 가능한 기술 스택, 프로젝트를 나타내는 화면은 반드시 필요했습니다. 그런데 이것만 표시한다면 프론트엔드에 모든 내용을 넣으면 되고 백엔드가 필요가 없었습니다. 백엔드를 활용할 수 있는 방안을 생각하던 중 프로젝트 내용을 DB에 넣고 프론트엔드가 백엔드를 통해 목록을 가져오도록 하면 좋겠다는 생각이 들었습니다.
Django에서 Project 모델을 만들기 위해 프론트엔드에서 표시할 내용을 정하기 시작했습니다. 먼저 프로젝트의 이름과 그에 대한 설명이 필요하므로 각각을 넣을 title과 content 필드를 추가했습니다. 단순히 줄글만 있다면 너무 허전하기 때문에 이미지도 같이 표시하면 좋겠다고 생각했고 img 필드도 추가했습니다. 나중에 설명하겠지만 프론트엔드에서 이미지가 없을 경우 이미지를 표시하는 부분에 Project {title}로 표시하기로 정했기 떄문에 img는 반드시 포함되지 않으므로 null=True로 설정했습니다. 프로젝트에 대한 자세한 설명이 있는 외부 페이지가 있으면 (현재 블로그처럼) 그 곳으로 리다이렉트 할 수 있도록 link 필드를 null=True로 설정했습니다.
#첫 모델 코드
class Project(models.Model):
id = models.BigAutoField(primary_key=True)
title = models.CharField(max_length=50)
content = models.CharField(max_length=200)
img = models.FileField(null=True)
link = models.URLField(null=True, max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
이렇게 처음 Project 모델을 만들었습니다. 그런데 이미지를 업로드하고 다운로드할 저장소가 필요했고 회사에서 사용해본 AWS S3과 연동하기로 했습니다.
AWS S3 버킷을 퍼블릭으로 만들고 외부에서 사용할 수 있도록 다음과 같이 버킷 정책을 설정했습니다:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::zeroiron-bucket/*"
}
]
}
그 다음 Django의 my_settings.py와 settings.py에 AWS S3 관련 값을 저장했습니다.
my_settings.py와 settings.py를 나눈 이유는 GitHub repo에 push할 때 민감한 정보를 .gitignore하기 위해서입니다.
#my_settings.py
AWS_S3_ACCESS_KEY = AWS S3 액세스키
AWS_S3_SECRET_ACCESS_KEY = AWS S3 비밀 액세스 키
AWS_STORAGE_BUCKET_NAME = AWS S3 버킷 이름
AWS_REGION = AWS S3 리전
AWS_TOTAL_DOMAIN = '{}.s3.{}.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME, AWS_REGION)
#settings.py
AWS_ACCESS_KEY_ID = my_settings.AWS_S3_ACCESS_KEY
AWS_SECRET_ACCESS_KEY = my_settings.AWS_S3_SECRET_ACCESS_KEY
AWS_REGION = my_settings.AWS_REGION
이제 Django에서 받은 이미지파일을 AWS S3에 업로드하는 방법을 구현해야했습니다.
예전에는 boto3와 smart_open을 이용해서 boto3로 AWS S3 세션을 열고 smart_open을 이용해 파일을 저장하는 함수를 만들고 이를 User를 생성하는 serializer의 perform_create를 넣었습니다. 다음은 그 예시입니다:
#filemanage/uploader.py
import boto3
import smart_open
import uuid
class FileManager:
@classmethod
def get_uuid_name(cls, filename):
split = filename.split('.')
ext = split[-1]
uuid_name = '{}.{}'.format(''.join(str(uuid.uuid4()).split('-')), ext)
return uuid_name
@classmethod
def file_upload_to_s3(file):
session = boto3.Session(
aws_access_key_id = AWS_ACCESS_KEY_ID
aws_secret_access_key = AWS_SECRET_ACCESS_KEY
region_name = AWS_REGION
)
client = session.client('s3')
uuid_name = cls.get_uuid_name(file.name)
s3_url = 's3://{}/file/{}'.format(AWS_STORAGE_BUCKET_NAME, uuid_name)
with smart_open.open(s3_url, 'wb', transport_params={'client': client}) as fout:
bytes = fout.write(file.read())
return 'https://{}/{}'.format(AWS_S3_TOTAL_DOMAIN, 'file/' + uuid_name)
#users/serializers.py
from filemanage.uploader import FileManager
class UserSerializer(serializers.ModelSerializer):
def perform_create(self, validated_data):
user = User.objects.create_user(**validated_data)
#...
if 'img' in validated_data:
url = FileManager.file_upload_to_s3(validated_data['img'])
user.img = url
#...
user.save()
#...
위의 방식으로 할까 고민하다가 django-storages의 S3Storage를 이용하는 방법을 알게 되었고, 간편할 것 같아 이를 사용하기로 했습니다.
storages를 사용하기 위해선 settings.py에 기본 설정이 필요했습니다.
static과 media 파일의 저장 위치와 default로 사용할 Storage를 저장했고
이에 맞게 다시 Project 모델을 수정했습니다.
#settings.py
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATIC_URL = "https://%s/static/" % my_settings.AWS_TOTAL_DOMAIN
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_URL = "https://%s/media/" % my_settings.AWS_TOTAL_DOMAIN
#proj/models.py
import django.db.models as models
from django.dispatch import receiver
import uuid
def get_uuid_name(instance, filename):
split = filename.split('.')
ext = split[-1]
uuid_name = '{}.{}'.format(''.join(str(uuid.uuid4()).split('-')), ext)
return uuid_name
class Project(models.Model):
id = models.BigAutoField(primary_key=True)
title = models.CharField(max_length=50)
content = models.CharField(max_length=200)
img = models.FileField(null=True, upload_to=get_uuid_name)
link = models.URLField(null=True, max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
@receiver(models.signals.post_delete, sender=Project)
def remove_file_from_s3(sender, instance, using, **kwargs):
instance.img.delete(save=False)
위의 코드에서 추가적으로 receiver 데코레이터가 달린 함수가 있는 것을 알 수 있는데 Project의 매니저가 자신의 테이블에서 특정 인스턴스를 삭제할 때 삭제가 이루어진 뒤 호출되어 AWS S3에서도 파일이 삭제되도록 만드는 함수입니다. img필드에는 파일 이름을 uuid로 바꿀 수 있도록 upload_to에 get_uuid_name 함수를 넣었습니다. 이렇게 까지 구현한 후 테스트 해본 결과 Projects.objects.create를 진행할 때 img 필드에 할당된 파일이 자동으로 S3에 파일을 저장되는 것을 확인할 수 있었습니다.
그런데 MySQL에서 테이블을 확인할 때 img 필드에는 단순히 파일 이름만 적혀있는 것을 확인할 수 있었습니다. 이것으로 serializer를 통해 json 데이터가 반환될 때 자동으로 AWS S3 버킷 주소가 포함되어 반환된다는 것을 알 수 있었습니다.
코드로 인해 글이 길어졌으므로 나머지 모델의 구현은 다음 글로 작성하겠습니다.
감사합니다.
'FE & BE' 카테고리의 다른 글
6. 방명록 프론트엔드 구현 [MyHomepage: 개인 홈페이지 만들기] (0) | 2022.11.17 |
---|---|
5. React Hook과 방명록 백엔드 구현 [MyHomepage: 개인 홈페이지 만들기] (0) | 2022.11.16 |
4. React 모달창 구현 + Styled Components [MyHomepage: 개인 홈페이지 만들기] (0) | 2022.11.15 |
3. User모델과 OAuth 2.0 [MyHomepage: 개인 홈페이지 만들기] (0) | 2022.11.15 |
1. 프레임워크 결정 + React 기본 문법 [MyHomepage: 개인 홈페이지 만들기] (0) | 2022.11.15 |
댓글