Flaskまとめ

# どんな記事？
忘年会用のウェブアプリを開発していて，Flaskを使うことまでは決まったのですが，Flaskの情報がいろいろなところに散らばっている印象を受けたのでまとめようと思いました．
他のフレームワークを使ったことがある人がflaskでの実装方法を調べるときに使っていただければと思っています．
他にもこれを書いといて！などのリクエストがあれば送ってくださると幸いです．

# Flask導入
## Flaskとは
[Flask](https://a2c.bitbucket.io/flask/) とはpythonで動作する軽量なウェブアプリケーションフレームワークです．pythonのウェブフレームワークはほかにはDjangoがあげられます．個人的な意見ですが，DB処理などを伴うものはDjango，それ以外の比較的軽い処理を行うときはflaskと使い分けるといいかなと思います．
この記事ではflaskのバージョンは1.1.1を使用しています．

## インストール
インストール方法ですが，anacondaなどには初期から入っている場合があります．ない場合は

```sh
pip install flask
```

で導入できます．また，バージョン確認は

```sh
$ python
$ >> import flask
$ >> flask.__version__
```

で確認可能です．

## ディレクトリ構成
flaskの基本的なディレクトリ構成は以下のようなものになります．

```
webroot |- server.py
        |- static - hoge.png
        |- templates - index.html
```

## Hello Wrold
index.htmlを送信するようなserver.pyは以下のようになります．
Hello Worldは他のサイトで詳しく書いているところもあるので（http://python.zombie-hunting-club.com/entry/2017/11/03/223503 など）そちらも参考にしてください．

```server.py
from flask import *

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0', port=8080)
```

# 基礎編
ここではウェブアプリケーションとして最低限必要な機能の実装についてみていきます．

## ルーティングとパラメータ
ルーティングとはURL（とhttpメソッド）に対して呼び出される処理を決めることです．flaskではアノテーションを用いて実装されています．たとえば `/start` に `start()` 関数を紐付けるには以下のように書きます．HTTP methodも指定することができます．
各httpメソッドでのパラメータの受け取り方とともにのせておきます．

```python
@app.route("/start", methods=["GET", "POST"])
def start():
    if request.method == "POST":
        user_id = request.form["user_id"] # POSTされたformから取得
    else:
        user_id = request.args.get("user_id") # GETのURLから取得

@app.route("/start_2/<user_id>", method=["GET"])
def start_2():
    # /start_2/1 にアクセスされると user_id に1を格納  
```

## レスポンスの作成
Hello World のコードでは `return render_template("index.html")` としてindex.htmlを返しましたが，レスポンスを明示的に作成して，responseオブジェクトにヘッダを付与するなどの操作を行うこともできます．
これを用いてjsonオブジェクトの返却なども実装できます．

```python
@app.route("/")
def index():
    response = make_response(render_template("index.html"))
    return response

@app.route("/json")
def get_json():
    response = make_response(jsonify({"result": 1}))
    return respnose
```

# 発展編
## Cookie, Sessionの利用
### Cookie
クッキーは以下のようにして取得，設定します．pathではクッキーの設定もと，expiresではタイムアウト，secureではhttpsのみのアクセス，httponlyではjavascriptからのアクセスを拒否します．
詳しくは https://pythonise.com/series/learning-flask/flask-cookies などを参照してください．

```python
@app.route("/")
def index():
    max_age = 60 * 60 * 24 # 1 days
    expires = int(datetime.now().timestamp()) + max_age

    user_id = request.cookies.get("user_id", None) # クッキーの取得
    # responseの作成
    response = make_response(render_template("index.html")) 
    response.set_cookie("user_id", value=user_id, path=request.path,
                        expires=expires, httponly=True, secure=False)
```

### Session
セッションは辞書のようにあつかうことができます．ログインをするときなどは以下のようになります．

```python
from datetime import timedelta
app.secret_key = "hogefuga" # 暗号化用の鍵
@app.route("/login")
def login():
   # セッションのタイムアウト設定
   session.permanent = True
   app.permanent_session_lifetime = timedelta(minutes=30)
   # セッションのデータをいじる
   if "userid" not in session: # ログインしているかの確認
       user_id = request.form["user_id"]
       session["userid"] = user_id
   return redirect(url_for("logined"))
```

## jinja2を利用した動的HTML作成
flaskではjinja2と呼ばれるテンプレートエンジンを使用して動的なHTMLを作成することができます．たとえば，index.htmlでuser_nameを表示したい場合は以下のようになります．基本的に `{% %}` で囲まれた部分にpythonライクな構文を書いていきます．もちろんオブジェクトを与えてメンバにアクセスすることも可能です．

```server.py
@app.route("/")
def index():
    user_name = "hoge"
    lst = ["item1", "item2"]
    return render_template("index.html", user_name=user_name, lst=lst)

```

```index.html
<!-- if文 -->
{% if user_name %}
  <p> {{ user_name }} </p>
{% endif %}

<!-- for文 -->
{% for item in lst %}
  <p> {{ item }} </p>
{% endfor %}
```

また，外部からのhtml読み込みは以下のようにできます．

```html
{% include "footer.html" %}
```

## エラー処理
flaskではエラーコードによるハンドリングと例外によるハンドリングの両方をサポートしています．また，エラーを起こす方法ですが， `abort(404)` などで起こすことができます．

```python
@app.errorhandler(404)
def handle_not_found(error):
    return render_template("not_found.html"), 404

@app.errorhandler(MyException)
def handle_my_error(error):
    ....
```

## 設定ファイル
https://qiita.com/nanakenashi/items/e272ff1aafb3889230bc
こちらに詳しくまとまっているので，こちらを参照してください．
開発の規模によっても異なりますが，自分は設定ファイルから読み込めればいいかなくらいにしか考えてませんでした（雑）．

## Flask-WTFとの連携（Formクラスの生成）
FormヘルパではFlask-WTFを使うといいかなと思います．これは入力チェックやCSRFトークンの生成などを自動で行ってくれます．
たとえば，ログインフォームを作ろうと思った場合のサンプルは以下のようになります．

```login_form.py
from flask_wtf import FlaskForm
from wtforms import *

class LoginForm(FlaskForm):
    user_id = StringField("user_id")
    password = PasswordField("password")

    def __init__(self, *args, **kwargs):
        kwargs["csrf_enabled"] = False # csrf tokenを無効化もできます
        super(LoginForm, self).__init__(*args, **kwargs)
    
    def validate_user_id(self, user_id):
        if len(user_id.data) > 100:
            raise ValidationError("user_id is too long")
```

```server.py
@app.route("/login", methods=["POST"])
def login():
    form = LoginForm(request.form)
    if form.validate(): # 自動バリデーション
        user_id = form.user_id.data # user_idにアクセス
    return render_template("login.html", form=form)
```

```login.html
<form action="login" method="POST">
  {{ form.hidden_tag() }} <!-- scrf tokenはここで送られる -->
  {{ form.user_id(class_="form-control") }} <!-- class_ でcssをあてられる -->
</form>
<!-- エラーの表示 -->
{% for error in form.answer.errors %}
  <div class="text-danger">
    {{error}}
  </div>
{% endfor %}
```

公式(https://flask.palletsprojects.com/en/1.1.x/patterns/wtforms/)も参考にしてみてください．