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

S3 스토리지 설정 추가 #2

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ [email protected]
# CACHE
CACHE_URL=rediscache://127.0.0.1:6379/1

# FILES
MEDIA_ROOT=/path/to/media
# STORAGES
STORAGE_DEFAULT=s3://key:[email protected]/bucket?querystring_expire=86400&location=media
STORAGE_STATICFILES=s3://key:[email protected]/bucket?querystring_expire=86400&location=static
113 changes: 113 additions & 0 deletions apps/shared/utils/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import json
from urllib.parse import parse_qs, urlparse

from .string import str_to_bool


class BaseStorageParser:
BACKEND = None
OPTION_CASTS = {}

def __init__(self, url):
self.url = urlparse(url)
self.query_params = self.url_querystring_to_dict()

def __call__(self):
storage = {'BACKEND': self.BACKEND}
options = self.get_options()
if options:
storage['OPTIONS'] = options
return storage

def url_querystring_to_dict(self):
query_string = self.url.query

query_dict = parse_qs(query_string)

for key, value in query_dict.items():
if len(value) == 1:
query_dict[key] = value[0]

return {
key: self.OPTION_CASTS[key](value) if key in self.OPTION_CASTS else value
for key, value in query_dict.items()
}

def get_options(self):
return None


class FileSystemStorageParser(BaseStorageParser):
BACKEND = 'django.core.files.storage.FileSystemStorage'

def get_options(self):
options = {**self.query_params}

location = ''
if self.url.hostname:
location = self.url.hostname
if self.url.path:
location += self.url.path

if location:
options['location'] = location
return options


class StaticFilesStorageParser(FileSystemStorageParser):
BACKEND = 'django.contrib.staticfiles.storage.StaticFilesStorage'


class S3StorageParser(BaseStorageParser):
BACKEND = 'storages.backends.s3boto3.S3Boto3Storage'
OPTION_CASTS = {
'object_parameters': json.loads,
'querystring_auth': str_to_bool,
'max_memory_size': int,
'querystring_expire': int,
'file_overwrite': str_to_bool,
'gzip': str_to_bool,
'use_ssl': str_to_bool,
'verify': str_to_bool,
'proxies': json.loads,
'transfer_config': json.loads,
}

def get_options(self):
endpoint_url = '.'.join(self.url.hostname.split('.')[1:])
return {
'endpoint_url': f'https://{endpoint_url}',
'bucket_name': self.url.path[1:],
'access_key': self.url.username,
'secret_key': self.url.password,
'region_name': self.url.hostname.split('.')[0],
**self.query_params,
}


STORAGE_PARSERS = {
'filesystem': FileSystemStorageParser,
'staticfiles': StaticFilesStorageParser,
's3': S3StorageParser,
}


def get_storages(env, storages=None):
if storages is None:
storages = ['default', ('staticfiles', 'staticfiles://')]

storage_settings = {}

for storage in storages:
if isinstance(storage, tuple):
storage_url = env(f'STORAGE_{storage[0].upper()}', default=storage[1])
storage_name = storage[0]
else:
storage_url = env(f'STORAGE_{storage.upper()}')
storage_name = storage

storage_scheme = urlparse(storage_url).scheme
parser_class = STORAGE_PARSERS[storage_scheme]
storage_settings[storage_name] = parser_class(storage_url)()

return storage_settings
4 changes: 4 additions & 0 deletions apps/shared/utils/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
def generate_random(type='char', length=6):
chars = string.ascii_uppercase + string.digits
return ''.join(random.choice(chars) for _ in range(length))


def str_to_bool(value):
return value.lower() in ['true', '1', 'yes']
10 changes: 6 additions & 4 deletions project_name/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import environ

from apps.shared.utils.storage import get_storages

env = environ.Env()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
Expand Down Expand Up @@ -106,20 +108,20 @@

USE_TZ = True

# Storages for Static and Media Files
# https://docs.djangoproject.com/en/5.0/ref/settings/#std-setting-STORAGES

STORAGES = get_storages(env)

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

STATIC_URL = '/static/'

STATIC_ROOT = BASE_DIR / 'resources/static'

STATICFILES_DIRS = [BASE_DIR / 'static']

MEDIA_URL = '/media/'

MEDIA_ROOT = env('MEDIA_ROOT')


# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
django==5.0
django-cleanup
django-environ
django-storages[s3]==1.14.2
easy-thumbnails
psycopg[binary]
Loading