Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/backup 2 backend #14

Merged
merged 14 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions backend/config/s3storages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from storages.backends.s3boto3 import S3Boto3Storage


# settings.py에서 static과 media에 storages.backends.s3boto3.S3Boto3Storage를 사용할 수 있지만
# 그러면 둘이 같은 경로에서 관리되므로 여기서 분리해줌 (저장 경로 관련된 부분)
# 우리 앱은 static 쓸 일 크게 없을 것 같긴 하지만..
class MediaStorage(S3Boto3Storage):
location = 'media'
file_overwrite = False


class StaticStorage(S3Boto3Storage):
location = 'static'
file_overwrite = False
40 changes: 33 additions & 7 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"SECRET_KEY": os.environ.get("SECRET_KEY"),
"EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER"),
"EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"),
"AWS_ACCESS_KEY_ID": os.environ.get("AWS_ACCESS_KEY_ID"),
"AWS_SECRET_ACCESS_KEY": os.environ.get("AWS_SECRET_ACCESS_KEY"),
"AWS_STORAGE_BUCKET_NAME": os.environ.get("AWS_STORAGE_BUCKET_NAME"),
"AWS_S3_REGION_NAME": os.environ.get("AWS_S3_REGION_NAME"),
}
else:
secret_file = os.path.join(BASE_DIR, 'secrets.json') # secrets.json 파일 위치를 명시
Expand All @@ -57,8 +61,34 @@ def get_secret(setting, secrets=secrets):
EMAIL_HOST_PASSWORD = get_secret("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

# AWS S3 settings
AWS_ACCESS_KEY_ID = get_secret("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = get_secret("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = get_secret("AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = get_secret("AWS_S3_REGION_NAME")
AWS_QUERYSTRING_AUTH = False
AWS_S3_FILE_OVERWRITE = False
# AWS_DEFAULT_ACL = 'public-read' uncomment하면 s3에 이미지 업로드 못함
AWS_S3_CUSTOM_DOMAIN = "%s.s3.%s.amazonaws.com" % (AWS_STORAGE_BUCKET_NAME, AWS_S3_REGION_NAME)
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}

# Static Setting
STATICFILES_STORAGE = "config.s3storages.StaticStorage" # 저장 루트 관련
STATIC_URL = "https://%s/static/" % AWS_S3_CUSTOM_DOMAIN # 저장 루트와 관련 X, 불러오는 루트
# STATICFILES_STORAGE 가 기본값(django.contrib.staticfiles.storage.StaticFilesStorage)일때 저장되는 곳
# STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

# Media Setting
DEFAULT_FILE_STORAGE = "config.s3storages.MediaStorage" # 저장 루트 관련
MEDIA_URL = "https://%s/media/" % AWS_S3_CUSTOM_DOMAIN # 저장 루트와 관련 X, 불러오는 루트
# DEFAULT_FILE_STORAGE 가 기본값(django.core.files.storage.FileSystemStorage)일때 저장되는 곳
# MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')


# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False

ALLOWED_HOSTS = [
'ec2-54-180-112-72.ap-northeast-2.compute.amazonaws.com',
Expand All @@ -83,6 +113,7 @@ def get_secret(setting, secrets=secrets):
'user',
'entry',
'setup',
'storages',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -130,8 +161,8 @@ def get_secret(setting, secrets=secrets):
# Add your own confidential.py
if not ("GITHUB_ACTIONS" in os.environ):
import confidential
DATABASES = confidential.DATABASES

DATABASES = confidential.DATABASES

# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
Expand Down Expand Up @@ -164,11 +195,6 @@ def get_secret(setting, secrets=secrets):

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

Expand Down
24 changes: 24 additions & 0 deletions backend/entry/migrations/0002_auto_20231027_1301.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.6 on 2023-10-27 04:01

from django.db import migrations, models
import entry.models


class Migration(migrations.Migration):

dependencies = [
('entry', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='symbol',
name='image',
field=models.ImageField(blank=True, upload_to=entry.models.img_upload_func),
),
migrations.AddField(
model_name='symbol',
name='is_valid',
field=models.BooleanField(default=False),
),
]
21 changes: 21 additions & 0 deletions backend/entry/migrations/0003_auto_20231028_1742.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.6 on 2023-10-28 08:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('entry', '0002_auto_20231027_1301'),
]

operations = [
migrations.AlterField(
model_name='symbol',
name='category',
field=models.IntegerField(),
),
migrations.DeleteModel(
name='Category',
),
]
18 changes: 18 additions & 0 deletions backend/entry/migrations/0004_alter_symbol_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2023-10-28 11:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('entry', '0003_auto_20231028_1742'),
]

operations = [
migrations.AlterField(
model_name='symbol',
name='text',
field=models.CharField(max_length=20),
),
]
21 changes: 21 additions & 0 deletions backend/entry/migrations/0005_alter_symbol_created_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.6 on 2023-10-29 04:35

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('entry', '0004_alter_symbol_text'),
]

operations = [
migrations.AlterField(
model_name='symbol',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='symbols', to=settings.AUTH_USER_MODEL),
),
]
21 changes: 21 additions & 0 deletions backend/entry/migrations/0006_alter_symbol_created_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.6 on 2023-10-29 05:51

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('entry', '0005_alter_symbol_created_by'),
]

operations = [
migrations.AlterField(
model_name='symbol',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='symbols', to=settings.AUTH_USER_MODEL),
),
]
33 changes: 21 additions & 12 deletions backend/entry/models.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import os
from django.utils import timezone
from uuid import uuid4

from django.db import models

from user.models import User


# Create your models here.
# 저장 경로 설정
def img_upload_func(instance, filename):
prefix = 'symbol/user_{}/'.format(instance.created_by.id)
ymd_path = timezone.now().strftime('%Y%m%d')
file_name = uuid4().hex # random string
extension = os.path.splitext(filename)[-1].lower() # 확장자 추출
return "".join(
[prefix, ymd_path, file_name, extension, ]
)


class Symbol(models.Model):
text = models.CharField(max_length=15, null=False, blank=False)
category = models.ForeignKey('Category', related_name='symbols', on_delete=models.CASCADE) # Would it be better to use SET_NULL/DEFAULT?
# image = models.ImageField
created_by = models.ForeignKey('user.User', related_name='symbols', on_delete=models.CASCADE)
text = models.CharField(max_length=20, null=False, blank=False)
category = models.IntegerField(null=False, blank=False)
image = models.ImageField(blank=True, upload_to=img_upload_func)
created_by = models.ForeignKey('user.User', related_name='symbols', on_delete=models.CASCADE, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
is_valid = models.BooleanField(default=False)


class FavoriteSymbol(models.Model):
symbol = models.ForeignKey('Symbol', related_name='favorites', on_delete=models.CASCADE)
user = models.ForeignKey('user.User', related_name='favorites', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)


# Would it be better to remove this category table?
# doesn't seem so useful..
class Category(models.Model):
text = models.CharField(max_length=15, null=False, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
64 changes: 61 additions & 3 deletions backend/entry/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ class FavoriteBackupSerializer(serializers.Serializer):

def validate(self, data):
id = data.get('symbol_id')
# Symbol table에 저장된 마지막 symbol의 id
last_symbol_id = Symbol.objects.latest('id').id
user = self.context['user']

if id not in list(range(1, last_symbol_id+1)):
all_symbols = list(Symbol.objects.values_list('id', flat=True))
if id not in all_symbols:
raise ValidationError({"id": ["Invalid symbol (no such symbol)"]})

# Default symbol이 아니라면
if id > 500:
symbol = Symbol.objects.get(id=id)
if symbol.created_by != user:
raise ValidationError({"not_mine": ["the requested symbol is created by another user"]})

return data

def create(self, validated_data):
Expand All @@ -28,3 +34,55 @@ def create(self, validated_data):
FavoriteSymbol.objects.create(user=user, symbol=symbol)


class MySymbolBackupSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
text = serializers.CharField(required=True)
category = serializers.IntegerField(required=True)
image = serializers.ImageField(required=True)
created_at = serializers.DateTimeField(read_only=True)

def validate(self, data):
text = data.get('text')
category = data.get('category')

# To send custom error message
if len(text) > 20:
raise ValidationError({"long_text": ["word text is too long"]})

if not (1 <= category <= 24):
raise ValidationError({"category": ["No such category"]})

return data

def create(self, validated_data):
user = self.context['user']

text = validated_data['text']
category = validated_data['category']
image = validated_data['image']

symbol = Symbol.objects.create(text=text, category=category, image=image, created_by=user)

return symbol


class MySymbolEnableSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)

def validate(self, data):
id = data.get('id')
user = self.context['user']

all_symbols = list(Symbol.objects.values_list('id', flat=True))
if id not in all_symbols:
raise ValidationError({"id": ["Invalid symbol (no such symbol)"]})

if id <= 500:
raise ValidationError({"id": ["Default symbol"]})

# Default symbol이 아닌 경우
symbol = Symbol.objects.get(id=id)
if symbol.created_by != user:
raise ValidationError({"not_mine": ["the requested symbol is created by another user"]})

return data
6 changes: 5 additions & 1 deletion backend/entry/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@

app_name = 'entry'
urlpatterns = [
path('backup/', views.MySymbolBackupView.as_view(), name='backup my symbol'),
path('', views.MySymbolRetrieveView.as_view(), name='get all of my symbols'),
path('<int:pk>/', views.MySymbolRetrieveView.as_view(), name='get my specific symbol'),
path('favorite/backup/', views.FavoriteBackupView.as_view(), name='backup favorite symbol'),
]
path('enable/', views.MySymbolEnableView.as_view(), name='enable my symbol'),
]
Loading