俺的Djangoベストプラクティス雛形
Djangoプロジェクト開発において、先人の知恵に大いに助けられた。
まとまりつつある俺的Djangoベストプラクティス雛形を構築するまでの手順を記録しておく。
2019年10月01日 最新版に更新
2020年09月01日 いまだ現役のプラクティス
2023年01月06日 Django 4.1対応版に更新
大まかなキーワード
- python環境
- pipenv
- Django環境
- 開発版/テスト版/デプロイ版の切り替え, 保守性, git管理
- アプリケーション開発
- パッケージ化
- デプロイ
- pipenvの準備, スクリプトで一括処理
python環境の準備 pipenv
python周りの管理はpipenvを使う。
バージョン管理、開発環境の分離、スクリプトの一元管理を叶えてくれる便利な奴。
ハングアップを疑うくらい遅い時がある。
pyenv インストール
$ sudo yum -y install gcc make zlib zlib-devel openssl openssl-devel
$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
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 startproject config .
testDjango
│── config
│ ├── __init__.py
│ ├── asgi.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 = Path(__file__).resolve().parent.parent
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent
各分割ファイルで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公式ドキュメントのチュートリアル。
仕様も言いたいこともアプリケーション毎にまとめたい気持ちも分かるけど、冗長すぎる。
名前空間つけるなら、元より一箇所にまとめればいい。
仕組みは静的ファイルも同じ。
デザイナーがいる場合は切り分けたstatic, templatesだけを渡せばいいから衛生的。
これを実現するための設定は以下のとおり。
ついでに、デプロイ時に静的ファイルを集めるディレクトリも作っておく。(staticsディレクトリ)
TEMPLATES = [
{
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
'DIRS': [os.path.join(BASE_DIR, 'templates')],
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
},
]
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'statics')]
# デプロイ用の設定
STATIC_ROOT = os.path.join(BASE_DIR, 'static') # STATICFILES_DIRSで指定されたディレクトリからSTATIC_ROOTにファイルを集めて
STATIC_URL = '/static/' # STATIC_URL上で配信する
testDjango
├── config
├── .venv
├── manage.py
├── static
├── statics
└── manage.py
git管理から除外するファイル
あとはアプリケーションを追加して開発を進めるだけ。
そこで、個人開発でもチーム開発でももはやgit管理は避けられない。
チームで共有すること、デプロイのためのファイル管理をすることを念頭に.gitignoreファイルを作成する。
といっても、除外するのは仮想python環境(.venv)と(もしあるなら)ローカル開発環境用settings系ファイルのみ。
理由は、それぞれ(人、マシンともに)が個別に準備すべきものだから。
.venv/
config/settings/local.py #あれば
あと、後からアプリケーションを追加するとできるmigrationsも追記すべきだと思う。
開発で試行錯誤したmigrationファイルがデプロイ環境に行って実行されてしまうのがよくないかもしれない。
migrationsを.gitignoreに入れてたら、makemigrations
とmigration
でアプリケーションを認識しなくなった。
開発環境で試行錯誤したとしても、成功までの履歴をなぞることで最終的にはきれいになるのがmigration機能のいいところなので、
その恩恵を賜る。
testDjango
├── .gitignore
├── config
├── .venv
├── manage.py
├── templates
├── static
├── statics
└── manage.py
アプリケーション models, viewsをパッケージ化する
アプリケーションを作成する。
(project)$ python manage.py startapp testApp
testDjango
├── .gitignore
├── config
├── .venv
├── templates
├── 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
├── templates
├── 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プロジェクトのはじめかた