dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

Heroku+DjangoでPostgresをローカルで使う時につまづいた点

メモ。

以前、Heroku の Getting Started with Python on Heroku チュートリアルをやった時に

一応、最後まで進めることはできたんだけど、heroku local でローカルでアプリを立ち上げた時 /db にアクセスするとエラーになってしまい、そこだけうまくいってなかった。

/db に対応するビューの処理は

def db(request):

    greeting = Greeting()
    greeting.save()

    greetings = Greeting.objects.all()

    return render(request, 'db.html', {'greetings': greetings})

なので、Postgres の設定まわりがうまくいってないんだろうとは思いつつ結局原因を突き止められずにいたが、その後ようやくわかったのでメモ。

TL;DR

clone してきたリポジトリの README に書いてある通り、下記のコマンドを実行すればよかった。
(実行には foreman という gem が必要になるので、
なければ gem install foreman でインストールする)

$ createdb python_getting_started
$ foreman run python manage.py migrate


前提:Postgres をインストールする

前提として、Postgres が正しくインストールされていることを確認する。
自分は公式ドキュメントに書かれている Postgres.app ではなく、Homebrew から入れてた。

$ brew install postgresql

「Homebrew Postgres」とかでググるとこのへんの記事が見つかるんだけど、
Homebrewを使ったPostgreSQLのインストール(Mac OS Lion) - Qiita
initdb などは brew install した時に自動的に設定されてるらしく不要だった。

ただ、Heroku 公式ドキュメントのここにある通り、DATABASE_URL を設定しておいた方が良い。
https://devcenter.heroku.com/articles/heroku-postgresql#local-setup

使用しているシェルに合わせて設定ファイルに以下を記載する。
(自分の場合は zsh なので .zshrc に記載)

export DATABASE_URL=postgres:///$(whoami)

Postgres.app を入れた場合はどういう設定が必要なのか未確認。
ただ、少なくとも psql などのコマンドへのパスが通ってないので設定する必要がある。

ref. Using Command Line Tools with Postgres.app

export PATH=$PATH:/Applications/Postgres.app/Contents/Versions/9.4/bin


最初のエラー

/db にアクセスした時に最初に表示されたエラーがこれ。

f:id:dackdive:20151101014947p:plain

OperationalError at /db

FATAL: database "python_getting_started" does not exist

エラーメッセージの通り、python_getting_started というデータベースが存在しないのが原因。
そのため、

$ createdb python_getting_started

でデータベースを作成してやれば OK。

では、python_getting_started というデータベース名はどこからきたのかというと
リポジトリ内に .env があり、そこに書かれていた。

DATABASE_URL=postgres:///python_getting_started

.env についてはチュートリアルDefine config vars にも出てきたが、
ここに書いた変数は heroku local した時に自動的に読み込まれ、環境変数にセットされる。


次のエラー

無事にデータベースを作成した後発生したのが次のエラー。

f:id:dackdive:20151101110602p:plain

ProgrammingError at /db

autocommit cannot be used inside a transaction

これはローカルだけでなくデプロイしたアプリでも最初に発生するエラーで、
テーブルが作られていないのが原因。
ref. https://devcenter.heroku.com/articles/getting-started-with-python#provision-a-database

Accessing it will yield an error though, because while the database is configured, the tables have not been created. Run the standard Django manage.py migrate to create the tables.

ということでチュートリアルでは

$ heroku run python manage.py migrate

を実行してうまくいったので、ローカルでも同じコマンドを実行する。

$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages, hello
  Apply all migrations: admin, contenttypes, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Creating table hello_greeting
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK

なんとなく全部うまくいってる感じがしたので再度 /db にアクセスするも、改善していない。

これは、python manage.py migrate だけだと .env で指定している DATABASE_URL を考慮せず、.zshrc で指定した DATABASE_URL が指すデータベースに対してテーブルを作成しているからのよう。

で、それを解決するには foreman という Ruby Gem が必要。
foreman run コマンドについてはちゃんと調べ切れていないが、foreman run に続いて実行したいコマンドを入力すると
.env を考慮した状態でコマンドが実行される。

$ foreman run python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages, hello
  Apply all migrations: admin, contenttypes, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Creating table hello_greeting
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK

この状態で /db にアクセスすると正しく動いてるのがわかる。

f:id:dackdive:20151101112656p:plain

よかった。


TODO

  • python manage.py migrate でやっていること

Django の話。たぶんこのへん。

Migrations | Django documentation | Django

  • foreman について

heroku local などのコマンドも、内部で実行しているのはこの foreman というコマンドらしい。

Running Apps Locally | Heroku Dev Center

なので、heroku local のかわりに

$ foreman start web

としてもアプリが起動する。

(2015/12/13追記)

こんなエラーも出た。