俺的Djangoベストプラクティス雛形

Djangoプロジェクト開発において、先人の知恵に大いに助けられた。
まとまりつつある俺的Djangoベストプラクティス雛形を構築するまでの手順を記録しておく。

2019年10月1日 最新版に更新

大まかなキーワード

  • python環境
  • pipenv
  • Django環境
  • 開発版/テスト版/デプロイ版の切り替え, 保守性, git管理
  • アプリケーション開発
  • パッケージ化
  • デプロイ
  • pipenvの準備, スクリプトで一括処理

python環境の準備 pipenv

python周りの管理はpipenvを使う。
バージョン管理、開発環境の分離、スクリプトの一元管理を叶えてくれる便利な奴。
ハングアップを疑うくらい遅い時がある。

pipenv インストール

pipenvはbrewやpipでインストールできる。
便利なので仮想環境でなくグローバルにインストールしていいと思う。

$ pip install pipenv

諸設定を環境変数に登録する。

$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bash_profile # パス通し
$ echo 'export PIPENV_VENV_IN_PROJECT=1' >> ~/.bash_profile # スクリプトでvenvを使いやすくするために、venvをプロジェクトディレクトリ内に作ってもらう設定

pipenvの初期設定

pipenvでpythonを始める。

$ pyenv install --list # インストール可能なバージョンの一覧
$ pipenv --python <バージョン>

始めて起動するとPipfileとPipfile.lockができる。
Pipfileのscriptsブロックに任意のスクリプトを記述できる。

[scripts]
init = "pipenv install"
init_dev = "pipenv install --dev"
server = "python manage.py runserver 0.0.0.0:8000"
$ pipenv run スクリプト名

pipenvでpython

次に、仮想環境のON/OFF。

$ pipenv shell # 仮想環境のON 未作成であればこの時に作成
$ exit # 仮想環境のOFF Ctrl + dも可

スクリプトにこれを書くと、仮想環境に入って止まるので、
スクリプト内で仮想環境に入りたい場合は次のように書く

source .venv/bin/activate # 仮想環境のON
deactivate # 仮想環境のOFF

pipenvでパッケージ管理

基本的な使い方はpipと同じ。
ただし、開発環境にだけインストールしたいものは分離できる。

(project)$ pipenv install パッケージ名
(project)$ pipenv --dev install パッケージ名 # 開発環境only

別環境でパッケージを復元するとき、開発用を含めるかどうか選択できる。

(project)$ pipenv install
(project)$ pipenv install --dev # 開発環境用パッケージもインストールする

仮想python環境でDjangoプロジェクトを立ち上げる

(project)$ pipenv install django
(project)$ django-admin.py startproject config .
testDjango
│── config
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── venv
└── manage.py

settings.pyを実行環境に応じて変更できるようにする

セキュリティやパフォーマンスの観点から、開発版、テスト版、デプロイ版それぞれでALLOWED_HOSTS、データベースの設定、DEBUGなどを切り替えたほうが良い。
環境に適した設定で動作させるために、settings.pyの内容を分割する。
実行時にどの設定ファイルを読み込むかを指定できる。
ファイル名や分割の粒度は任意。

(project)$ mkdir config/settings
(project)$ mv config/settings.py config/settings/base.py # 既存のsettings.pyを分割後の汎用ファイルとする
(project)$ vi config/settings/base.py # 汎用設定ファイルから分割する項目を削除する
(project)$ vi config/settings/develop.py # 開発版だけに適用したい設定を記述
(project)$ vi config/settings/test.py # テスト版だけに適用したい設定を記述
(project)$ vi config/settings/deploy.py # デプロイ版だけに適用したい設定を記述
(project)$ touch config/settings/__init__.py # config/settingsディレクトリをパッケージ化
testDjango
├── config
│   ├── __init__.py
│   ├── settings
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── deploy.py
│   │   ├── develop.py
│   │   └── test.py
│   ├── urls.py
│   └── wsgi.py
├── venv
└── manage.py

設定ファイルのディレクトリ階層が一つ深くなったのでbase.pyを修正する。

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

各分割ファイルでbase.pyを読み込むことで、それぞれが一つの完結したsettings.py系ファイルになる。

from .base import * # base.pyを読み込む 

# base.pyにない分を補完

DEBUG = False

ALLOWED_HOSTS = ['公開するホスト']

実行時にどの設定ファイルを読み込むか指定すれば環境ごとに異なる設定で実行できる。
デプロイ版ではuwsgiやgunicornで設定ファイルを指定すればいい。

(project)$ python manage.py runserver 0.0.0.0:8000 --settings=config.settings.develop

ファイルの指定はパスをドット区切りで記述する。

Pipfileに書いておくと楽できる。

[scripts]
server = "python manage.py runserver 0.0.0.0:8000 --settings=config.settings.develop"
(project)$ pipenv run server

ちなみに、設定ファイル無指定の場合のデフォルト値はmanage.pyに記述してある。
ここを書き換えておくと便利だけど、実行時の指定をサボらないなら必須ではない。

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.develop')

static, templateをプロジェクトディレクトリ直下に配置する

djangoで静的ファイルやテンプレートを呼び出すと、settings系で設定されたBASE_DIR直下にあるstatic,templatesディレクトリとアプリケーションディレクトリ以下にあるstatic,templatesディレクトリを検索し、最初にヒットしたものを使用する。
複数アプリケーションを開発しているとき、静的ファイルやテンプレートの名前が被ると意図しないものを使用してしまう可能性がある。
それを避けるために名前空間を与えておき、指定するデータを確実に特定できるようにするのを通例とするのがDjango公式ドキュメントのチュートリアル。
Untitled.png

仕様も言いたいこともアプリケーション毎にまとめたい気持ちも分かるけど、冗長すぎる。
名前空間つけるなら、元より一箇所にまとめればいい。
Untitled (2).png

仕組みは静的ファイルも同じ。
デザイナーがいる場合は切り分けたstatic, templatesだけを渡せばいいから衛生的。

これを実現するための設定は以下のとおり。
ついでに、デプロイ時に静的ファイルを集めるディレクトリも作っておく。(staticsディレクトリ)

TEMPLATES = [
    {
        〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    },
]

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# デプロイ用の設定
STATIC_ROOT = os.path.join(BASE_DIR, 'statics') # STATICFILES_DIRSで指定されたディレクトリからSTATIC_ROOTにファイルを集めて
STATIC_URL = '/static/' # STATIC_URL上で配信する
testDjango
├── config
├── venv
├── manage.py
├── static
├── statics
└── manage.py

git管理から除外するファイル

あとはアプリケーションを追加して開発を進めるだけ。
そこで、個人開発でもチーム開発でももはやgit管理は避けられない。
チームで共有すること、デプロイのためのファイル管理をすることを念頭に.gitignoreファイルを作成する。
といっても、除外するのは仮想python環境(.venv)と(もしあるなら)ローカル開発環境用settings系ファイルのみ。
理由は、それぞれ(人、マシンともに)が個別に準備すべきものだから。

/env/
/config/settings/local.py #あれば

あと、後からアプリケーションを追加するとできるmigrationsも追記すべきだと思う。
開発で試行錯誤したmigrationファイルがデプロイ環境に行って実行されてしまうのがよくないかもしれない。

testDjango
├── .gitignore
├── config
├── venv
├── manage.py
├── static
├── statics
└── manage.py

アプリケーション models, viewsをパッケージ化する

アプリケーションを作成する。

(project)$ python manage.py startapp testApp
testDjango
├── .gitignore
├── config
├── venv
├── static
├── statics
├── manage.py
└── testApp
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
    ├── tests.py
    └── views.py

ここで気に入らないのがmodels.pyとviews.py。
保守性を高めるためにも、modelごと、viewごとにファイルを分割したい。
そこで、それぞれをパッケージ化する。

(project)$ mkdir testApp/models
(project)$ touch testApp/models/__init__.py
(project)$ rm testApp/models.py
(project)$ mkdir testApp/views
(project)$ touch testApp/views/__init__.py
(project)$ rm testApp/views.py
testDjango
├── .gitignore
├── config
├── venv
├── static
├── statics
├── manage.py
└── testApp
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models
    │   └── __init__.py
    ├── tests.py
    └── views
        └── __init__.py

以降、modelはmodelsディレクトリ、viewはviewsディレクトリに追加すればいい。
追加時、__init__.pyにimportすること。
(import必須?詳しい方教えてください。)

デプロイ

いつもの構成はnginx <-> gunicorn <-> Django
デプロイ環境の構築・設定はこちら、Djangoアプリケーション with gunicornのサービス化はこちらを参考にする。
やるべきことは以下4点。

  • git clone(初回以降fetch, mergeでもいいと思う)
  • pipenvでライブラリパッケージのインストール
  • 静的ファイル配信の準備
  • デプロイ環境用DBのmigration

定型なので、スクリプトにしちゃいましょう。

#!/usr/bin/bash

git fetch
git merge origin/master
source .venv/bin/activate
pipenv install
python manage.py collectstatic --noinput
python manage.py makemigrations
python manage.py migrate
deactivate
systemctl restart testDjango

デプロイ環境を更新したい場合は、このスクリプトを実行するだけでOK。

参考

現場で使える Django の教科書《基礎編》
現場で使える Django の教科書《実践編》
EC2にNginx + Gunicorn + SupervisorでDjangoアプリケーションをデプロイする
gunicorn + Flask + nginx + Systemdで動かしてみた
2018年のPythonプロジェクトのはじめかた