이 포스팅에서는 장고 3.0와 python 3.8.x을 이용하여 간단한 앱을 구현할 것입니다. 그리고 본격적으로 웹 어플리케이션을 만들기 전에 프로젝트 스켈레톤에 대해 다룰 예정입니다.
- 가상환경(Virtual Environment)구축하기.
- 프로젝트 구조 구성하고 첫번째 앱 시작하기.
- 환경별로 settings 분리하기.
- 민감한 설정정보들 관리하기.
- STATIC 파일 관리하기.
가상환경(Virtual Environment) 구축하기.
로컬에 python 3.8.x가 설치되어 있다고 가정하고 시작하겠습니다. 만약 python 3.8.x가 설치되어 있지 않다면 여기를 참조하여 설치를 진행해 주세요.
에제의 root directory명은 learn-django3 입니다. 해당 디렉토리로 이동하여 아래 커맨드를 실행해 주세요.
$ cd ~/learn-django3/
$ python3.8 -m venv env
env 디렉토리로 가상환경이 생성되었 습니다. 쉘에서 생성한 가상환경을 활성화(activation) 하려면 activate를 실행해 주어야 합니다. 아래 커맨드를 실행해 주세요.
$ source env/bin/activate
실행을하면 CLI(Command Line Interface)의 prefix에 아래와 같이 표시됩니다.
(env) $
가상환경에서 빠져나오려면 아래의 커맨드를 실행하면 됩니다.
(env) $ deactivate
여기까지가 가상환경을 생성하고, 활성화하고, 다시 비활성화하는 방법에 대해 알아봤습니다.
프로젝트 구조 구성하고 첫번째 앱 시작하기.
잘 디자인되어 일관성있는 프로젝트 구조는 비즈니스 로직에 더 빠르게 집중하여 생산성을 높힐 수 있습니다. 프로젝트 구조인 레이아웃은 장고로 개발된 많은 프로젝트와 개발자들 사이에서도 의견이 분분합니다. 개인적으로 많은 장고 프로젝트를 진행하면서 익숙해진 나름의 프로젝트 레이아웃에 대해 설명하겠습니다.
웹 어플리케이션을 운영하다보면 장고 프로젝트 외에 Dockerfile, .gitignore, nginx, version, README, setup.py 파일 등이 위치할 디렉토리 레벨도 필요로 합니다. 그래서 장고 프로젝트는 src 디렉토리 하위로 위치시키는게 관리 측면에서 유리합니다. 장고 프로젝트를 생성하기 앞서 패키지 관리 도구인 setup.py를 생성하여 장고를 설치해 보도록 하겠습니다.
setup.py
패키지를 관리하는 방법에는 setup.py와 requirements.txt 두 가지 방법이 있습니다. 그 중 메타데이터까지 포함하여 더 많은 정보를 주는 setup.py로 패키지를 관리하도록 하겠습니다.
setup.py를 루트 디렉토리에 생성하여 아래 코드를 따라 작성해 주세요.
# ~/learn-django3/setup.py
from setuptools import setup
setup(
name='learn-django3',
version='0.0.1',
author='coninggiu',
install_requires=[
'django>=3.0',
],
)
setup.py에는 위 정보들 말고도 더 많은 메타데이터를 포함시킬 수 있습니다. 하지만 당장은 장고 학습에 필요한 정보만 입력하도록 하겠습니다. 위 코드를 작성하고 아래와 커맨드를 실행하여 패키지를 설치해 주세요.
(env) $ pip install -e .
가상환경에 장고가 정상적으로 설치되었습니다. 아래 커맨드를 실행하여 설치된 패키지 목록을 알수 있습니다.
(env) $ pip freeze
...
Django==3.0.7
...
가상환경에 장고 패키지를 설치했으니 이제 프로젝트와 앱을 설치해 보겠습니다. 앞서 장고 프로젝트는 src 하위 디렉토리에 위치시킨다고 하였습니다. 아래 커맨드를 실행해 주세요.
(env) $ mkdir src
(env) $ cd src
(env) $ django-admin.py startproject coningguproj
프로젝트명을 ‘coningguproj’로 하였습니다. 필요에 따라 변경하여 생성해주시기 바랍니다. 다음은 아래 커맨드로 웹서버를 시작해 주세요.
(env) $ ./manage.py runsever
웹 브라우저에서 http://127.0.0.1:8000 으로 접근하면 성공적으로 프로젝트가 생성된 것을 확인할 수 있다. 자 다음으로 첫번째 app 을 생성하고 장고에게 알려주고 app의 디렉토리 구조를 수정하도록 하겠습니다. 아래 커맨드를 실행해 주세요. 프로젝트와 마찬가지로 app명은 필요에 따라 변경해 주세요.
(env) $ ./manage.py startapp coningguapp
startapp 커맨드를 실행하면 장고가 자동적으로 app에 대한 디렉토리와 필수파일들을 설치해 줍니다. 설치한 앱을 장고가 인식하도록 settings.py에 장고 built-in apps 아래에 app을 추가해 주세요.
# coningguproj/settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'coningguapp.apps.ConingguappConfig', # 추가
]
...
다음으로는 urls.py에 URL Route를 구성해 주는 것입니다. app 별로 url 을 별도 분리하여 관리할 것이기에 coningguapp에 urls.py를 생성해 주고 프로젝트의 root urls.py에서는 include 함수를 사용하여 app.urls을 포함해 줍니다.
# coningguproj/urls.py
from django.contrib import admin
from django.urls import path, include # include 추가
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('coningguapp.urls')), # 추가
]
그리고 coninguapp.urls.py에 / 에 대한 route를 구성해줍니다.
# coningguapp/urls.py
from django.urls import path
urlpatterns = [
path('', HomepageView.as_view(), name='homepage'), # 추가
]
path()를 보면 3개의 인자를 전달해주는 것을 알 수 있습니다. 첫번째 인자는 url을 입력하고, 두번째 인자는 target view를 입력하고, 세번째 인자는 optional인 name을 입력합니다. name을 설정하면 이를 이용해 template에서 {% url ‘name’ %}, view에서 reverse(‘name’)처럼 URL path를 하드코딩하지 않고 name을 이용하여 path를 불러올 수 있습니다. 이는 모범사례임으로 되도록 작성해주도록 합니다. 그리고 장고에서 view를 정의하는 방법으로 FBV(Function Based View), CBV(Class Based View) 두 가지가 있는데 coninggu 프로젝트에서는 CBV로 view를 구현할 것이기에 url에서는 항상 as_view()까지 작성해 주어야 합니다. CBV로 view를 구성하면 더 적은 코드와 코드를 재사용하면서 view를 구현할 수 있습니다.
다음은 / 의 간단한 target view를 작성해 보겠습니다.
# coningguproj/view.py
from django.http import HttpResponse
from django.views.generic import View
class HomepageView(View):
def get(self, request):
return HttpResponse('Hello, World!!!')
단순히 “Hello World”만 출력할 것이기에 템플릿 파일을 설정하는 대신에 하드코딩하였습니다.
다시 runserver command 를 실행하여 브라우저에 접근해보면 “Hello, World!!!가 화면에 출력되는 걸 볼 수 있습니다.
장고는 기본포트번호로 8000을 사용하는데 다른 포트를 사용하려면 runserver의 인자로 포트번호를 추가해주면 됩니다.
(env) $ ./manage.py runsever 8080
프로잭트 구조와 “Hello World”가 출력되는 간단한 첫번째 앱을 시작해봤습니다. 다음으로는 Settings 관리에 대해 다루겠습니다.
환경별로 Settings 분리하기.
일반적인 웹 개발은 새로운 기능이 추가될 때 마다 development 환경에서 새 기능을 개발하고, test 환경에서 테스팅한 이후 staging, production 환경에 deploy됩니다. 그리고 각 환경들은 환경별로 설정값이 다를 것입니다. 이를 위해 장고에서 settings를 유연하게 관리하는 방법에 대해 알아보겠습니다.
1. 우선, 아래와 같이 장고 프로젝트에서 settings 디렉토리를 생성하고 환경별 파일을 생성해 줍니다.
# coningguproj/settings/
__init__.py
base.py
dev.py
test.py
prod.py
staging.py
2. settings.py에 있는 설정들을 base.py로 복사하고, settings.py를 삭제합니다.
3. base.py의 BASE_DIR의 값을 아래처럼 상위 값으로 변경합니다.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname((os.path.abspath(__file__)))))
4. 모든 환경파일에 base의 설정을 import 합니다.
# coningguproj/settings/dev.py
# coningguproj/settings/test.py
# coningguproj/settings/staging.py
# coningguproj/settings/prod.py
from .base import *
5. manage.py와 wsgi.py에서 아래처럼 기본 settings를 수정합니다.
# manage.py
# wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'coningguproj.settings.dev')
6. 이제 분리한 파일로 환경별로 다른 EmailBackend를 구성해보겠습니다. 아마 dev환경에서는 console로 출력되길 원할거고, test 환경에서는 아무런 동작을 하지 않길 원할것이고, staging, production 환경에서는 실제 메일이 전송되길 원할것입니다. 아래처럼 환경별로 설정을 변경해 주겠습니다.
# coningguproj/settings/dev.py
from .base import *
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# coningguproj/settings/test.py
from .base import *
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
runserver command 의settings 옵션으로 각 환경별로 server를 가동시킬 수 있습니다.
(env) $ ./manage.py runserver --settings='coningguproj.settings.test'
장고는 settings에서 media, static files, template, translation files의 path를 정의해줄 수 있습니다. 가상환경으로 구성했다고 하더라도 개발자마다 OS가 다를수 있고, 도커 컨테이너화 하였다고 하더라도 Absolute Path(절대 경로)보다 Relative Path(상대 경로)로 정의하는게 유지보수성(maintainability)과 이식성(portability)를 줄일 수 있습니다. 이런 이유로 위 설정들에 대해 상대경로를 base.py에서 정의해 보겠습니다. 프로젝트 구성마다 설정은 달라질 수 있으니 참고해 주시기 바랍니다.
# coningguproj/settings/base.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname((os.path.abspath(__file__)))))
TEMPLATES = [
{
#...
'DIRS': ['templates'],
#...
},
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
환경을 파일로 나뉘어 관리하는 방법을 보았습니다. 이처럼 민감하지 않는 정보들(nonsensitive)은 환경별로 파일을 나뉘어 VCS에서 관리할 수 있습니다. 다음은 민감한(sensitive) 정보를 다루는 방법에 대해 알아보겠습니다.
민감한 설정정보들 관리하기.
장고 프로젝트를 진행하면서 API Keys, 비밀번호 등 반드시 민감한(sensitive)정보를 다루게 될 것입니다. 이런 정보들은 VCS에서 관리하는 건 권장되지 않는 방법이기에 환경변수 또는 파일로 관리해야 합니다.
환경변수로 관리하기.
환경변수로 민감한 정보를 관리하는 방법으로는 os 모듈을 사용해야 합니다. os 모듈을 사용하여 환경변수를 불러오는 헬퍼 함수를 만들어보도록 하겠습니다. 헬퍼 함수는 utils이라는 디렉토리에 위치시키도록 하겠습니다.
#coningguapp/utils/imports.py
import os
from django.core.exceptions import ImproperlyConfigured
def get_environ(key):
"""환경변수 값을 가져오거나 환경변수가 존재하지 않는 경우 에러를 발생시킵니다."""
try:
return os.environ[key]
except KeyError:
raise ImproperlyConfigured(f'Environment variable {key} does not exist.')
그리고 settings에 get_environ() 헬퍼 함수를 사용하여 SECRET_KEY 를 가져오도록 변경하겠습니다.
# coningguproj/settings/base.py
SECRET_KEY = get_environ('SECRET_KEY')
runserver를 하면 “ Environment variable SECRET_KEY does not exist” 에러가 발생할 것입니다. 원인은 SECRET_KEY 환경변수를 설정하지 않았기 때문입니다. IDE로 파이참을 사용중이시라면 Preferences에서 설정할 수 있고, 터미널에서 설정을 하실거면 아래 커맨드를 입력해 주시면 됩니다. {secret_key} 는 변경해 주세요.
(env) $ export SECRET_KEY={secret_key}
SECRET_KEY 외 API Keys, 비밀번호, 데이터베이스 정보 등과 같은 민감한 정보도 환경변수로 설정하여 get_secret()를 사용하도록 변경해주시면 됩니다.
파일로 관리하기.
환경변수 대신 민감한 정보를 관리하는 방법으로는 파일로 관리하는 것입니다. 대표적으로 YAML, JSON 포맷입니다. 환경변수 예와 마찬가지로 os와 json 모듈을 사용하여 정보를 불러오는 헬퍼함수를 작성해보겠습니다.
# coningguapp/utils/imports.py
import os
import json
from django.core.exceptions import ImproperlyConfigured
_secrets = None
def load_secrets():
global _secrets
with open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname((os.path.abspath(__file__))))),
'_secrets.json'), 'r') as f:
_secrets = json.loads(f.read())
def get_secret(key):
global _secrets
if _secrets is None:
load_secrets()
try:
return _secrets[key]
except KeyError:
raise ImproperlyConfigured(f'Secret variable {key} does not exists.')
그리고 _secrets.json 파일을 프로젝트 루트 디렉토리에 위치시킵니다.
# coningguproj/_secrets.json
{
"SECRET_KEY": "2_sq0#63v_8#yq_hsiov&gguc$lz89gcj-f&rq++**_iwel+qj"
}
settings 에서도 get_secret 함수를 사용하도록 변경해 줍니다.
SECRET_KEY = get_secret('SECRET_KEY')
STATIC 파일 관리하기.
css, js, image 등 정적 파일을 STATIC_URL로 설정한 경우, 업데이트 할 때마다 변경 방문자 브라우저의 캐시를 지워야 합니다. 대부분 이를 위해서 URL에 timestamp 또는 version을 붙혀 업데이트가 발생할 때 마다 방문자 브라우저의 정적 파일을 강제로 로드시킵니다. 그럼 version이 변경될 때 마다 로드시키는 코드를 작성해보겠습니다.
1. versioning
- 최상단 레벨에 VERSION 파일을 생성하여 프로젝트 version을 관리하도록 합니다.
# src/VERSION
0.0.1
2. get_version()
- get_version() 함수에서는 VERSION 파일을 읽어 값을 반환합니다.
# coningguapp/utils/imports.py
# ...
def get_version():
return open(os.path.join(os.path.dirname((os.path.abspath(__file__))), '../../../../', 'VERSION')).read().strip()
3. STATIC_URL
- settings.STATIC_URL에 version을 추가합니다.
# coningguproj/settings/base.py
from coningguapp.utils.imports import get_environ, get_secret, get_version
# ...
STATIC_URL = f'/static/{get_version()}/'
# ...
정리
이 포스팅에서는 본격적으로 어플리케이션을 작성하기 전에 필요한 스켈레톤에 대해 알아보았습니다. 장고 프로젝트를 진행하시면서 조금이나마 도움이 되셨으면 합니다.
'Python > Django' 카테고리의 다른 글
Django 2.2 | 유저 모델 커스텀하기 (0) | 2020.09.29 |
---|---|
Django 2.2 | 프로젝트 디렉토리 구조 (2) | 2020.09.29 |
Django 2.2 | Hello World 그리고 dockerize(도커화) (0) | 2020.09.29 |