読者です 読者をやめる 読者になる 読者になる

Djangoのsettingsの分割と構造化について

Djangoの設定ファイルであるsettingsの分割、構造化について。
開発、ステージング、本番、テストなど環境ごとにsettingsの内容は変える必要があるので、settings.pyを分割したり、共通部分をまとめたりする。
settingsの分割についてはdjangoprojectのWikiにいろいろな方法が書かれている。
SplitSettings – Django
また先日、VさんがDjangoのsettingsについて書いていた。
Django トラノマキ · GitHub
いろんな方法があって、それぞれメリットデメリットはあるので、自分にあったものを使えばいいと思う。銀の弾丸はない。

2013/10時点の暫定案

いくつかのプロジェクトで試してる方法はあるので、まとめておく。
方針としては以下のとおり。

  • 個人用の設定はリポジトリにコミットしない
  • --settings指定を省略した場合(デフォルトの設定)に本番環境やステージングなどに接続しないようにする
  • 1つのディレクトリにたくさんのファイルを並べるようなことはしない
  • 共通部分はまとめる
基本的な構造

startprojectで作成したsettings.pyファイルのある位置に、settingsディレクトリを作成してbase.pyを置く。
各環境の設定は、サブディレクトリを作成して、base.pyを作成し、それぞれの環境ごとの設定ファイルを作成する(admin, app, batchなど)。
base.pyでは、1つ上の階層のbase.pyの内容をすべて読み込む。developならdevelop系の共通部分をこのファイルに記述する。

# myproject1/settings/develop/base.py
from myproject1.settings.base import *

# 以下差分設定など
INSTALLED_APPS += (
    'django_extensions',
    'debug_toolbar',
)

環境ごとの設定ファイルでは、同じ階層のbase.pyの内容をすべて読み込む。

# myproject1/settings/develop/mysql.py
from myproject1.settings.develop.base import *

# 以下差分設定など
DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'

開発者はdevelop(またはdev)のlocal.py.tmplをlocal.pyにコピーして利用する。sqlite/mysql等、ユニットテストを高速に実行するために使い分けたりする。(tmplの階層はもう一つ上でもいいかもしれない)
ディレクトリツリーはこんな感じ。

├── manage.py
└── myproject1
    ├── __init__.py
    ├── settings
    │   ├── __init__.py
    │   ├── base.py
    │   ├── develop
    │   │   ├── __init__.py
    │   │   ├── base.py
    │   │   ├── local.py.tmpl
    │   │   ├── mysql.py
    │   │   └── sqlite.py
    │   ├──production 
    │   │   ├── __init__.py
    │   │   ├── base.py
    │   │   ├── admin.py
    │   │   ├── app.py
    │   │   └── batch.py
    │   ├── staging
    │   │   ├── __init__.py
    │   │   ├── base.py
    │   │   ├── admin.py
    │   │   ├── app.py
    │   │   └── batch.py
    │   └── test
    │       ├── __init__.py
    │       ├── base.py
    │       ├── jenkins.py
    │       ├── mysql.py
    │       └── sqlite.py
    ├── urls.py
    └── wsgi.py

ユニットテストの実行も、sqliteのインメモリ(一番速い)とか、mysql(本番に近い)とか、CI用とかで分けたりしている。分割はできるだけ避けたほうがいいんだけど、なかなか難しい。
この場合、runserverで実行する場合は以下のようなコマンドとなる。

$ python manage.py runserver --settings=myproject1.settings.develop.mysql

長い。 "myproject1.settings" の部分はみんな同じなので省略したいが現状ではショートカットのスクリプトを用意して対応している。

部分的に外部ファイルから設定を読み込む場合

本番環境のデータベース接続の設定をコミットしたくないという場合がある。
パスワード等のコピーをむやみにコピーさせたくないというポリシーでそうなることもあるし、運用担当者が部分的に設定を作成するのでそうなることもある。
幸い、Djangoの設定ファイルはPythonコードなので、jsonモジュールやConfigParserモジュールを使って、汎用的なフォーマットのファイルを読み込んで使用するのは簡単。

import json
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATABASES = json.load(open(os.path.join(BASE_DIR, 'databases.json')))

こういう工夫は適宜入れる。
ただ、バージョン管理外のファイルというのは失われてしまいがちなので、databases.json.tmplなどの設定例を用意したり、ドキュメントを書いておいたほうが良さそう。

環境ごとに変更する項目について

環境ごとに変更しそうな項目について箇条書き。

  • DATABASES(データベース設定は環境ごとに変わる)
  • SECRET_KEY(セッションのキー作成に使う文字列なので、環境ごとに変えておいてよし)
  • INSTALLED_APPS(開発時のみ有効にするアプリなどがある)
  • CACHES(本番だとRedisとかだけど、開発時はPythonのオンメモリキャッシュとか)

以前、newauthのサンプルを書いた際に作成したプロジェクトのsettingsが構造化されているので、それを参照されたし。
tokibito / sample_nullpobug / source / django / myproject1 / myproject1 / settings / develop / base.py — Bitbucket

おわりに

より良い方法を模索し続けてるので、ここから変わっていく可能性は当然ある。
ここで書いた方法も、いきなりこうしたわけじゃなくて、いろんな試行錯誤の結果によるもの。そこそこ大きい規模じゃないと、settingsをここまで分割するのは不要だと思う。
「こういう方法でうまくいってるぜー」的なのがあれば、皆さんも記事を書いて公開してくれるとうれしい。