IT/WEB

[Django] Django 개발을 시작할 때, 최적의 프로젝트 디렉토리 구성

개발자 두더지 2022. 6. 17. 10:30
728x90

일본의 한 블로그 글을 번역한 포스트입니다. 오역 밒 직역, 의역이 있일 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 Django 개발에 대해서 알아보던중, Django로 개발할 때 의식하면 좋은 최적의 구성이 있다는 사실을 알게 됐다. 이번 포스팅에서는 이와 관련된 얘기를 하고자 한다. 목차는 다음과 같다.

  • 설정 파일을 config등 알기 쉬운 명칭으로 한다.
  • static 디렉토리를 생성한다.
  • 시크릿 변수나 환경 의존 변수는 .env 파일에 쓴다.
  • 단계별로 설정 파일을 전환할 수 있도록 한다.
  • 어플리케이션 마다 urls.py를 배치한다.
  • 베이스 템플릿을 사용한다.

 이 목차를 모두 거치면 다음과 같이 디렉토리 구성이 변경된다(첫 번째가 변경'전', 두 번째가 변경'후'이다). 참고로 프로젝트명은 임의로 작성한 것으로 이번 포스팅에서는 "mysite" 상정했다.

# 변경전

mysite
|-- apl (<- 어플리케이션 디렉토리)
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- migrations
|   |   `-- __init__.py
|   |-- models.py
|   |-- tests.py
|   `-- views.py
|-- manage.py
`-- mysite (<- 설정 디렉토리)
    |-- __init__.py
    |-- asgi.py
    |-- settings.py
    |-- urls.py
    `-- wsgi.py
# 변경후

mysite
|-- .env
|-- app (<-어플리케이션 디렉토리)
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- migrations
|   |   `-- __init__.py
|   |-- models.py
|   |-- tests.py
|   |-- urls.py
|   `-- views.py
|-- manage.py
|-- config (<-설정 디렉토리)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings
|   |   |-- __init__.py
|   |   |-- base.py
|   |   |-- local.py
|   |   |-- production.py
|   |   `-- staging.py
|   |-- urls.py
|   `-- wsgi.py
|-- static (<- static디렉토리)
|   `-- admin
|       |-- css
|       |-- fonts
|       |-- img
|       `-- js
`-- templates (<- templates디렉토리)
    |-- common
    |   `-- base.html
    `-- app
        `-- index.html

 

 

설정 디렉토리를 config 등 알기 쉬운 명칭으로 하기


  기본 설정에서는 설정 디렉토리가 프로젝트명과 동일하게 되어 있어, 디렉토리의 구성을 쉽게 파악하기 힘들게 되어 있다. 따라서 설정 디렉토리를 config이라는 명칭으로 바꾼다.

 프로젝트 작성시에는 아래와 같은 커맨드를 실행한다. 그러면 config이라는 명칭으로 생성될 것이다.

$ mkdir mysite
$ cd ./mysite
$ django-admin startproject config .

 결과를 확인하면 다음과 같다.

mysite
|-- manage.py
`-- config (<- 설정 디렉토리)
    |-- __init__.py
    |-- asgi.py
    |-- settings.py
    |-- urls.py
    `-- wsgi.py

 

 

static 디렉토리를 생성하기


 static파일은 APP 서버에서가 아닌 Web 서버에서 반환하는 구성일 일반적이다. Django에서는 STATIC_ROOT를 설정하는 것으로, static 파일 전용의 static 디렉토리를 정의하는 것이 가능하다.

 STATIC_ROOT를 설정하는 방법은 "mysite/config/setting.py" 안에 아래를 추가하는 것이다.

STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

 그 다음 아래의 커맨드를 입력하면 static 디렉토리가 생성된다.

$ python manage.py collectstatic

 이러한 설정으로 기본 디렉토리 구성에서 아래와 같은 구성으로 변경된다.

mysite
|-- manage.py
|-- config (<- 설정 디렉토리)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
`-- static (<- static디렉토리)
    `-- admin
        |-- css
        |-- fonts
        |-- img
        `-- js

 

 

시크릿 변수나 환경 의존 변수는 .env 파일에 쓴다.


 시크릿 변수나 환경 의존 변수는 1개의 파일로 묶어서 일괄 관리하는 것이 좋다. 그러한 변수를 관리하는 파일이 .env이라고 불리는 파일이다.

 .env를 읽어들일 Python 라이브러리로써 추천하는 것이 "python-dotenv"이다. 아래의 커맨드로 설치할 수 있다.

$ apt-get install python-dotenv

 .env에 기재할 내용은 API 토큰이나 패스워등의 비밀스러운 정보나 DB의 설정, 디버그의 설정 등 개발 단계에 따른 정보등을 기재한다.

 기재하는 방법은 다음과 같다.

ENV_STATE=local
DB_NAME=testDB
DB_USER=hogehoge
DB_PASSWORD=password12345
DB_PORT=5432

 .env 파일은 어디든 위치해도 괜찮지만, 프로젝트 디렉토리 바로 아래에 둔 "mysite/.env"로 상정하고 계속해서 설명하도록 하겠다. 이로 인해 디렉토리의 기본 구성에서 다음과 같이 변경되었을 것이다.

mysite
|-- .env
|-- manage.py
|-- config (<- 설정 디렉토리)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
`-- static (<- static디렉토리)
    `-- admin
        |-- css
        |-- fonts
        |-- img
        `-- js

 

 

단계마다 설정 파일이 전환되도록 하기


 기본적인 상태에서는 설정 파일은 ''mysite/config/setting.py" 에 함께 정리되어 있다. 그러나 설정파일에 기재되어 있는 데이터베이스의 정보나 DEBUG 유무, 허가할 HOST 등은 단계가 개발중, staging중, production중인지에 따라 달라진다.

 그러므로 설정 파일을 단계에 따라 전환할 수 있도록 아래의 디렉토리 구성과 같이 단계마다 설정 파일을 (local.py, staging.py, production.py)작성한다. 또한 단계마다의 전환은 환경 변수 ENV_STATE(local, staging, production)으로 정의한다.

mysite
|-- config
|   |-- settings
|   |   |-- __init__.py
|   |   |-- base.py
|   |   |-- local.py
|   |   |-- production.py
|   |   `-- staging.py

 

「mysite/config/settings/base.py」를 작성하기

「mysite/config/settings/base.py」파일은 설정 파일의 베이스가 되는 파일이다. 기존의 setting.py의 내용을 그대로 복사/붙여넣기하여, 여기에 추가적으로 세큐리티를 강화하는 설정을 한다.

# 기본은 setting.py의 내용과 다른 것이 없다.
from dotenv import load_dotenv
load_dotenv('../.env')

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

DEBUG = False

# STATIC_URL와 STATIC_ROOT는 하위의 settings파일에 이관하므로 삭제
  • 기존의 setting.py 내용을 그대로 복사/붙여넣기하여 윗 부분만 위 코드로 바꾼다.
  • dotenv 라이브러리는 환경변수를 읽어들이기 위해 사용하는 라이브러리이다. 
  • 세큐리티를 강화하는 설정은 어떻게 하는 것인가?라는 의문이 들거라고 예상되지만 관련된 설명은 생략한다.
  • 앞에서 정의한 STATIC_ROOT는 환경에 의존하는 내용이므로 base.py에서는 삭제한다. 또한 STATIC_URL도 삭제한다.

 

「mysite/config/settings/local.py」를 작성하기

「mysite/config/settings/local.py」파일은 개발시의 로컬 환경 변수를 상정한 파일이다. 즉, Django의 runserver 커맨드로 로컬 서버를 실행하는 것을 상정한다는 것이다. 

import os
from .base import *

DEBUG = True

ALLOWED_HOSTS = ["*"]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
    }
}

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static/')]
  • runserver 커맨드로 실행한 로컬 서버를 상정하고 있으므로, DEBUG는 유효, ALLOWED_HOSTS는 모두 허가한다.
  • DATABASE의 설정은 sqlite를 사용하는 것을 가정하고 있지만 적절히 자신의 개발 환경에 맞게 변경하길 바란다. 또한, USER, PASSWORD에 설정하고 있는 os.environ['XXX']는 환경변수에서 읽어 들이는 것이 된다.
  • STATICFILES_DIRS는 runserver 커맨드 실행했을 때 정적 파일 자동 전송 기능을 읽어들어 들이는 디렉토리를 상정한다. "static 디렉토리를 생성한다"에서 static 디렉토리는 "mysite/static"에 이동시켰으므로 그것을 설정한다.

 

「mysite/config/settings/staging .py」을 작성한다.

「mysite/config/settings/staging .py」 파일은 staging 환경을 가정한 파일이다.

import os
from .base import *

DEBUG = True

ALLOWED_HOSTS = ["*"]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
        'HOST': os.environ['DB_NAME'],
        'PORT': os.environ['DB_PORT']
    }
}

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
  • staging이므로, DEBUG는 유효, ALLOWED_HOSTS는 모두 허가로 한다.
  • DATABASE는 PostgreSQL을 가정하고 있지만, 자신의 환경에 맞는 것으로 변경하길 바란다. 또한, os.environ['XXX']는 환경변수에서 읽어 들이게 된다.
  • STATIC_ROOT는 Web 서버쪽에서 static 파일을 돌려주기 때문에, "static 디렉토리를 생성하기"에서 설정한 static 디렉토리를 설정한다.

 

「mysite/config/settings/production .py」를 작성하기

「mysite/config/settings/production .py」파일은 production 환경을 가정한 파일이다.

import os
from .base import *

ALLOWED_HOSTS = ["*"]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
        'HOST': os.environ['DB_NAME'],
        'PORT': os.environ['DB_PORT']
    }
}

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
  • 기본은 staging환경과 동일하지만, DEBUG의 설정을 삭제하여 "base.py"의 설정 내용에 있는 DEBUG=False를 이용한다.

 

「 mysite/config/settings/__init__.py 」를 작성하기

python에서 패키지를 정의하기 위해 "__init__.py"를 생성한다. 파일 안은 비어있어도 괜찮다.

 

「mysite/config/settings.py」를 삭제하기

settings.py가 남아있으면 "/setting" 패키지와 충돌하기 때문에 삭제한다.

 

「mysite/manage.py」와「mysite/config/wsgi.py」를 편집하기

 어떤 설정파일이 사용되고 있는가는 환경 변수 DJANGO_SETTINGS_MODULE에 설정되어 있으며, 아래의 2개의 파일로 정의되어 있다.

  • runserver의 경우 manage.py
  • wsgi 경우의 경우 config/wsgi.py

 그러므로 "mysite/manage.py"와 "mysite/config/wsgi.py"를 아래와 같이 변경한다.

먼저 mysite/manage.py는 다음과 같이 작성한다.

from dotenv import load_dotenv

load_dotenv('../.env')

def main():
    # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
    envstate = os.getenv('ENV_STATE','local')
    if envstate=='production':
        # settings/production.py
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
    elif envstate=='staging':
        # settings/staging.py
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.staging')
    else:
        # settings/local.py
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')
    # ...
  • dotenv 라이브러리는 환경 변수를 읽어 들이기 위해서 사용하는 라이브러리이다
  • 환경변수 ENV_STATE는 local/staging/production의 단계를 정의한 것이다.

 그리고 mysite/config/wsgi.py는 다음과 같이 정의한다.

from dotenv import load_dotenv

load_dotenv('../.env')

envstate = os.getenv('ENV_STATE','local')
if envstate=='production':
    # settings/production.py
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
elif envstate=='staging':
    # settings/staging.py
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.staging')
else:
    # settings/local.py
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')

 이것으로 설정이 완료됐다. 지금까지의 설정에 의해 디렉토리 구성이 다음과 같이 변경됐다.

mysite
|-- .env
|-- manage.py
|-- config (<- 설정 파일)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings
|   |   |-- __init__.py
|   |   |-- base.py
|   |   |-- local.py
|   |   |-- production.py
|   |   `-- staging.py
|   |-- urls.py
|   `-- wsgi.py
`-- static (<- static디렉토리)
    `-- admin
        |-- css
        |-- fonts
        |-- img
        `-- js

 

 

 

어플리케이션마다 urls.py를 배치하기


 지금까지 프로젝트 전체에 관련된 설정을 해왔지만, 어플리케이션 배부에 대해서도 좋은 예시가 있다. urls.py는 "mysite/config/urls.py"와 프로젝트 전체에 관련된 config 디렉토리가 있지만, 여기에 집약해두면 어플리케이션이 늘 때마다 난잡해지므로 "urls.py"를 어플리케이션마다 분리하는 것이 좋다.

 먼저 어플리케이션은 아래의 커맨드로 작성이 가능하다.

$ python manage.py startapp apl

 "mysite/config/urls.py"에서 어플리케이션쪽의 urls.py에 includ함수를 위임한다.

# mysite/config/urls.py
from django.urls import include,path

urlpatterns = [
    path('', include('app.urls')),
    # ...
]
  • include로 지정하는 것은 어플리케이션쪽의 "urls.py"의 이름 공간을 지정하는 것이다.

어플리케이션 쪽의 urls.py 은 아래와 같이 설정한다.

# mysite/app/urls.py
from django.urls import path
from . import views

app_name = 'app'
urlpatterns = [
    # 여기에 어플리케이션마다의 설정을 기재한다.
    path('', views.IndexView.as_view(), name='index'),
]
  • app_name으로 어플리케이션의 이름공간을 지정한다.
  • urlpatterns내에 어플리케이션마다의 설정을 기재한다.
  • path()의서 번째 인수의 name 으로 지정한 문자열에 따라 "[이름공간]:[이름공간내의 name]"으로 URL를 역으로 당길 수 있게 된다. views.IndexView.as_view()는 참고하여 기재했을 뿐이므로 본인의 환경에 맞는 설정을 하길 ㅂ라ㅏㄴ다.

지금 까지의 설정에 의해 기본 디렉토리 구성에서 아래와 같은 구성으로 변경됐다.

mysite
|-- .env
|-- app (<- 어플리케이션 디렉토리)
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- migrations
|   |   `-- __init__.py
|   |-- models.py
|   |-- tests.py
|   |-- urls.py
|   `-- views.py
|-- manage.py
|-- config (<- 설정 디렉토리)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings
|   |   |-- __init__.py
|   |   |-- base.py
|   |   |-- local.py
|   |   |-- production.py
|   |   `-- staging.py
|   |-- urls.py
|   `-- wsgi.py
`-- static (<- static디렉토리)
    `-- admin
        |-- css
        |-- fonts
        |-- img
        `-- js

 

 

베이스 템플릿을 사용한다.


 Django의 템플릿을 사용하여 Web 어플리케이션의 공통된 정적인 부분을 정의하는 것이 가능하다. Web 어플리케이션은 HTTP로 기술되기 때문에, 헤더나 풋터, CSS, JS의 정의 등 어플리케이션에 관계없지 정적으로 정의하는 곳이 베이스 템플릿으로 정의하고, 어플리케이션 의존하는 곳은 다른 템플릿으로 정의하는 경우가 많다.

 그러므로, 다음과 같은 구성으로 한다.

`-- templates (<- templates디렉토리)
    |-- common
    |   `-- base.html
    `-- app
        `-- index.html
  • 템플릿 내의 기재 방식은 어플리케이션마다 다르므로 여기서는 언급하지 않도록 하겠다.
  • 「mysite/templates/common/」디렉토리에 어플리케이션 의존하지 않는 공통의 템플릿을 저장한다. 「mysite/templates/common/base.html」가 베이스 템플릿이다.
  • 어플리케이션마다 「mysite/templates/app/」디렉토리를 만들고, 어플리케이션 의존하는 템플릿을 거기에 저장한다.

 위에서 언급한 templates 디렉토리를 Django에 인식시키기 위해서는 "config/setting/base.py"의 TEMPLATES에 다음과 같이 설정한다.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'templates') # 추가
        ],
        # ...(생략)
    },
]

지금까지의 설정을 마치면 디렉토리 구성은 다음과 같이 변경된다.

mysite
|-- .env
|-- app (<- 어플리케이션 디렉토리)
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- migrations
|   |   `-- __init__.py
|   |-- models.py
|   |-- tests.py
|   |-- urls.py
|   `-- views.py
|-- manage.py
|-- config (<- 설정 디렉토리)
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings
|   |   |-- __init__.py
|   |   |-- base.py
|   |   |-- local.py
|   |   |-- production.py
|   |   `-- staging.py
|   |-- urls.py
|   `-- wsgi.py
|-- static (<- static디렉토리)
|   `-- admin
|       |-- css
|       |-- fonts
|       |-- img
|       `-- js
`-- templates (<- templates디렉토리)
    |-- common
    |   `-- base.html
    `-- app
        `-- index.html

참고자료

https://create-it-myself.com/know-how/best-practices-for-getting-started-with-django-development/#config

728x90