はじめに
うごきましておめでとう、nikkieです。
Python製Webアプリケーションフレームワークの1つ、Django(ジャンゴ)。
Djangoの入門チュートリアルの1つ、Django Girls Tutorialでは、Djangoで作ったアプリをPythonAnywhereにデプロイできます。
チュートリアルから離れ設定ファイルを分割したアプリをPythonAnywhereにデプロイするのにハマり、対処するという経験を年末にしました。
目次
- はじめに
- 目次
- そもそもことの始まりは…
- まとめ
- Django Girls Tutorial 「デプロイ!」
- レガシースクリプトpa_autoconfigure_django.pyがやっていること
- 仮想環境を再構築して、設定ファイルを分割したDjangoアプリをPythonAnywhereで動かすまで
- 終わりに
そもそもことの始まりは…
かつて(2019年に)PythonAnywhereにデプロイしたアプリがありました1。
https://ftnext.pythonanywhere.com/
"dangermouse"というシステムイメージを使っていたのですが、2022年後半に「dangermouseはend-of-lifeなのでシステムイメージを変えてほしい」というメールが来ました。
ヘルプページに沿ってシステムイメージを変える2と、ページは表示されなくなりました(何かしたら壊れた!)。
エラーメッセージ「Py_Initialize: Unable to get the locale encoding」で調べると、Forumの中に同じメッセージについての記載が見つかります。
You need to rebuild your web app's virtual env now.
(意訳) Webアプリの仮想環境を再ビルドする必要があります。
そこでPythonAnywhereでの仮想環境再構築に取り組みました。
そのときの私はまだ知らなかったのです、設定ファイルを分割したDjangoアプリをPythonAnywhereでデプロイするのが大変だということを…🙀
まとめ
設定ファイルを分割したDjangoアプリをPythonAnywhereでデプロイするノウハウは以下に素晴らしいまとめがあります。
django-tutorial-workshop-memo.md · GitHub
このGistは必見です。
このGistを参照しながら、自分のアプリがデプロイできない事象に対処しました。
この後に続くのは、その闘いの記録です。
闘いを終えての私の感覚としては、設定ファイルを分割したら、Django Girls Tutorial記載のpa_autoconfigure_django.py
でのPythonAnywhereへのデプロイはやらない方がよいというものです。
開発しやすくするために設定ファイルを分けているのに、ここまでデプロイしづらいというのは、デメリットがメリットを大きく上回ってしまっていて、ぶっちゃけ割に合わないと感じます。
設定ファイルを分割したDjangoアプリをデプロイするなら
- PythonAnywhereは使わないのがオススメ
- PythonAnywhereでもレガシーな
pa_autoconfigure_django.py
の代わりに新しいpa
コマンドを使えばカイゼンしているかもしれません(どなたかチャレンジしてみて😉)
アウトプットする行動と矛盾してますが、このアウトプットが活用されない状態があるべきと考えます。
Django Girls Tutorial 「デプロイ!」
かつてDjango Girls Tutorialに取り組んだときは「よく分からない(ブラックボックス)けど、できた!🙌」という感覚でしたが、今回ハマったことでブラックボックスの蓋を開けました。
「PythonAnywhere でサイトを設定する」では2つのことをやっています。
- ライブラリ
pythonanywhere
のインストール(pip3.6 install --user pythonanywhere
) pythonanywhere
が提供するpa_autoconfigure_django.py
の実行(pa_autoconfigure_django.py --python=3.6 https://github.com/<your-github-username>/my-first-blog.git
)- この中で仮想環境も構築されます
ライブラリpythonanywhere
PythonAnywhereのヘルパーツールがライブラリとしてPyPIで配布されているんです!
ソースコードはこちら(このあと中を覗きます):
「Usage」を見ると、2つの方法で使えるとあります。
- コマンドラインインターフェース(
pa
コマンド) - Legacy scripts
Django Girls TutorialはLegacy scripts(の1つのpa_autoconfigure_django.py
)を使っています。
レガシースクリプトpa_autoconfigure_django.py
がやっていること
https://github.com/pythonanywhere/helper_scripts/blob/v0.10.3/scripts/pa_autoconfigure_django.py
モジュールレベルのdocstringで以下のように説明されています:
- リポジトリのダウンロード
- virtualenvを作り、Djangoをインストール
requirements.txt
が見つかったらインストールに使われる- PythonAnywhereは標準ライブラリのvenvではなくてvirtualenvのようです
- APIを介してwebappを作る
- (これはPythonAnywhereの話でしょうか?)
- DjangoのWSGI設定ファイルを作る
- 静的ファイルの設定を追加する
実はこの内容はDjango Girls Tutorialにも書いてあるんですよね(「実行しているところを見れば、何をしているのかわかるでしょう。」)。
ソースコードでは、main関数にてDjangoProject
のメソッドを呼び出しています。
# https://github.com/pythonanywhere/helper_scripts/blob/v0.10.3/scripts/pa_autoconfigure_django.py#L27-L44 def main(repo_url, branch, domain, python_version, nuke): domain = ensure_domain(domain) project = DjangoProject(domain, python_version) project.sanity_checks(nuke=nuke) project.download_repo(repo_url, nuke=nuke), project.ensure_branch(branch), project.create_virtualenv(nuke=nuke) project.create_webapp(nuke=nuke) project.add_static_file_mappings() project.find_django_files() project.update_wsgi_file() project.update_settings_file() project.run_collectstatic() project.run_migrate() project.webapp.reload() print(snakesay(f'All done! Your site is now live at https://{domain}')) print() project.start_bash()
仮想環境を再構築して、設定ファイルを分割したDjangoアプリをPythonAnywhereで動かすまで
設定ファイルを分割したDjangoアプリ
以下のように分割しています3。
settings/ ├── __init__.py ├── base.py ├── local.py └── production.py
先日のまとめに即して言うと
pa_autoconfigure_django.py
が通るまで(仮想環境再構築)
pythonanywhere
のソースコードを編集してから、以下のコマンドを実行しました:
$ DJANGO_SETTINGS_MODULE=mysite.settings.production pa_autoconfigure_django.py --python=3.9 https://github.com/ftnext/djangogirls-nextstep.git --nuke --branch=feature/deploy-pythonanywhere
pythonanywhere
のソースコード編集
pa_autoconfigure_django.py
から呼び出されるproject.find_django_files()
なのですが、settings.py
以外の名前の設定ファイルを見つけられません。
設定ファイルを分割したDjangoアプリをPythonAnywhereでデプロイするには、インストールしたpythonanywhere
のソースコードの編集が必要になります(必見Gistの24-4-1)。
PythonAnywhereのシェル(ブラウザから使える)でvimを使って編集しました。
https://github.com/pythonanywhere/helper_scripts/blob/v0.10.3/pythonanywhere/django_project.py#L82
- self.settings_path = next(self.project_path.glob('**/settings.py')) + self.settings_path = next(self.project_path.glob('**/settings/production.py'))
ここはハードコードなので、インストールされたライブラリに手を入れるしかなさそうです。
引数で指定できると非常に使いやすくなるのですが…6(pa
コマンドだと改善されているのかな?)
本番設定の編集(settings/production.py
)
import os from dotenv import load_dotenv load_dotenv() SECRET_KEY = os.getenv('SECRET_KEY')
pa_autoconfigure_django.py
の引数
pa_autoconfigure_django.py --help
で確認できます
--nuke
:仮想環境を再構築するために必要です- 仮想環境の名前は
<your-pythonanywhere-domain>.pythonanywhere.com
(例:ftnext.pythonanywhere.com
) --nuke
を指定すると、すでにできている仮想環境があっても新しく作り直します
- 仮想環境の名前は
--branch
:デプロイ時点では https://github.com/ftnext/djangogirls-nextstep に複数のブランチがあったので指定する必要がありました
環境変数DJANGO_SETTINGS_MODULE
の指定
pa_autoconfigure_django.py
の処理が進むと、project.run_collectstatic()
やproject.run_migrate()
でpython manage.py collectstatic
やpython manage.py migrate
相当のことが実施されます(subprocess
を使った実装)。
collectstatic
やmigrate
のときに、本番環境用の設定ファイルを使うように指定する必要があります。
そこでDJANGO_SETTINGS_MODULE
を使いました。
manage.py
を編集してデフォルトで本番設定を参照するようにしているやり方もあります(必見Gistの19-2)--settings
引数はpa_autoconfigure_django.py
がサポートしていなさそうでした(manage.py
に渡せたらもっと便利ですね)
pa_autoconfigure_django.py
が通った後
ページを表示するにはもう少し手順が必要です。
pa_autoconfigure_django.py
で作られたWSGIファイルのDJANGO_SETTINGS_MODULE
の編集
必見Gistではテンプレートの元を直していますが(24-4-3)、ソースコードの修正は最小限にしたかったので、テンプレートからコピーされたファイルを修正して対処しました。
project.update_wsgi_file()
で/var/www/<your-pythonanywhere-domain>_pythonanywhere_com_wsgi.py
ができています。
このファイルで設定モジュールのimportができていません。
編集して対処しました(編集した後はWebコンソールの「Web」タブからReloadが必要です)。
# Set environment variable to tell django where your settings.py is - os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.settings' + os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings.production'
WSGIファイルでsys.path
を編集
/var/www/<your-pythonanywhere-domain>_pythonanywhere_com_wsgi.py
はまだ修正が必要です。
# djangoはprojectやappが入ったディレクトリの名前 # ref: https://github.com/ftnext/djangogirls-nextstep/tree/master/django settings_path = '/home/ftnext/ftnext.pythonanywhere.com/django' sys.path.insert(0, settings_path)
django
ディレクトリ以下にあるプロジェクトmysite
やアプリmysite
・accounts
がimportできるように変更しました。
環境変数を読み込むためのWSGIファイルの編集
これが/var/www/<your-pythonanywhere-domain>_pythonanywhere_com_wsgi.py
の最後の修正です。
SECRET_KEY
を環境変数から読み込みます。
python-dotenv
を使っているのは、開発時に以下のドキュメントを参照したためと思われます。
Webアプリケーションを動かすため、<your-pythonanywhere-domain>.pythonanywhere.com
7の直下に.env
ファイルを置き、そこにSECRET_KEY
の値を書きます(環境変数として設定)。
この.env
を読み込むために、WSGIファイルに追加します。
from dotenv import load_dotenv project_folder = os.path.expanduser('~/<your-pythonanywhere-domain>.pythonanywhere.com') load_dotenv(os.path.join(project_folder, '.env'))
ドキュメントではbashでも環境変数を読み込むために(virtualenvの)postactivateスクリプトの設定も案内されています(今回はスキップ)。
PythonAnywhereの「Web」タブで「Static files」を変更
デフォルト値から変える必要がありました。
これをやらないとCSSが当たらないですし、アップロードした画像ファイルも参照できません。
- URL /static/ を
/home/<your-pythonanywhere-domain>/<your-pythonanywhere-domain>.pythonanywhere.com/django/static
に変更 - URL /media/ を
/home/<your-pythonanywhere-domain>/<your-pythonanywhere-domain>.pythonanywhere.com/django/media
に変更
これが必要な理由はproject.update_settings_file()
によってsettings/production.py
の末尾に以下が追記されているからです。
# BASE_DIR は /home/<your-pythonanywhere-domain>/<your-pythonanywhere-domain>.pythonanywhere.com/django MEDIA_URL = '/media/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
update_settings_file
の実装は、言ってみればsed
コマンドでテキストファイルを編集するような実装です8。
テキストファイルの操作となっているので、共通設定からのimportは無視されます。
これによりPythonAnywhereのルールが強制適用された形になっていて、「Static files」の設定値を変えて対処したということです。
WSGIファイルの最終形
/var/www/<your-pythonanywhere-domain>_pythonanywhere_com_wsgi.py
は最終的には以下のようになっています。
終わりに
PythonAnywhereでシステムイメージを変えた後、仮想環境を再構築し、アプリが動くようになりました。
設定ファイルを分割したことで、PythonAnywhereでの仮想環境の再構築には
といった操作が必要となっていました。
必見Gistがなければ再構築はもっとずっと難航したと思われます。
このアウトプットには大変感謝しています。
django-tutorial-workshop-memo.md · GitHub
pa_autoconfigure_django.py
、これはDjango Girls Tutorialの範囲では便利ですが、Tutorialから離れるとむしろデメリットに感じてしまいます。
一番つらかったのは、ライブラリのユーザがソースコードを編集しないといけないことです(ハードコードの代償)。
また、コケるたびに最初から再実行したのですが、途中のステップから再開できるような引数があると非常に助かるなあと思いました。
「正しい使い方を簡単に、誤った使い方を困難に」で言うと、Tutorialから離れたとき、誤った使い方が容易にできてしまい、正しい使い方はこの1エントリ分くらい大変なので、このスクリプトには伸びしろしかありません!
私の中でこのスクリプトをカイゼンすることはあまり優先度が高くないのですが、もしアイデアが浮かんだ方がいたらぜひプルリクエストを送ってみてください!
もし次にやる機会があるならcookiecutter-django
で作ったアプリケーションを以下に沿ってデプロイしたいですね。
pa_autoconfigure_django.py
こわい…
PythonAnywhereに置いているDjangoアプリ、古いシステムイメージから変更したことでエラーになっており対処。
— nikkie にっきー (@ftnext) 2022年12月29日
settings\.pyを分割したケースなのですが、以下のメモに大いに助けられました。大感謝https://t.co/LQBYNVW2TL
settings\.pyの分割って結構やられるのに対して、対応はかなり大変でした…
- このアプリは2019年のDjangoCongress JPのトークの準備で作成したものです。登壇報告 | #djangocongress にて、Django Girls Tutorialの次に取り組みたいトピックについて共有してきました - nikkie-ftnextの日記↩
- しばらく変えなくていいように最新のhaggisイメージに変えました。システムイメージについては Batteries included: PythonAnywhere で知ることができます↩
- https://github.com/ftnext/djangogirls-nextstep/tree/72b05b415e782247d9d8ca1e5f8a68eb73c9e1ba/django/mysite/settings↩
- django-environではないのも辛かった要因の1つなのかもしれません↩
- PyCon JPスタッフ向けのレビューアプリ開発は2020年からなので、Djangoに関しては練習中(伸びしろ豊富)の時期です↩
- 同様の指摘がissueでもされていそうです。Handle custom django settings. · Issue #32 · pythonanywhere/helper_scripts · GitHub↩
-
URLで指定したリポジトリは
<your-pythonanywhere-domain>.pythonanywhere.com
という名前でgit clone
されています↩ - https://github.com/pythonanywhere/helper_scripts/blob/v0.10.3/pythonanywhere/django_project.py#L91-L117↩