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

Django + Herokuでdj-ringo-tabetterを作った時の作業メモ

Django Python Heroku

前回Python + Django + HighchartsのHerokuアプリ作成を記事にしました。

今回は、これからのDjangoで開発するときの参考にするため、その時の流れや考えたことを残しておきます。

 

開発環境

 

仕様

アプリ構成

1つのDjangoプロジェクトを3つのアプリ(tweets, api, highcharts)で構成することにしました。それぞれのアプリの機能は、以下の通りです。

tweets
  • Twitter APIを利用してツイートを収集し、テーブルへ登録
    • Heroku Scheduleで一日一回実行するため、manage.pyで実行可能な形にする
  • プロジェクト全体で使うModelはこのアプリに置いた
api
  • tweetsアプリのModelからデータを読み出し、JSONとして返す
highcharts
  • apiアプリのJSONを元にHighchartsでグラフ表示

 

ディレクトリ構成

以下を参考して、主なディレクトリ構成は以下の通りとしました。
Django Best Practiceへの道 #1

<project_root>\
├── apps\
│    ├── api\
│    │    ├── urls.py
│    │    └── views.py
│    ├── highcharts\
│    │    ├── templates\
│    │    │    ├── base.jinja2
│    │    │    ├── total.jinja2
│    │    │    └── total_by_month.jinja2
│    │    ├── urls.py
│    │    └── views.py
│    └── tweets\
│          ├── management\commands\
│          │    └── gather_tweets.py
│          ├── migrations\
│          ├── urls.py
│          └── views.py
├── dj_ringo_tabetter\
│    ├── settings.py
│    ├── urls.py
│    └── wsgi.py
├── libs\
│    └── cultivars.py
├── static\
│    └── js\
│          ├── total.js
│          └── total_by_month.js
├── .env
├── Procfile
├── apples.yaml
├── manage.py
├── requirements.txt
└── runtime.txt

 

開発環境のセットアップ

PostgreSQLをセットアップ
インストール

現時点では、Heroku PostgresはPostgreSQL 9.4系のようでした。
Heroku Postgres | Heroku Dev Center

そこで、同じバージョンのPostgreSQLとして、Win x86-64版(postgresql-9.4.4-3-windows-x64.exe)をダウンロード・インストールします。

インストール時の設定は以下の通りとします。また、スタックビルダのインストールは行いません。

項目
Installation Directory C:\PostgreSQL\9.4
Data Directory C:\PostgreSQL\9.4\data
postgres user password postgres
port 5432
Locale C

 

PostgreSQLにデータベースを作成

以下のチュートリアルを参考に、pgAdmin3でデータベースの上で右クリックし、新しいデータベースを作成します。なお、開発環境だったので、UserやRoleの新規作成は特にしませんでした。
python - How to get postgresSQL to work on a windows 7 computer with django? - Stack Overflow

設定内容は以下の通りです。

項目
名前 ringo_tabetter_py
オーナー postgres

 

Djangoプロジェクト作成

IntelliJ IDEAにて、Djangoプロジェクトを作成します。自分の場合は作成時にエラーが出たため、以前の記事の方法で回避しました。
IntelliJ IDEAにて、virtualenv + Djangoプロジェクトの新規作成時にエラー - メモ的な思考的な

なお、virtualenv環境はプロジェクトと同じフォルダを指定し、作成時の設定は以下の通りとしました。

項目
Addtional Libraries and Frameworks Django
Template language Jinja2
Template folder template
Application name 空白のまま
Enable Django admin チェックする
Project name dj_ringo_tabetter

 

IntelliJ IDEAの設定変更

デフォルトでは、ファイルの文字コードwindows-13jとなっているので、utf-8へと変更しました。

  • File > Settings > Editor > File Encodings
    • 反映されない場合、IntelliJ IDEAを再起動

 

Djangoプロジェクトのデータベース設定変更

DjangoではSQLite3がデフォルトのようですが、今回はPostgreSQLを使うため、以下の設定変更を行います。

 

Pythonパッケージpsycopg2のインストール

PythonからPostgreSQLへと接続するため、以前の記事を参考に、psycopg2をvirtualenv環境にeasy_installでインストールしました。

(dj_ringo_tabetter) path\to\dj_ringo_tabetter\Scripts>easy_install path\to\psycopg2-2.6.1.win32-py3.4-pg9.4.4-release.exe
...
Finished processing dependencies for psycopg2==2.6.1

 

Pythonパッケージdj-database-urlのインストール

今回はHeroku postgresを使用するため、接続するのに便利なPythonパッケージdj-database-urlをインストールします。

 

データベースまわりの設定変更

まずは、接続に関する設定として、sqlite3用からPostgreSQL用に<project_root>\dj_ringo_tabetter\settings.pyを修正します。
Settings | Django documentation | Django

なお、開発環境のPostgreSQLへ接続してみたところ、引数engineを渡さないとエラーが発生しました。似たような事例は以下にありました。
postgresql - Django Heroku database error ENGINE not provided - Stack Overflow

そのため、両環境を識別するような環境変数を探したところ、環境変数DYNOを使うのが良さそうでした。

それを元にsettings.pyのDATABASESに関する設定を変更します。

 
次に、日付項目をタイムゾーン付で保存するために、USE_TZ = Trueとなっていることを確認します。
Time zones | Django documentation | Django

 

Djangoタイムゾーンと日本語化の設定

dj_ringo_tabetter\settings.pyを修正します。

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'

 
ここまでのアプリ全体は以下の通りです。
thinkAmi/dj_ringo_tabetter at 7ba235e805ca7243d58b2d44503da7d23cf70b5c · GitHub

 

アプリ共通ライブラリの作成

今回はアプリ共通ライブラリとして、

  • 品種などが書かれているYAMLファイルを読み込む
  • 品種を渡すとYAMLファイルに書かれている色を返す

という機能を持ったcultivars.pyを作成します。

パスは<project_root>\libs\cultivars.pyとしました。

 

PythonパッケージPyYAMLのインストール

YAMLファイルを読み込むために、PyYAMLをインストールします。

 

品種などを含むYAMLファイルを作成

以下の内容を持つ、<project_root>\apples.yamlファイルを作成します。

- Name:  あいかの香り
  Color: Crimson
- Name:  秋映
  Color: DarkRed
# 以下略

 

cultivars.pyの実装

YAMLファイルの読み方はこんな感じで、settings.pyに記載されているBASE_PATHを利用してYAMLのフルパスを取得します。

ここまでのアプリは、以下のコミットになります。
thinkAmi/dj_ringo_tabetter at 2667c2af0e0c4cdcde83ce634c264354521974c1 · GitHub

 

tweetsアプリの作成

3つのアプリのうち、まずはツイートを収集するtweetsアプリから作成します。

また、このアプリの中にModelを作成し、データベースへと保存できるようにします。

 

Pythonパッケージdjango_jinjaのインストール

manage.pyを使用してtweetsアプリを作成しようと、 Tools > Run manage.py Task...を実行しようとしたところ、

ImportError: No module named 'django_jinja'

というエラーが発生しました。IntelliJ IDEAでの作成時にINSTALLED_APPSに記載されるものの、パッケージdjango_jinjaはインストールされないようでした。

Django1.8からはJinja2をテンプレートエンジンとして使えるようですが、今回はdjango_jinjaパッケージをインストールすることにします。
Django1.8からテンプレートエンジンにJinja2を選べるようになっていた #djangoja - Qiita

 

manage.pyによる、tweetsアプリの生成

再度、Tools > Run manage.py Task...を実行したところ、manage.pyウィンドウが表示されます。

アプリを作成するために、

manage.py@dj_ringo_tabetter > startapp tweets ./apps/tweets

を実行したところ、以下のエラーが発生しました。

CommandError: Destination directory 'path\to\project\apps\tweets' does not exist, please create it first.

 
エラーメッセージや以下の情報を見ると、事前にフォルダを作成しておく必要がありそうでした。
django-admin and manage.py | Django documentation | Django

そのため、<project_root>\apps\tweetsフォルダを作成してから、再実行したところ、tweetsアプリが生成されました。

 

settings.pyへアプリの登録

Djangoにアプリを認識させるために、<project_root>\dj_ringo_tabetter\settings.pyを修正します。

なお、アプリ名にはフォルダ名(apps)も忘れずに付けます。

INSTALLED_APPS = (
# 略
'django_jinja',
'apps.tweets', #追加
)

 

Modelの追加

<project_root>\apps\tweets\models.pyTweetsLastSearchという、こんな感じの2つのModelを追加します。

 

manage.pyによるマイグレーションファイルの作成

Modelを追加したため、manage.pyにてマイグレーションファイルを作成します。INSTALLED_APPSと異なり、appsの指定は不要です。

manage.py@dj_ringo_tabetter > makemigrations tweets
...
Migrations for 'tweets':
0001_initial.py:
- Create model LastSearches
- Create model Tweets

 

manage.pyによるマイグレーション

マイグレーションファイルができたところで、引き続きmanage.pyを使ってPostgreSQLマイグレーションを行います。

manage.py@dj_ringo_tabetter > migrate
...
  Applying sessions.0001_initial... OK
  Applying tweets.0001_initial... OK

 

manage.pyで実行可能なコマンドの作成

Twitter APIを使ってツイートを収集する機能については、manage.pyで実行できるようにしたいことから、以下を参考にして作成します。

今回はgather_tweetsというコマンドを作成したいことから、<project_root>\apps\tweets\management\commands\gather_tweets.pyというファイルを作成します。

 

必要なPythonパッケージのインストール
tweepy

PythonTwitter APIを扱うパッケージを探したところ、以下の機能を持つtweepyがあったため、インストールします。

tweepyの使い方については以下が参考になりました。
Tweepyの2.3.0が出ました - Shogo's Blog

 

django-dotenv

Twitter APIのApplication authでは、Consumer KeyとConsumer Secretを使います。

ただ、ソースコード上にハードコードしたり、バージョン管理ファイルとして保存したくないことから、.envファイルを使うことにします。

Djangoの場合、django-dotenvを使って.envファイルから環境変数へロードします。
jpadilla/django-dotenv · GitHub

django-dotenvを使うために、<project_root>\dj_ringo_tabetter\settings.pyに以下の行を追記します。

import dotenv
dotenv.read_dotenv(os.path.join(BASE_DIR, '.env'))

 

pytz

TwitterのStatusのcreated_atはタイムゾーンを持っていません。ただ、そのままでは扱いづらいことから、タイムゾーンを持たせて日本時間へと変換できるようにします。

そのため、pytzをインストールし、タイムゾーンを持てるようにします。

 

gather_tweets.pyの実装

実装の大枠は以下の通りとなります。

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def handle(self, *args, **options):
        # ....

Twitterのパラメータについては以下が参考になりました。

また、Modelを経由したデータベースの更新では、以下を参考にtransaction.atomic()を使ったトランザクションを実行しています。 Database transactions | Django documentation | Django

 
全体のソースコードこんな感じになります。

 

manage.pyによる、gather_tweetsのテスト実行

ここまでで、Twitter APIで取得したツイートをPostgreSQLに登録する機能ができました。

そのため、manage.pyにて実際の動作をテストします。

manage.py@dj_ringo_tabetter > gather_tweets
...
# 成功した場合、以下の表示あり
commit
finish

 
なお、データベースの中身をすべてクリアしたい場合、flushコマンドを実行します。

manage.py@dj_ringo_tabetter > flush
...
Are you sure you want to do this?

    Type 'yes' to continue, or 'no' to cancel:  yes
Installed 0 object(s) from 0 fixture(s)
Installed 0 object(s) from 0 fixture(s)
Installed 0 object(s) from 0 fixture(s)

 
tweetsアプリ作成後のコミットは以下になります。
thinkAmi/dj_ringo_tabetter at 6f441f39bbc9f41ba186b206d839905b0fe62b74 · GitHub

 

apiアプリの作成

一般的なapiアプリのため、メモは簡単にしておきます。

 

apiアプリの実装
  • 事前準備
    • <project_root>\apps\apiフォルダを作成
  • アプリの作成
    • manage.pyにて、startapp api ./apps/apiを実行
  • Djangoへアプリの登録
    • <project_root>\ringo-tabetter-py\settings.pyのINSTALLED_APPSへapps.apiを追加
  • URLの設定
    • <project_root>\apps\api\urls.pyを実装
    • <project_root>\ringo-tabetter-py\urls.pyへ追加
  • Viewの作成
    • <project_root>\apps\api\views.pyを実装

 

apiアプリのテスト

IntelliJ IDEAでプロジェクトを起動した後、

へとアクセスして両方ともJSONが返ってくればOKです。

 
apiアプリ作成後のコミットは以下になります。
thinkAmi/dj_ringo_tabetter at c31a1f62f9008be71437751aece4401efc15a944 · GitHub

 

highchartsアプリの作成

highchartsアプリの実装
  • 事前準備
    • <project_root>\apps\highchartsフォルダを作成
  • アプリの作成
    • manage.pyにて、startapp highcharts ./apps/highchartsを実行
  • Djangoへアプリの登録
    • <project_root>\ringo-tabetter-py\settings.pyのINSTALLED_APPSへapps.highchartsを追加
  • URLの設定
    • <project_root>\apps\highcharts\urls.pyを実装
    • <project_root>\ringo-tabetter-py\urls.pyへ追加
  • Viewの作成
    • <project_root>\apps\highcharts\views.pyを実装

 

JavaScriptファイルなどの静的ファイルを用意
静的ファイル用のフォルダを用意

CSSは特に使用しませんので、<project_root>\static\jsというJavaScript用のフォルダだけ準備します。

 

静的ファイル用フォルダとして認識するよう設定

<project_root>\ringo-tabetter-py\settings.pyに、以下の設定を追加します。

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

 

JavaScriptファイルを用意

<project_root>\static\jsの下に、以下の2ファイルを用意します。

  • total.js
  • total_by_month.js

 

Jinja2テンプレートの作成

以下を参考に、django-jinjaパッケージを使って、Jinja2テンプレートをviewとして返すようにします。

 

DEFAULT_JINJA2_TEMPLATE_EXTENSIONを設定

IntelliJ IDEAの場合、拡張子.jinja2としておくとJinja2テンプレートとして認識してくれます。

そのため、<project_root>\ringo-tabetter-py\settings.pyに、以下の設定を追加します。

DEFAULT_JINJA2_TEMPLATE_EXTENSION = '.jinja2'

 

テンプレートエンジンの変更

DjangoのテンプレートエンジンからJinja2へと変更します。

そのため、<project_root>\ringo-tabetter-py\settings.pyに、以下の設定を修正します。

TEMPLATES = [
    {
        "BACKEND": "django_jinja.backend.Jinja2",
        "APP_DIRS": True,
        "OPTIONS": {
            "match_extension": ".jinja2",
        }
    },
]

 

Jinja2テンプレートの作成

今回作成するJinja2テンプレートはhighchartsアプリ内でのみ使用するため、<project_root>\apps\highcharts\templates\に作成します。

今回はテンプレート継承なども行うため、

  • base.jinja2
  • total.jinja2
  • total_by_month.jinja2

の3ファイルを作成します。

 

highchartsアプリのテスト

Djangoアプリを起動し、

にてグラフが表示されればOKです。

 
highchartsアプリ作成後のアプリ全体は、以下の通りです。
thinkAmi/dj_ringo_tabetter at 1a53828ed18b06aca14d4ee8f6c14232bd4778b1 · GitHub

 

Herokuへデプロイするための準備

以下を参考に、Herokuへデプロイするための準備を行います。
Rubyに負けるな!HerokuでPython(Django)動かす方法 - Qiita

 

heroku-toolbeltのインストール

デプロイするためにheroku-toolbeltをインストールします。
Heroku Toolbelt

 

dj-toolbeltの検討

現時点では、dj-toolbeltをWindowsへインストールするのは厳しそうです。
windows とherokuとpython3.4(64bit)でdjango-toolbeltインストールする時に地獄を見ないために… - Qiita

そのため、dj-toolbeltの内容を確認しますが、

パッケージ 今回のプロジェクトの状況
Django 既にインストール済
Gunicorn 後述のようにして自分で入れる
dj-database-url 既にインストール済
dj-static 今回必要なさそう

だったため、今回はdj-toolbelt自体のインストールは不要そうでした。

 

procfileの作成

Herokuではgunicorndで動作させるため、<project_root>\Procfileを以下の内容で作成します。

web: gunicorn --env DJANGO_SETTINGS_MODULE=dj_ringo_tabetter.settings dj_ringo_tabetter.wsgi --log-file -

 

staticファイルの配信のため、PythonパッケージWhitenoiseをインストール

デフォルトのままのDjangoの場合、Heroku上ではviewは配信してくれますが、total.jsなどの静的ファイルは配信してくれませんでした。

そのため、Heroku公式でも推奨されているPythonパッケージWhitenoiseをインストール・設定をします。
Django and Static Assets | Heroku Dev Center

Whitenoiseの設定として、

  • <project_root>\dj_ringo_tabetter\settings.pyへの設定
    • STATIC_ROOTSTATICFILES_STORAGEの設定を、上記のHerokuの記載通りに追加
  • <project_root>\dj_ringo_tabetter\wsgi.pyへの設定

を行います。

 

gunicornを使うための設定

以下を参考に、Herokuでgunicornで動作させる設定を行います。

 
ただ、開発環境のWindowsでは動作しません。

そのため、<project_root>\dj_ringo_tabetter\settings.pyINSTALLED_APPSへ以下を追加します。なお、INSTALLED_APPSはtupleなので末尾カンマを忘れずに付けます。

# 開発環境がWindowsでgunicornが使えないことから、
# herokuのみgunicornを使えるように設定
if 'DYNO' in os.environ:
    INSTALLED_APPS += ('gunicorn',)

 
ちなみに、将来的にはWindowsでの動作も期待が持てそうです。
python - Does Gunicorn run on Windows - Stack Overflow

 

runtime.txtの用意

Heroku上でのPythonのランタイムを指定するために用意します。

python-3.4.3

 

requirement.txtの用意

コマンドプロンプトでactivateし、pip freezeを実行します。

(dj_ringo_tabetter) path\to\dj_ringo_tabetter>pip freeze > requirements.txt

その後、作成されたrequirements.txtに不足している、gunicorn==19.3.0を追加します。

 
ここまでの設定が完了後、再度 http://localhost:8000/hc/total などにアクセスして、問題なく動作することを確認します。

Herokuで使うための設定をした後のコミットは以下になります。
thinkAmi/dj_ringo_tabetter at e72e28464d3c62569e55981626e5c1e0d376048b · GitHub

 

Herokuへデプロイ

.gitignoreの作成

https://www.gitignore.io/api/python,django,intellij,pycharm にて作成したものに、今回のアプリで不要なものを追加します。

.env
Scripts/
pyvenv.cfg

 

gitでコミット

git init ~ git commitまで行います。

 

Herokuアプリの作成

heroku creategit push heroku masterを実行します。

path\to\dj_ringo_tabetter>heroku create dj-ringo-tabetter
...
Git remote heroku added

path\to\dj_ringo_tabetter>git push heroku master
...
To https://git.heroku.com/dj-ringo-tabetter.git
 * [new branch]      master -> master

 

環境変数の作成

.evnに記載していた環境変数を、heroku上に作成します。
Configuration and Config Vars | Heroku Dev Center

> heroku config:set USER_ID=<user_id> TWITTER_CONSUMER_KEY=<your_key> TWITTER_CONSUMER_SECRET=<your_secret>

 

Python packageの確認

heroku run pip listを実行し、Python packageがインストールされているかを確認します。

 

DBのマイグレーション
> heroku run python manage.py migrate...  Applying tweets.0001_initial... OK

 

動作確認
データ投入

本来ならHeroku Schedulerで実行するものですが、動作確認のために実行します。

> heroku run python manage.py gather_tweets

 

pgAdminIII で確認

該当するデータベースの、スキーマの下にDjangoで使っているテーブルが居るので、そのテーブルを確認してデータが入っていることを確認します。
HerokuのPostgreSQLをpgAdminから見る方法 - MD Blog

 

herokuアプリを起動して、動作を確認

heroku openで開いて、

  • http://<your_application_name>/api/v1/total
  • http://<your_application_name>/hc/total

などで動作を確認します。

 

データ削除

先ほど投入したものは不要なデータなので、削除しておきます。

> heroku run python manage.py flush

 

スケジュールアドオンの追加

ツイートを収集する部分は一日一回動作するようスケジュールします。今回もHerokuのアドオン、Heroku Schedulerを使います。

 
設定した内容は以下の通りです。なお、最初はEvery 10 minutesにしてみて、動作するかを確認しておきます。

項目
$ python manage.py gather_tweets
FREQUENCY Daily
Next DUE 18:00 (JSTで3:00)

 
Heroku Postgresのデータ移行やアプリ名の変更については、前回の記事通りですので、省略します。

 
以上で、dj-ringo-tabetterを作った時の流れは終わりです。

 

ソースコード

再掲となりますが、最新のソースコードは以下となります。
thinkAmi/dj_ringo_tabetter · GitHub