【Python Flask】初心者プログラマーのWebアプリ#4 フォーム POST/GET送信・受け取り

第4回目はform入力ページから情報を入力してもらい、Flask(サーバー)に送ってデータ処理を行う方法を扱います。

![form.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/9562ac96-6f89-46e8-da18-0fd2594af105.png)

フォームを使って情報を柔軟に受け取れるようになると、一般的なWEBアプリはもちろん、AI、画像認識機械学習、自然言語系の機械学習などなど、入力されたフォームの情報を使って役に立つWebアプリで使えるようになると思います。

Pythonを使っているならぜひ身につけておきたいところですね。

# :pushpin: Pythonで作るFlaskアプリ記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[簡単なページ作成](https://qiita.com/Bashi50/items/30065e8f54f7e8038323)|
|**part2**|[HTMLテンプレート表示](https://qiita.com/Bashi50/items/9bb5ca4a519c17caf475)|
|**part3**|["画像" "CSS" "Javascript"実装](https://qiita.com/Bashi50/items/f4c2eb42b9cda9b91295)|
|**part4**|**フォーム送信　← ココ**|
|**part5**|[データベースの値取得・更新](https://qiita.com/Bashi50/items/e3459ca2a4661ce5dac6)|

## ソースコード
この章のコードは以下です。確認やコピペでどうぞ

https://github.com/kohishibashi/flask/tree/part4

# 0. 前提

:white_check_mark: 作れるもの

- フォーム作る。そして送るデータを入力できるようにする。
    - inputフィールドを使用。文字の入力と送信
    - サーバーサイドでデータを受け取り。Flaskで処理する。

初心者向け記事なので基礎的な内容を少し書いています。HTML,GET,POSTなど問題ない人は[ 1.実装の ](#1.-実装)から始めていただければと思います。

## 0.1. フォームに情報を入力してPost送信→Flaskで受け取り
HTMLの話になるので軽くだけ触れますが

- メール、パスワード入れてログイン → 文字入力フォーム
- 数量や日時を選択 → 選択フォーム
- 画像などのファイルのアップロード → アップロードフォーム

なんかがHTMLには用意されています。ただ、

1. HTMLはユーザーがフォームが表示として見えて、
1. 情報入力できて、
1. フォームに入力or選択した情報を特定のURLに送信する

までしかできません。本当に価値があるのは、送信されたデータをDBに登録したり、データを照合したり、データを使って何かしらの処理を行うことです。

この本当に価値がある処理を行うのは、サーバーサイドの処理、つまり今回ならFlaskを入れたサーバーでの処理ということになります。

※ HTMLだけでも簡単な値チェックもできたりしますがおまけ程度です。

## 0.2. POSTとGET送信
GET送信とPOST送信に簡単に触れます。

### 0.2.1 GET
URLの中に送信するデータを`?`, `&`つけてサーバーサイドに送信するのがGETです。
ページの表示や検索には乱用されていて、URLに`https://qiita.com/`みたいに入力して移動は基本的にはGET使います。

:white_check_mark: 問題点

```
http://sample.saite/?customer_name=Tanaka&age=40
```
例えばこんなURL。簡単に見れますので情報が筒抜けです。個人情報やパスワードなど送信する場合にはセキュリティ的にあまりお勧めできません。
そして、ファイルなど送るのも送れる容量が小さいので、まず無理です。

GETは見られてもそれほど問題ないような情報として、商品ID、ページ番号、商品検索フォームとかに入力した値とか送信する場合に使用します。

### 0.2.2. POST
POSTはURLからはデータの内容は分かりません。
パスワードとかアップロードしたファイルを送信するときなどで使います。

下は試さなくてもいいですが、Postmanでテキトウにjsonとして送信できるようにデータ作ってました。（cURLです）

```
curl --location --request POST 'http://sample.saite/' \
--header 'Content-Type: application/json' \
--data-raw '{
    "customer_name": "Tanaka",
    "age": 40
}'
```

POSTだとURLに`&`, `?`を使ってデータをつける以外にデータbodyの部分にデータを複数入れて送信することができます。
上記の例だと`customer_name`, `age`をオブジェクトみたいな感じで書いて送ってます。

- GETだとアドレスバーに情報が堂々と表示されている
    - パスワードとか住所とか表示されてたらなんか嫌ですよね
- 仮にアドレスバー見られなくてもURLって過去の履歴などで見られたくない情報を見られる可能性がある
- 情報増えると文字数増えすぎる。制限あるので大量には送れない

ということがあるので必要に応じてPOSTを使います。

# 1. 実装
前提が少し長くなりましたが、フォームの実装に進みます。

## 1.1. フォームを表示するページを作成

名前はなんでもいいので`sampleform.html`として作成。
人間が文字を入力できるページを作りたいから作ります。

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テストフォーム</title>
</head>
<body>
    <h1>サンプルフォームです。</h1>
</body>
</html>
</html>
```
以前やった共通のテンプレート適用しないなら、こんな感じで普通のHTML書いてあとはBodyの中に入力フォーム作っていけばOK。

[以前](https://qiita.com/Bashi50/items/9bb5ca4a519c17caf475)やったように、これだけだと表示できないのでViewにあたるファイル`/Users/dev/flask/testapp/views.py`の中にページ表示のための処理書いていく。

```/Users/dev/flask/testapp/views.py
@app.route('/sampleform')
def sample_form():
    return render_template('testapp/sampleform.html')
```
いかのURLにページ作成しました。

> http://127.0.0.1:5000/sampleform

![スクリーンショット 2021-12-12 0.16.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f249e53e-a3c7-2452-94f3-9d7f69b93aac.png)

これは以前までの復習ですね。ただ、HTMLファイルを割り当てたURL(`/sampleform`)に来たらレンダリング(HTMLファイルを作成)してるだけです。

## 1.2. formの要素を作る
HTMLの要素作ります。まんまHTMLなので、HTMLは知っている前提で書いていきます。

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
    <h1>サンプルフォームです。</h1>
    <form action="/sampleform-post" method="POST">
        <input name="data1"></input>
        <button type="submit">送信</button>
    </form>
```

- inputフォームが文字入力フォーム準備。
- 送り先は`/sampleform-post` POSTで送信するのでmethod指定。
- 送ったときのデータの名前を`data1`にしてます。適時名前をつけてOK。
- 押したら送信できるボタン配置

ダサいですが入力できる機能準備しました。

![スクリーンショット 2021-12-12 0.23.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/12f73c84-bba7-b894-0a98-551baabaa03d.png)

この段階で送ってもFlaskでPOSTで送信した場合の処理を書いていないのでPOSTでデータを送られてきたデータを受け取って処理をする記述をします。

## 1.3. POSTデータを受け取りデータ処理
`/sampleform-post`と言うURLにデータを`POST`として送信していますので処理を追記していきます。

`@app.route('POSTで受け取るURLここに', methods=[メソッドを指定するので'POST'])`
すればOK。

```/Users/dev/flask/testapp/views.py
@app.route('/sampleform-post', methods=['POST'])
def sample_form_temp():
    print('POSTデータ受け取ったので処理します')
    return 'POST受け取ったよ'
```
テキトウにデータを入力してボタンを押すとreturnに書いた文字列`POST受け取ったよ`が表示されます。

![スクリーンショット 2021-12-12 0.34.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/fae24557-0c34-7887-d524-a410cd80b65c.png)
`print()`は確認用で入れただけなので不要ですがデバックなどに使えます。
サーバーを起動したターミナルにこんな感じで表示されると思います。

```
127.0.0.1 - - [12/Dec/2021 00:33:58] "GET /sampleform HTTP/1.1" 200 -
POSTデータ受け取ったので処理します
127.0.0.1 - - [12/Dec/2021 00:34:00] "POST /sampleform-post HTTP/1.1" 200 -
```

今回は最初なので何も処理をせずに文字だけを返却しています。

押したらURLが変わっているのでもう一度試すときは以下にURLで試してください。

> http://127.0.0.1:5000/sampleform


せっかく送ったデータ使ってないので使えるようにします。

`request`をimportします。フォームから送られてきたデータなどをみるために使います。

```/Users/dev/flask/testapp/views.py
from flask import render_template, request  # request追加
from testapp import app
.
.
.
@app.route('/sampleform-post', methods=['POST'])
def sample_form_temp():
    print('POSTデータ受け取ったので処理します')
    req1 = request.form['data1']

    return f'POST受け取ったよ: {req1}'

```
フォーム　に「テストの値」を入力すると結合されて`POST受け取ったよ: テストの値`となります。

![スクリーンショット 2021-12-12 0.55.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/a174b505-28f7-ee68-af87-0f3554d22709.png)

`request.form['data1']`これがフォームから受け取ったデータを取り出している部分になります。
※ HTMLの方で`name=data1`と定義してましたね。

request.formにdictの一種みたいな形でデータが入っていますので
`request.form['data1']`で取り出せます。
取り出したデータを文字列として結合して表示しているだけです。

## 1.4. GETとPOST一つのメソッドに書く
今回、入力フォームは`/sampleform` POSTは`/sampleform-post`と分けましたが、分けるのは必須ではありません。入力のためのページを表示するときは「GET」メソッドで表示して、inputから送信するときは「POST」を使用しています。

なので

- ページの表示だけする場合: GET
- フォームからの情報を処理: POST

で作り直します。
`sampel_form_temp`メソッドを削除して`sample_form`に統合します。

```/Users/dev/flask/testapp/views.py
@app.route('/sampleform', methods=['GET', 'POST'])
def sample_form():
    if request.method == 'GET':
        return render_template('testapp/sampleform.html')
    if request.method == 'POST':
        print('POSTデータ受け取ったので処理します。')
        req1 = request.form['data1']
        return f'POST受け取ったよ: {req1}'

```

:white_check_mark: 今回、使用するのでGETとPOSTなのでmethodsを追加。
`@app.route('/sampleform', methods=['GET', 'POST'])`

:white_check_mark: GET or POSTどちらの処理をするかは`request.method`を使用します。この中にGETで送信された時は'GET', POSTで送信された時は'POST'が文字列として入っています。これを使って条件をつければGETとPOSTで処理を変えることができます。

入力フォームを表示するときは`if request.method == 'GET':`の条件になるのでテンプレートをレンダリング（表示）する。

フォームに入力して送信した場合はPOSTなので`if request.method == 'POST':`の条件に入る。
こちらは先ほどと同じように文字を表示するだけになります。


#### テンプレートで送り先変更
POSTの送り先も統合する必要あるので`<form action="/sampleform" method="POST">`に修正

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
    <form action="/sampleform" method="POST">
        <input name="data1"></input>
        <button type="submit">送信</button>
    </form>
```

リダイレクトや戻るボタンはまだつけてないので`http://127.0.0.1:5000/sampleform`で戻ってください。



# 2. 付録: ジャンケンアプリで少しアプリっぽくする
なんかフォームで情報送って表示させるだけだと面白くないので少し改造します。
特に意味はないですが、ジャンケンの勝ち負け判定するアプリ作ってみます。付録なのでサラッと説明。

## 2.1. 実装

- :white_check_mark: ラジオボタンでグー、チョキ、パーを選べるようにする
- :white_check_mark: 初期状態はグーとする
- :white_check_mark: グー：０, チョキ: 1, パー: 2 としてPOST送信
- :white_check_mark: Flaskにて0~2をランダムに出現させる。ラジオボタンで選択された数値（グー：０, チョキ: 1, パー: 2）と比較して勝敗を判定して表示する
    - 表示は文字を表示するだけとする

ラジオボタンで、グー、チョキ、パーを選べるようにします。

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
    <form action="/sampleform" method="POST">
        <input type="radio" name="janken" value="0" checked>グー</input>
        <input type="radio" name="janken" value="1" >チョキ</input>
        <input type="radio" name="janken" value="2" >パー</input>
        <button type="submit">送信</button>
    </form>
```

![スクリーンショット 2021-12-12 1.46.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/48b1d50d-a92f-3475-2454-8e292d1cc98d.png)


```/Users/dev/flask/testapp/views.py
from random import randint
.
.
.

@app.route('/sampleform', methods=['GET', 'POST'])
def sample_form():
    if request.method == 'GET':
        return render_template('testapp/sampleform.html')
    if request.method == 'POST':
        # ジャンケンの手を文字列の数字0~2で受け取る
        hands = {
            '0': 'グー',
            '1': 'チョキ',
            '2': 'パー',
        }
        janken_mapping = {
            'draw': '引き分け',
            'win': '勝ち',
            'lose': '負け',
        }

        player_hand_ja = hands[request.form['janken']]  # 日本語表示用
        player_hand = int(request.form['janken'])  # str型→数値に変換必要
        enemy_hand = randint(0, 2)  # 相手は0~2の乱数
        enemy_hand_ja = hands[str(enemy_hand)]  # 日本語表示用
        if player_hand == enemy_hand:
            judgement = 'draw'
        elif (player_hand == 0 and enemy_hand == 1) or (player_hand == 1 and enemy_hand == 2) or (player_hand == 2 and enemy_hand == 0):
            judgement = 'win'
        else:
            judgement = 'lose'
        print(f'じゃんけん開始: enemy_hand: {enemy_hand}, player_hand: {player_hand}, judgement: {judgement}')
        return f'相手： {enemy_hand_ja}, あなた: {player_hand_ja}, 判定：{janken_mapping[judgement]}'

```
![スクリーンショット 2021-12-12 2.22.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/3f673625-4e3b-45e2-fd79-2f221a21da30.png)


文字表示されるだけしょぼアプリできました。
もう少しスマートに書けそうですがサクッと作ったのでお許しを:innocent:

注意点は`request.form['janken']`で受け取るのを0~2にしてますが、文字列になるので変換が必要なことでしょうか。


## 2.2. bootstrapと共通部分のテンプレートを適用
以前作った、headとかbootstrapを共通で読み込めるようにしてみます。

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
{% extends "layout.html" %}

{% block content %}
<div class="container">
    <h1>サンプルフォームです。</h1>
    <!-- ここに<form>などの該当箇所を移す -->

</div>
{% endblock %}
```

こんな感じになりますね。
この記事から見ている人もいるかもしれないので一応簡単に説明すると、Flaskはテンプレートエンジンを使っているので`{% ..... %}`とか`{ ... }`なんかの記法でレンダリングするときにFlaskで情報を入れることができるのでこのように書くことができるわけです。

そして`{% extends "layout.html" %}`と書くと共通部分を`layout.html`に記述しているので`layout.html`の`{% block content %}`の部分に`sampleform.html`の
`{% block content %}` ~ `{% endblock %}`が入ることになります。

なんやらかんやら少し整えた結果は以下

```/Users/dev/flask/testapp/templates/testapp/sampleform.html
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
    <h2>ジャンケン</h2>
    <form action="/sampleform" method="POST">        
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" id="gu" name="janken" value="0" checked>
            <label class="form-check-label" for="gu">グー</label>
          </div>
          <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" id="choki" name="janken" value="1">
            <label class="form-check-label" for="choki">チョキ</label>
          </div>
          <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" id="pa" name="janken" value="2">
            <label class="form-check-label" for="pa">パー</label>
          </div>
          <button type="submit" class="btn btn-primary">送信</button>
    </form>
</div>
{% endblock %}
```
変わっているように見えますがbootsrapのフォームの装飾しただけで本質は何も変えてないです。

![スクリーンショット 2021-12-12 2.48.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/377f23f5-efe6-5761-d36a-fac1d0c1426f.png)

少しだけですが見た目マシになりました！

bootstrap参考

> https://getbootstrap.jp/docs/5.0/components/buttons/

## 2.3. 結果表示ページ作成
これまでの復習の内容なので任意です。結果表示ページが文字を表示するだけになっているので、せめてHTMLテンプレート作って表示を整えて、戻るボタンをつけます。

`janken_result.html`と言うファイルをこれまでのテンプレートと同じところに作ってください。

views.pyはreturnの部分に`render_template`を使って、`janken_result.html`テンプレートにデータを埋め込みを行います。
テンプレートで参照するための名前として`result`としてテンプレートに渡してます。

```/Users/dev/flask/testapp/views.py
@app.route('/sampleform', methods=['GET', 'POST'])
def sample_form():
    ...
    ...
    ...
        result = {
            'enemy_hand_ja': enemy_hand_ja,
            'player_hand_ja': player_hand_ja,
            'judgement': janken_mapping[judgement],
        }
        return render_template('testapp/janken_result.html', result=result)
```


```/Users/dev/flask/testapp/templates/testapp/janken_result.html
{% extends "layout.html" %}

{% block content %}
<div class="container mt-4">
    <h2>ジャンケンの結果</h2>
    <p>相手：{{ result.enemy_hand_ja }}</p>
    <p>あなた：{{ result.player_hand_ja }}</p>
    <p>判定：{{ result.judgement }}</p>
</div>
{% endblock %}
```

## 2.3. 戻るためのリンクを設置
戻るボタンがないと今のままだと不便すぎるので前回の静的ファイル表示、読み込みでやった、URLを埋め込むための方法を使って実装します。

普通にHTMLだけ使って`<a href="/sampleform">戻る</a>`
でもいいんですが、

「url_for」を使います。どっちでもいいならHTMLのアンカータグ使えば良さそうですが、URLを変更するときテンプレートは変更しなくても良いのでこのように実装する方がおすすめです。

### メソッドを指定してそのviewに遷移
テンプレートに以下のように記述すればそれでOKです。楽ですね。

```
<a href="{{ url_for('メソッド名') }}">戻る</a>
```


```/Users/dev/flask/testapp/templates/testapp/janken_result.html
{% extends "layout.html" %}

{% block content %}
<div class="container mt-4">
    <h2>ジャンケンの結果</h2>
    <p>相手：{{ result.enemy_hand_ja }}</p>
    <p>あなた：{{ result.player_hand_ja }}</p>
    <p>判定：{{ result.judgement }}</p>
    <p>
        <a href="{{ url_for('sample_form') }}">戻る</a>
    </p>
</div>
{% endblock %}
```
ラジオボタンで手を選択して送信するとこんなページが出る。

![スクリーンショット 2021-12-12 21.35.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/298d1afe-a6f0-68e5-2088-f1d8e2dd730a.png)

これで結果表示ページ完成です。
戻るはただのリンクでしかないのでbootstrapなど使って装飾してみるのもいいかも。

## 3. 補足：version 2.0以降のroute書き方
今回Flask2.0を使っています。今のところ日本語情報はversion 1系の情報多いですので補足という形で書きます。
デコレータで書いている`@app.route`が以下のように書くこともできるようになっています。

```/Users/dev/flask/testapp/views.py
@app.get('/sampleform')
@app.post('/sampleform')
def sample_form():
    ...
```

Fast APIと同じような感じですね。直感的にわかりやすくなっている気がします。
できることは同じなので今のところは好きな方で書けばいいかと思います。

# :pushpin: Pythonで作るFlaskアプリ記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[簡単なページ作成](https://qiita.com/Bashi50/items/30065e8f54f7e8038323)|
|**part2**|[HTMLテンプレート表示](https://qiita.com/Bashi50/items/9bb5ca4a519c17caf475)|
|**part3**|["画像" "CSS" "Javascript"実装](https://qiita.com/Bashi50/items/f4c2eb42b9cda9b91295)|
|**part4**|**フォーム送信　← ココ**|
|**part5**|[データベースの値取得・更新](https://qiita.com/Bashi50/items/e3459ca2a4661ce5dac6)|

## ソースコード
この章のコードは以下です。確認やコピペでどうぞ

https://github.com/kohishibashi/flask/tree/part4