BLOGサブスレッドの日常

2016.05.23

Django settings.py

tama

#ハタチ でーす。
主たる仕事が開発から離れてしまったので日々仕事をしていてもなかなか技術ネタが貯まらないことに気付いてしまったわけで。
前に予告した (Djangoの) settings.py の環境依存差分をバージョン管理から外す仕組み というネタに挑戦してみたいと思います。

Django settings.py とは

プロジェクトディレクトリに作られる、プロジェクト全体の設定ファイルです。リファレンス

from django.conf import settings

で参照します。

環境ごとに異なる設定

ふだんの開発は手元のパソコンで runserver を使っています。ローカル環境と呼びます。
当然本番環境というものがあります。実際に運用されているサイトです。
この中間に本番環境に近い動作確認・テスト用の環境もあったりします。開発環境とか検証環境とか言います。
あとお客さんによっては本番相当のステージング環境もあったりします。受入環境と呼んだりもします。

これらが、みんながみんな、全く同じ設定ではありません。
典型的にはDBサーバが違ったり、
使うS3バケットが違ったり、
自身にアクセスするURL(ドメイン名やパス)が違ったり。
それをね、うまいこと settings.py で吸収したいのです。
具体的には django.conf.settings で吸収したいのです。

対応策v1

最初はこんなフラグを用意していました。(わりと何年もこれで運用してた)

# 開発環境フラグ
DEVENV = (os.name == 'nt') or (hasattr(os, 'uname') and os.uname()[0] == 'Darwin')

WindowsやOSXだったらローカル環境と見なしてフラグを立てる。
適宜 settings.DEVENV を参照してローカル環境と本番環境で違うコード(設定)を用いる。

うーん。

これだけでは本番環境と検証環境、ステージング環境の切り分けができません。
それに担当者ごとに設定が異なる可能性があるローカル環境に関する設定なんかリポジトリにコミットしたくありません。

対応策v2

settings.py の末尾に

try:
    from settings_local import *
except ImportError:
    pass

というコードを書き足すことにしました。

環境固有の設定は settings_local.py に書こう、という戦略です。
settings.py に書いたデフォルトの設定に対して settings_local.py で上書きをします。
settings_local.py は .gitignore 対象としてリポジトリにはコミットしません。

これはなかなかよいかなと思ったのですが(1〜2年使ってた)、
settings_local.py の中で settings.py の設定値を参照できないという問題が発生しました。

対応策v3

site-packages/django/conf/__init__.py を見てみると、
settings は巡り巡って DJANGO_SETTINGS_MODULE という環境変数の名前をモジュール名と見立てて
importlib.import_module() しています。DJANGO_SETTINGS_MODULE は manage.py とかで

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

みたくしてるやつですね。
つまり、プロジェクトディレクトリから settings という名前でモジュール読み込みできればいいわけで、
settings.py でなくても、 settings/__init__.py でもよいのです。

そこでこんなディレクトリ構成にしてみました

  • manage.py
  • プロジェクト名/
    • __init__.py
    • urls.py
    • wsgi.py
    • settings/
      • __init__.py
      • default.py ← もともと settings.py だったもの。base.py などの名前でもOK
      • local.py ← これは .gitignore 対象としてリポジトリにはコミットしない

settings/__init__.py はこんな中身です。

try:
    from .local import *
except ImportError:
    from .default import *

settings/local.py があればそちらを。なければ settings/default.py を使います。

キモとなるのは settings/local.py で、先頭で

from .default import *

としておきます。
こうすることで settings/default.py を最初に読み込んで、それを参照した上で
環境固有の設定を上書きすることができます。
しかもその設定はリポジトリにコミットされない!環境固有!やっふー!

参考までに、とあるプロジェクトの本番環境の settings/local.py はこんな感じになっています。
デフォルト(リポジトリにあるファイル)はAS ISでローカル環境で動かせるようになっています。

# -*- coding:utf-8 -*-
# ローカル設定

from .base import *

# S3にファイル保存する(デフォルトはローカルにファイル保存)
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
AWS_STORAGE_BUCKET_NAME = 'hoge'
AWS_ACCESS_KEY_ID = '********************'
AWS_SECRET_ACCESS_KEY = '****************************************'
FILE_PATH = 'files/'

# RDSを使う(デフォルトはSQLite3)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'hage',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'hoge.hage.ap-northeast-1.rds.amazonaws.com',
    }
}

ベストプラクティスは?

いまのところ対応策v3でうまいこと運用できているので不満はありませんが
こういう場合のベストプラクティスってどんななんでしょうね…?

ご意見があればぜひ @tamacjp まで教えてください><

この記事を書いた人

tama