flaskで簡単なwebアプリ作成

## やるサイト
http://www.kzfmix.com/flaski/

## virtualenvでflask用の環境用意

virtualenvのインストールと使い方は[ここ](http://qiita.com/yshi12/items/37985e9e5cbc2008f35e)参照。

```
$ mkvirtualenv flaski
$ workon flaski
```

## インストール
```
(flaski):flask_test hoge$ pip install flask
(flaski):flask_test hoge$ pip install sqlalchemy
```

## ローカルでwebサーバー立てて、ブラウザにHello Worldを表示。
```{app.py}
from flask import Flask
app = Flask(__name__)

# http://127.0.0.1:5000をルートとして、("")の中でアクセスポイント指定
# @app.route("hoge")などで指定すると、http://127.0.0.1:5000/hogeでの動作を記述できる。
@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    # webサーバー立ち上げ
    app.run()
```

を作り、```python app.py```で実行すると、以下が標準出力に表示される。

```
(flaski)hoge:src hoge$ python app.py 
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```

ブラウザで```http://127.0.0.1:5000/```を打ち込むと以下が表示される。

<img width="365" alt="スクリーンショット 2016-05-10 21.35.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/40159/fb790acf-f177-bea1-3bf2-bc0bffd77c15.png">

## SQLAlchemyを用いたSQLiteの操作
### ファイル構成
```
├── flaski
│   ├── __init__.py
│   ├── database.py
│   └── models.py
├── do_create.py
└── do_insert.py
```

### 各スクリプトと動作確認
```{database.py}
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import os

# wiki.dbという名前のdbファイルの作成を指定
databese_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'wiki.db')
# sqlliteを指定して、テーブルのcreateを指定。
engine = create_engine('sqlite:///' + databese_file, convert_unicode=True)
# bindにテーブルcreateを指定。
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
# declarative_baseのインスタンス生成。
Base = declarative_base()
# 実行用のセッション格納？
Base.query = db_session.query_property()


def init_db():
    import flaski.models
    # Baseの内容でcreate実行？
    Base.metadata.create_all(bind=engine)
```

```{models.py}
# -*- coding: utf-8 -*-

from sqlalchemy import Column, Integer, String, Text, DateTime
from flaski.database import Base
from datetime import datetime


class WikiContent(Base):
    __tablename__ = 'wikicontents'                  # テーブル名
    id = Column(Integer, primary_key=True)          # カラム１(id)
    title = Column(String(128), unique=True)        # カラム２(title)
    body = Column(Text)                             # カラム3(body)
    date = Column(DateTime, default=datetime.now()) # カラム４(date) デフォルト現在日時を設定

    def __init__(self, title=None, body=None, date=None):
        self.title = title
        self.body = body
        self.date = date

    def __repr__(self):
        return '<Title %r>' % (self.title)
```

```{do_create.py}
from flaski.database import init_db
# テーブルのクリエイト
init_db()
```

do_create.pyを実行することで、flaskiディレクトリの下にwiki.dbが出来上がる。
中身を確認してみる。

```{confirm_sqllite.}
$ > (flaski)hoge:flaski hoge$ sqlite3 wiki.db 
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE wikicontents (
	id INTEGER NOT NULL, 
	title VARCHAR(128), 
	body TEXT, 
	date DATETIME, 
	PRIMARY KEY (id), 
	UNIQUE (title)
);
```

```{do_insert.py}
from flaski.database import init_db
from flaski.database import db_session
from flaski.models import WikiContent

# idには連番。title、textを指定。dateは日時が勝手に入る(model.pyのデフォルト設定により)
c1 = WikiContent("Flask", "micro framework")  # カラム1:'Flask' カラム2:'micro framework'を指定してインスタンス作成
db_session.add(c1)                            # insert実行
db_session.commit()                           # commit実行
c2 = WikiContent("python", "pppython")        # 以下同上
c3 = WikiContent("kobito", "kakure-momojiri")
db_session.add(c2)
db_session.add(c3)
db_session.commit()
```

さっきのテーブルを確認してみる。

```{confirm_sqllite2.}
(flaski)hoge:flaski hoge$ sqlite3 wiki.db 
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> select * from wikicontents;
1|Flask|micro framework|2016-05-14 15:04:24.246057
2|python|pppython|2016-05-14 15:04:24.246057
3|kobito|kakure-momojiri|2016-05-14 15:04:24.246057
```

3レコードインサートされていることがわかる。

### 参考
http://omake.accense.com/static/doc-ja/sqlalchemy/session.html

## flaskとSQLiteを連携して、データのGET
さっきのSQLiteに作ったテーブルの値を使って、Webページを表示する。

### ソースコード
[github](https://github.com/shiraiyusuke/python_study/tree/master/flask_test/src)

### ファイル構成
```
├── flaski
│   ├── __init__.py
│   ├── database.py
│   └── models.py
├── app.py
├── static
│   └── snake.jpg
└── templates
    ├── index.html
    ├── layout.html
    └── show_content.html
```

### 各スクリプト
```{app.py}
# -*- coding: utf-8 -*-
"""
 Using SQLAlchemy and Flask get db record.(GET)
"""

from flask import Flask, render_template, abort
from flaski.models import WikiContent

app = Flask(__name__)
app.config['DEBUG'] = True


# 起動されたサーバーの/にアクセスした時の挙動を記述。
# @app.route("/hoge")で記述すれば、http://127.0.0.1:5000/aaでアクセスした時の挙動を記述できる。
@app.route("/")
def hello():
    contents = WikiContent.query.all()
    # index.htmlにcontensを引数に渡して実行。
    return render_template("index.html", contents=contents)

#/<title>を指定することで、index.htmlのtitle=content.titleを指定している。methods=["GET"]で、GETリクエストを指定。
@app.route("/<title>", methods=["GET"])
def show_content(title):
    """
    :param title:modelに対するクエリ文字列
    :return:
    """
    # wikicontentテーブルから、titleでフィルタ(where指定して取得) firstは1行だけ取得するの意味。
    # all()だと、結果を複数リスト形式で取得する。
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        abort(404)
    # show_content.htmlを表示。引数にcontentを渡す。
    return render_template("show_content.html", content=content)

if __name__ == "__main__":
    # サーバーの起動
    app.run()
```

snake.jpg
![snake.jpg](https://qiita-image-store.s3.amazonaws.com/0/40159/a7046cfb-f4d7-9853-2e47-167e39c393b9.jpeg)

```{index.html}
{% extends "layout.html" %}
{% block body %}
<h1>Flaski</h1>
<!-- snake.jpgの表示-->
<img src="{{url_for('static', filename='snake.jpg')}}" alt="snake"/>
<ul>
<!-- 引数で渡されたcontentsのforループ-->
{% for content in contents %}
<!-- show_content.htmlを実行。+ {{content.title}}を表示。-->
<li><a href="{{url_for('show_content', title=content.title)}}">{{content.title}}</a></li>
{% endfor%}
</ul>
{% endblock %}
```

```{show_content.html}
{% extends "layout.html" %}
{% block body %}
<!-- contentの内容を使って表示-->
<h1>{{content.title}}</h1>
<div>{{content.body}}</div>
<p>{{content.date}}</p>
<div>{{content.body}}</div>
{% endblock %}
```

ここまでで、http://127.0.0.1:5000　で以下のページが表示される。
<img width="453" alt="スクリーンショット 2016-05-14 17.56.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/40159/1f1a0c93-d918-6818-a2a8-c12b14ce0664.png">

それぞれのリンクをクリックでSQLiteの内容が表示される。
例えばpythonのリンクを踏むと、以下のような感じ。
<img width="276" alt="スクリーンショット 2016-05-14 17.57.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/40159/baa7b4d4-ab4f-52dc-3812-75683d007c0f.png">

SQLiteのpythonの内容を使って、表示されている。（GETができた）
以下のid=2の内容。

```{confirm_sqllite2.}
(flaski)hoge:flaski hoge$ sqlite3 wiki.db 
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> select * from wikicontents;
1|Flask|micro framework|2016-05-14 15:04:24.246057
2|python|pppython|2016-05-14 15:04:24.246057
3|kobito|kakure-momojiri|2016-05-14 15:04:24.246057
```

## APIのpostでSQLiteのテーブルに挿入/更新(POST)
SQLiteのテーブルに対し、insertとupdateを実行してみる。

### ファイル構成
```
├── flaski
│   ├── __init__.py
│   ├── database.py
│   └── models.py
└── app.py
```

```{app.py}
# -*- coding: utf-8 -*-
from flask import Flask, render_template, abort, request
from flaski.models import WikiContent
from flaski.database import db_session
from datetime import datetime

app = Flask(__name__)
app.config['DEBUG'] = True


@app.teardown_request
def shutdown_session(exception=None):
    db_session.remove()


@app.route("/")
def hello():
    contents = WikiContent.query.all()
    return render_template("index.html", contents=contents)


@app.route("/<title>", methods=["GET"])
def show_content(title):
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        abort(404)
    return render_template("show_content.html", content=content)

@app.route("/<title>", methods=["POST"])
def post_content(title=None):
    if title is None:
        abort(404)
    content = WikiContent.query.filter_by(title=title).first()
    # contentが取得できていなければinsertするため、WikiContentのインスタンスを生成
    if content is None:
        content = WikiContent(title,
                              request.form["body"]
                              )
    # contentが取得できていればupdateするため、bodyの内容とdatetime=現在時刻をセット
    else:
        content.body = request.form["body"]
        content.date = datetime.now()

    # contentの内容をaddしてcommit
    db_session.add(content)
    db_session.commit()
    return content.body

if __name__ == "__main__":
    app.run()
```

この状態で、app.pyを実行しておく。

コンソール上で、httpリクエストをするため、pip install httpieでhttpieをインストールしておく。

```{insert_sqlite.}
# body=test from httpie を指定して更新
http --form POST http://localhost:5000/httpie body="test from httpie"
```

中身を確認してみると、id=4のレコードが追加された。

```{confirm_sqllite3.}
sqlite> select * from wikicontents;
1|Flask|micro framework|2016-05-14 15:04:24.246057
2|python|pppython|2016-05-14 15:04:24.246057
3|kobito|kakure-momojiri|2016-05-14 15:04:24.246057
4|httpie|test from httpie|2016-05-14 19:32:12.075871
```

今度は更新してみる。

```{update_sqlite.}
# body=modified from httpie を指定して更新
http --form POST http://localhost:5000/httpie body="modified from httpie"
```

```{confirm_sqllite4.}
sqlite> select * from wikicontents;
1|Flask|micro framework|2016-05-14 15:04:24.246057
2|python|pppython|2016-05-14 15:04:24.246057
3|kobito|kakure-momojiri|2016-05-14 15:04:24.246057
4|httpie|modified from httpie|2016-05-14 19:35:40.904904
```

更新されてる。

この状態で、http://localhost:5000　を確認するとhttpieが追加されている。

<img width="430" alt="スクリーンショット 2016-05-14 19.39.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/40159/110ec27f-923c-ed9e-1bc2-b788724a1024.png">

httpieをクリックすると、ちゃんと更新した状態で取得できていることがわかる。
POSTできました。

<img width="280" alt="スクリーンショット 2016-05-14 19.39.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/40159/d82e602d-9166-d2a4-7cb6-6aad36787aad.png">