Flaskチュートリアル + herokuにデプロイ

# 概要

Flaskに入門したのでherokuデプロイまでの手順を記して置こうと思います。

フェーズ１: Hello, World!アプリ
フェーズ2: sqliteを用いたflaskアプリ
フェーズ3: postgres + herokuにデプロイ

の順番でやっていこうと思います。

# 環境
- macOS High Sierra 10.13.6
- python 3.6.5

# 最終的なディレクトリ構成

最終的なディレクトリ構成はこのようになります。

```
flask-demo-app/
├── run.py
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── views.py
│   ├── models.py
│   ├── test.db
│   └── templates/
├── Procfile
├── requirements.txt
└── venv/
```

# プロジェクトの作成

```shell
mkdir flask-demo-app
cd flask-demo-app
git init
```

## python 仮想環境 構築
```
python3 -m venv venv
source venv/bin/activate
```
venvに関してはこちらがわかりやすいです。
https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e

`source venv/bin/activate`するとこんな感じになっているはずです。

```
(venv)$ 
```

venv環境はgitで管理する必要はないので、
.gitignoreに`venv`を追加しておきます。

```:.gitignore
venv
```

# flaskのインストール
pipをアップグレードしてflaskをインストールします。

```
(venv)$ pip install --upgrade pip
(venv)$ pip install flask
```

# フォルダの作成

```
(venv)$ mkdir main
```

今回は、このmainディレクトリにflaskの主要ファイルたちを置いていきます。

# フェーズ1
まずは最小構成で`Hello, World!`をしていきます。

## run.py
アプリケーションをデバッグモードで起動するだけのプログラムです。

```
(venv)$ vim run.py
```

```python3:run.py
from main import app

if __name__ == '__main__':
    app.run(debug=True)
```

## main/\__init__.py

```
(venv)$ vim main/__init__.py
```

```python3:__init__.py
from flask import Flask

app = Flask(__name__)

import main.views
```

`main package`がimportされた際にこのスクリプトが実行されます。

\__init__.pyの働きに関してはこちらがわかりやすかったです。
https://www.kangetsu121.work/entry/2018/09/16/004008


# main/views.py

```
(venv)$ vim main/views.py
```

```python3:views.py
import flask 
from main import app

@app.route('/')
def show_entries():
    return 'Hello, World!'
```

# 実行

```
(venv)$ python run.py
```
http://localhost:5000 へアクセス

`Hello, World!`が見えたらOK!


# フェーズ2

sqliteデータベースを使用するアプリケーションにしてみます。
Entryというクラスを作成し、ブログ(title, text)を投稿できるようにしていきます。

## flask_sqlalchemyのインストール

SQLAlchemyというORMを使用します。
flask用のものをインストール。

```
(venv)$ pip install flask_sqlalchemy 
```

## main/\__init__.py を編集

```
(venv)$ vim main/__init__.py
```

```python3:__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy # 追加

app = Flask(__name__)
app.config.from_object('main.config') # 追加

db = SQLAlchemy(app) # 追加

import main.views
```


## main/config.py

```
(venv)$ vim main/config.py
```

```python3:config.py
import os

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or "sqlite:///test.db"
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY="secret key"
```
or 演算子を使用して、os.environ.get('DATABASE_URL')が空文字だった場合,右辺を代入するようにしています。
`os.environ.get('DATABASE_URL')`はherokuのpostgresで使用し、
右辺はsqliteのデータベースです。
こうすることでherokuの環境とローカルの環境で書き換える必要がありません。

`SECRET_KEY`はセッション情報を暗号化するためのキーです
実際に運用する場合には、SECRET_KEYは必ず変更してください。

## main/models.py

```python3:models.py
from main import db
from flask_sqlalchemy import SQLAlchemy


class Entry(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.Text)
    text = db.Column(db.Text)

    def __repr__(self):
        return "<Entry id={} title={!r}>".format(self.id, self.title)


def init():
    db.create_all()

```

## データベース作成

```
(venv)$ python -c "import main.models; main.models.init()"
```

SQLALCHEMY_DATABASE_URI の場所に作成されます。
`main/test.db`が存在することを確認してください。

### データ挿入
初期データをいくつかいれておきます。
インタラクティブシェルから挿入することができます。

```python
(venv)$ python
>>> from main.models import Entry
>>> from main import db
>>> entry1 = Entry(title='title1', text='text1')
>>> db.session.add(entry1)
>>> db.session.commit()
>>> 
>>> entry2 = Entry(title='title2', text='text2')
>>> db.session.add(entry2)
>>> db.sesion.commit()
>>> db.session.commit()
>>> entries = Entry.query.all()
>>> entries
[<Entry id=1 title='title1'>, <Entry id=2 title='title2'>]
>>> exit()
```
2つのデータを挿入しました。


## main/views.py を編集
```main/views.py
import flask 
from main import app
from main.models import Entry # 追加

@app.route('/')
def show_entries():
    entries = Entry.query.all() # 追加
    return flask.render_template('entries.html', entries=entries) # 変更
```

`flask.render_template`でhtmlテンプレートが使用できます。
htmlテンプレートは`templates`に置きます。

## htmlテンプレートの準備

```
(venv)$ mkdir main/templates
(venv)$ vim main/templates/entries.html 
```

```html:entries.html
<!DOCTYPE html>
<html>
  <head>
    <title>flask demo app</title>
  </head>
  <body>
    <ul class="entries">
    {% for entry in entries %}
      <li>{{entry.title}} / {{entry.text}}</li>
    {% endfor %}
    </ul>
  </body>
</html>
```

## 実行

```
(venv)$ python run.py
```
http://localhost:5000 へアクセス

インタラクティブシェルで挿入したデータが見れたらOK!


## フォームの追加
データが表示出来たので、htmlにフォームを設置し、
そこからDBへ追加できるようにします。

### views.py の編集

```
(venv)$ vim main/views.py
```

```python3:views.py
import flask 
from main import app, db # 変更
from main.models import Entry

@app.route('/')
def show_entries():
    entries = Entry.query.all()
    return flask.render_template('entries.html', entries=entries)

# add_entry 関数の追加
@app.route('/add', methods=['POST'])
def add_entry():
    entry = Entry(
        title = flask.request.form['title'],
        text = flask.request.form['text']
    )
    db.session.add(entry)
    db.session.commit()
    return flask.redirect(flask.url_for('show_entries'))

```

## htmlテンプレート の編集

```
(venv)$ vim main/templates/entries.html 
```

```html:entries.html
<!DOCTYPE html>
<html>
  <head>
    <title>flask demo app</title>
  </head>
  <body>
    <!-- 追加 -->
    <form action="{{ url_for('add_entry') }}" method=post>
      <input type="text" name="title">
      <input type="text" name="text">
      <input type="submit">
    </form>
    <!-- ここまで -->
    <ul class="entries">
    {% for entry in entries %}
      <li>{{entry.title}} / {{entry.text}}</li>
    {% endfor %}
    </ul>
  </body>
</html>
```

## 実行

```
(venv)$ python run.py
```
http://localhost:5000 へアクセス

postしたデータがviewとしてみれたらOK!


# フェーズ3
ここからはherokuへデプロイのための準備をしていく。

## heroku用準備
### gunicorn のインストール

```
(venv)$ pip install gunicorn
```

### psycopg2 のインストール
`python`から`PostgreSQL`を使用するためにドライバの`psycopg2`をインストールします。

```
(venv)$ pip install psycopg2-binary  
```

`pip install psycopg2` を実行するとうまくいかなかったため、
こちらを実行しています。


### Procfile の準備
heroku 上で実行するコマンドを記載します。

```
(venv)$ vim Procfile
```

```:Procfile
web: gunicorn run:app --log-file -
```


### requirements.txt の準備
`pip freeze` でインストール済パッケージ一覧を表示することが出来ます。
`heroku` は`requirements.txt`を頼りに必要なライブラリをインストールするので準備してあげます。

```
(venv)$ pip freeze > requirements.txt
```

## herokuアプリの作成

ログイン

```
(venv)$ heroku login
```

アプリの作成

```
(venv)$ heroku create flask-demo-app
```

こういうエラーが出たら名前がすでに使われているので、
違う名前でアプリを作成してください。

```shell
Creating ⬢ flask-demo-app... !
 ▸    Name flask-demo-app is already taken
```

## postgresql アドオンの追加
```
(venv)$ heroku addons:create heroku-postgresql:hobby-dev
```

## heroku コミット

まずはgitにコミットして

```
(venv)$ git add .
(venv)$ git commit
```

herokuにpush!

```
(venv)$ git push heroku master
```

## heroku にデータベース作成

```
(venv)$ heroku run python -c "import main.models; main.models.init()" 
(venv)$ heroku open
```

heroku で確認できたらOKです！

---

python 仮想環境を抜けるには下のコマンドです。

```
(venv)$ deactivate   
```
## 終わりに

heroku デプロイまで出来ましたでしょうか..？
flaskですが、ディレクトリ構成がかなり自由で、どういうファイル配置にしていくか悩みましたが、一旦これに落ち着きました。

なにか間違っているところやこうしたほうがいい！というところがあれば教えてくださると幸いです。



# 参考
- [venv: Python 仮想環境管理](https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e)
- [Python \__init__.py の機能について](https://www.kangetsu121.work/entry/2018/09/16/004008)
- [Python の or と and 演算子の罠](https://qiita.com/keisuke-nakata/items/e0598b2c13807f102469)
- [FlaskとPostgreSQLでウェブアプリを作ってHerokuで無料で運用する](https://qiita.com/croquette0212/items/9b4dc5377e7d6f292671#heroku%E3%81%ABdeploy%E3%81%99%E3%82%8B)
- https://methane.github.io/flask-handson/flaskr.html#step-0