Djangoの続き。
とりあえずデータベース
大体のWebサービスにおいてはデータベースは必要になると思います。Djangoにおいてはデフォルトでsqliteがデータベースとして設定されており、マイグレートするだけでdb.sqlite3
というデータベースファイルが出来上がり、これに読み書きすることでサービスを実現できます。
しかし、これをこのままHerokuにデプロイしても上手くいきません。Heroku環境にもそのままdb.sqlite3
が移植され、データベースとしてはそれを参照することになりますが、これはGitに紐付かないデータなので、せっかくデータを書き込んでも定期的に初期状態に戻ってしまうのです。
悲しい。
これを防ぐためには、Gitファイルの外にあるデータベース、というかHerokuに備え付けられているPostgresに接続する必要があります。しかし、繰り返しになりますがDjangoの初期設定はそうなってはいません。かといって、ローカル環境ではHerokuのPostgressは使えないので、同一の設定で2つの環境を行き来することは原理的に不可能ということになります。
ローカルとリモートの切り分け
しかし、ある工夫、というかアドホックな修正を行えば、この問題を解決することができます。ローカル環境だけにしか存在しないファイルを設定し、そのファイルがある時だけif文でコードを変えればよいのです。これは、.gitignore
を使ってリモートに上げないファイルを選択することにより実現することができます。具体的には、ローカルフォルダに例えばlocal.py
というファイルを置いておきます。これは中身が空でもいいですが、アクセストークン等の重要情報を秘匿するのにちょうどよいので、そのような情報を乗せると便利でしょう。逆に、リモート環境ではlocal.py
にある情報を取り出せないので、環境変数でアクセストークンを指定する必要がありますが、後に載せるコードではそれも実装します。
フォルダ構成について想像しづらい人もいるかもしれないので実例を。
これは実際に私が管理しているリポジトリですが、local.py
とそれに関わるフォルダ構成の様子です。また、db.sqlite3
とlocal.py
のファイル名が灰色になっていることに気づくと思いますが、これは.gitignore
の対象になっていることを示しています。当然ながら、リンク先のgithubにはこの2つのファイルは存在しません。
そして、settings.py
では以下のようなif文で、諸々のパラメータを分岐させます。
import dj_database_url
if os.path.exists('local.py'):
DEBUG = True
ALLOWED_HOSTS = ['*']
from local import SK
SECRET_KEY = SK
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
else:
DEBUG = False
ALLOWED_HOSTS = ['*']
SECRET_KEY = os.environ['SK']
db_from_env = dj_database_url.config()
DATABASES = {
'default': dj_database_url.config()
}
本件とは関係ないですが、ローカルと本番でDEBUGを切り替えるのも、ついでにやってもいいでしょう。
これで、いい感じにローカルとリモートを切り分けた上でスムーズに開発していくことができます。
静的ファイル
画像ファイルやCSS等は、静的ファイルとしてフォルダ内の特別な場所に置く必要があります。この辺りは、settings.py
で
STATIC_URL = '/static/'
あたりに指定されているのですが、実は全然設定が足りていません。
まず、ローカル環境で静的ファイルを置く場所を指定しなければいけません。これは例えば、
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
という変数を足せば大丈夫です。この例だと、プロジェクトファイル直下にstatic
というフォルダを作れば、それが参照場所となります。
COLLECTSTATIC
しかし、このままだと実はデプロイできません。Herokuの初期設定だと、デプロイ時にCOLLECTSTATIC
という儀式を経る設定になっていますが、Djangoの初期設定ではそれに失敗するからです。静的ファイルを一切使わないならば、そもそもCOLLECTSTATIC
をしないという選択も取れますが、静的ファイルを扱うつもりならばこの問題に正面から向き合う必要があります。COLLECTSTATICについてはこのページが超わかりやすいのでおすすめです。
なお、COLLECTSTATICをオフする手段については、Heroku環境内(heroku run bash
で入れます)で
heroku config:set DISABLE_COLLECTSTATIC=1
というコマンドを打つことにより設定できますし、WebブラウザのGUIでも設定することができます。
さて、COLLECTSTATIC
を成功させるには、settings.py
を以下のように設定する必要があります。なおここら辺の設定は公式ドキュメントに詳しく載っています。
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
これで無事にデプロイできるはずです。
しかしまだ静的ファイルは使えません。
Whitenoise
リモート環境では、ファイル名そのままではパスの指定に失敗します。なぜなのか。
結論から言うと、リモート環境の静的ファイルは、ファイル名の途中に謎のハッシュ値みたいのがついています。
https://hoge.herokuapp.com/static/fuga.28487bbbe8b5.png
こんな感じで。ではどうすればいいかというと、Whitenoise
というソフトがこれをいい感じに処理してくれるらしいです。上のドキュメントに載っているので思考停止で載せましょう。
pip install whitenoise
した後で(requirements.txt
に書き加えておくのも注意!)、settings.py
に以下を追加。
MIDDLEWARE_CLASSES = (
'whitenoise.middleware.WhiteNoiseMiddleware',
...
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
これでやっとうまくいきます。テンプレートhtml内において{% static 'hoge.png' %}
といった形で表記された部分は、ローカル環境ではstatic/hoge.png
、リモート環境ではstatic/hoge.**********.png
といい感じにレンダーされてくれます。
これでやっとリモート環境で静的ファイルを利用できます。書き足すことが必要な設定多すぎん?