Python+Flaskでウェブアプリケーション④

1. 環境の準備(OSセットアップ)
2. 環境の準備(OS内のセットアップ)
3. FlaskのQuickStartの内容をトレースする(インストールと最低限のセットアップ)
4. FlaskのTutrialの内容をトレースする(基本的なアプリケーションの作り方の学習)★
5. オリジナルの内容を作る

[FlaskのTutrial](https://a2c.bitbucket.io/flask/tutorial/index.html)はこちら。
Flaskrというマイクロブログのサービスを構築する。データベースとしてはsqliteを使用している。

# フォルダ構成の作成
アプリケーションを配置するディレクトリを作成する。名前は任意だが、標準に合わせるためにstatic,templatesはそのまま使ったほうがよい。(templatesと複数形にすることを忘れずに)

```shell-session
[root@cnenyuy5l3c ~]# mkdir flaskr
[root@cnenyuy5l3c ~]# cd flaskr
[root@cnenyuy5l3c flaskr]#
[root@cnenyuy5l3c flaskr]# mkdir templates static
[root@cnenyuy5l3c flaskr]#
[root@cnenyuy5l3c flaskr]# virtualenv env
New python executable in /root/flaskr/env/bin/python2.7
Also creating executable in /root/flaskr/env/bin/python
Installing setuptools, pip, wheel...done.
[root@cnenyuy5l3c flaskr]#
[root@cnenyuy5l3c flaskr]# ls
env  static  templates
[root@cnenyuy5l3c flaskr]#
```

# 仮想環境分離とFlaskインストール
virtualenvを有効化して、そこにflaskをインストールする。

```shell-session
[root@cnenyuy5l3c flaskr]# . env/bin/activate
(env) [root@cnenyuy5l3c flaskr]#
(env) [root@cnenyuy5l3c flaskr]# pip install flask
Collecting flask
(中略)
Successfully installed Jinja2-2.9.5 MarkupSafe-0.23 Werkzeug-0.11.15 click-6.7 flask-0.12 itsdangerous-0.24
(env) [root@cnenyuy5l3c flaskr]#
```

# DB作成
まずはスキーマを作成。

```sqlite3:schema.sql
drop table if exists entries;
create table entries (
  id integer primary key autoincrement,
  title string not null,
  text string not null
);
```

このスキーマを直接流すわけではなく、Pythonでモジュール化している。
以下のflaskr.pyがflaskrアプリケーション本体になるが、ここではDBの初期化の部分だけ作成している。

```py:flaskr.py
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, \
     abort, render_template, flash
from contextlib import closing    # ここはオフィシャルにはなかったが必要だった

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])

def init_db():
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:  # ここでschema.sqlを実行させる
            db.cursor().executescript(f.read())
        db.commit()

# ここに各種View関数を追記していく。

if __name__ == '__main__':
    app.run(host='0.0.0.0')  # ゲストローカル以外から参照させる
```

from_objectは対象内の大文字の変数をすべて読み込むというメソッド。
ここでの対象は__name__,すなわち自分自身のファイル(flaskr.py)。
またオリジナルでは最後のapp.runはオプションを指定していないが、今回はホストマシンから参照するために、host='0.0.0.0'を指定している。
これで準備ができたので、DBを作成する。

```py
(flaskr) [root@localhost flaskr]# python
Python 2.7.12 (default, Jan  4 2017, 08:18:28)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from flaskr import init_db
>>> init_db()
>>>
```

ここで以下のエラーになる場合は、pythonコンパイル時にsqlite3-develが不足しているため、入れ直す必要あり。

```py
>>> from flaskr import init_db
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "flaskr.py", line 5, in <module>
    import sqlite3
  File "/opt/local/lib/python2.7/sqlite3/__init__.py", line 24, in <module>
    from dbapi2 import *
  File "/opt/local/lib/python2.7/sqlite3/dbapi2.py", line 28, in <module>
    from _sqlite3 import *
ImportError: No module named _sqlite3
```

作成されたデータベースを一応確認しておく。

```shell-session
(env) [root@cnenyuy5l3c flaskr]# sqlite3 /tmp/flaskr.db
SQLite version 3.6.20
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
entries
sqlite>
sqlite> .schema
CREATE TABLE entries (
  id integer primary key autoincrement,
  title string not null,
  text string not null
);
sqlite> .quit
```

# View関数の作成
ここから関数を追加していく。最初はデータベースとのコネクション作成とクローズ処理。

```py
@app.before_request
def before_request():
    g.db = connect_db()

@app.after_request
def after_request(response):
    g.db.close()
    return response
```

gはflaskの提供する特殊なオブジェクトで、現在のデータベースとのconnectionが保存されている。
(gは1つのリクエストの情報しか保存しない)
before_requestはリクエストの前に呼び出される。ここでは何の引数も持たない。

after_requestはリクエストの後に呼び出される。
ここでは、コネクションをクローズし、DBからのレスポンスをクライアントに渡している。
このへんはほぼ決まり文句と考えてよさそう?
ここから実際のページに対応する内容に入る。基本的にはrouteデコレータによって、URLと呼び出される関数を紐づけていく作業。

```py
@app.route('/')    # http://<hostname>:5000/ にアクセスした際に呼び出される関数を定義(ルーティング)
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)    # show_entries.htmlというHTMLにentriesという値を埋め込み(レンダリング)

@app.route('/add', methods=['POST'])
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    g.db.execute('insert into entries (title, text) values (?, ?)',
                 [request.form['title'], request.form['text']])
    g.db.commit()
    flash('New entry was successfully posted')
    return redirect(url_for('show_entries'))    # レンダリングでなく、関数に飛ばす場合はredirect()を使用。

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)

@app.route('/logout')
def logout():
    session.pop('logged_in', None)
    flash('You were logged out')
    return redirect(url_for('show_entries'))

```

# HTMLの作成
これで関数(機能)の部分を作成したので、次にHTML(見た目)に移る。
FlaskではおおもとのベースとなるHTML(layout.htmlということが多い)を作り、それを適宜拡張していく形になる。

```html:layout.html
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
  <h1>Flaskr</h1>
  <div class=metanav>
  {% if not session.logged_in %}
    <a href="{{ url_for('login') }}">log in</a>
  {% else %}
    <a href="{{ url_for('logout') }}">log out</a>
  {% endif %}
  </div>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}    <!-- このブロックが参照時に置き換えられる -->
</div>
```

```html:show_entries.html
{% extends "layout.html" %}    <!-- これで元になるHTMLを指定 -->
{% block body %}    <!-- layout.htmlのbodyというブロックを以下で置き換える -->
  {% if session.logged_in %}
    <form action="{{ url_for('add_entry') }}" method=post class=add-entry>
      <dl>
        <dt>Title:
        <dd><input type=text size=30 name=title>
        <dt>Text:
        <dd><textarea name=text rows=5 cols=40></textarea>
        <dd><input type=submit value=Share>
      </dl>
    </form>
  {% endif %}
  <ul class=entries>
  {% for entry in entries %}
    <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
  {% else %}
    <li><em>Unbelievable.  No entries here so far</em>
  {% endfor %}
  </ul>
{% endblock %}
```

```html:login.html
{% extends "layout.html" %}
{% block body %}
  <h2>Login</h2>
  {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
  <form action="{{ url_for('login') }}" method=post>
    <dl>
      <dt>Username:
      <dd><input type=text name=username>
      <dt>Password:
      <dd><input type=password name=password>
      <dd><input type=submit value=Login>
    </dl>
  </form>
{% endblock %}
```

# CSSの作成
最後にCSSファイルを作成する。

```css:style.css
body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377BA8; }
h1, h2          { font-family: 'Georgia', serif; margin: 0; }
h1              { border-bottom: 2px solid #eee; }
h2              { font-size: 1.2em; }

.page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                  padding: 0.8em; background: white; }
.entries        { list-style: none; margin: 0; padding: 0; }
.entries li     { margin: 0.8em 1.2em; }
.entries li h2  { margin-left: -1em; }
.add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl   { font-weight: bold; }
.metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                  margin-bottom: 1em; background: #fafafa; }
.flash          { background: #CEE5F5; padding: 0.5em;
                  border: 1px solid #AACBE2; }
.error          { background: #F0D6D6; padding: 0.5em; }
```

# 起動、テスト
以上のファイルを以下のように配置する。

* flaskr/ (ルート)
    * env/
    * templates/
        * layout.html
        * show_entries.html
        * login.html
    * static/
        * style.css
    * flaskr.py
    * schema.sql

ここまでできれば、アプリケーションを起動してアクセスを確認。

```shell-session
(env) [root@cnenyuy5l3c flaskr]# python flaskr.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger pin code: 602-691-323
```

![20170131_009.jpg](https://qiita-image-store.s3.amazonaws.com/0/159980/321bedbb-93c2-62f6-d9cd-ba71096d7479.jpeg)
![20170131_010.jpg](https://qiita-image-store.s3.amazonaws.com/0/159980/d2e70e64-2218-1eb8-183c-095f992a3463.jpeg)
![20170131_011.jpg](https://qiita-image-store.s3.amazonaws.com/0/159980/763f1ff6-efb7-a6fe-3e7f-2f4621b8b791.jpeg)

問題なさそう。
これで最低限の基礎は抑えられた(はず)なので、次回以降オリジナルに進む予定です。