FlaskにおけるForm（GET/POST）のまとめ

### 本記事の背景
Flaskは最小限に開発できるように作られたライブラリであるため、関連する周辺要素についてまとまった記事を書くにはそれなりの調査時間が必要となるため、ピンポイントの記事はあるものの、まとまった記事は少ない。
本記事は、筆者の独断で入門レベル、中級者レベルに分けて記事をまとめることにする。

### 入門レベル
① templateを持たせない場合（バックエンド側の開発を想定）
下記は小生がまとめた入門記事を参照してください。

https://qiita.com/akeyi2018/items/0ac41c60b8b048f74991

② templateを持っている場合（フロントエンド側を含めての開発を想定）
下記のソースコードを参照

https://github.com/akeyi2018/FlaskStudy/tree/master/CameraControl

③ CSVファイルをPOSTした場合の事例
こちらを実行すると、CSVファイルの内容をHTMLのTableに変換され、表示されるように作成しました。

https://github.com/akeyi2018/SearchFavor/tree/main

④ Flask-wtfについて
Flask-wtfについて下記の記事でまとめているので、そちらに譲るとします。

https://qiita.com/yuta6234/items/1089ed1b14af93fadda3

Flask-wtfはFlask専用のライブラリで、Flask独自にいろいろとカスタマイズできるようになっているFormです。
下記の事例は、バリデーション含めて実装。
```py
from flask_wtf import FlaskForm
# 中略
class UploadForm(FlaskForm):
    file = FileField('CSVファイル：',validators=[FileRequired(), FileAllowed(['csv'])])

class LoginForm(FlaskForm):
    username = StringField('ユーザ名：', validators=[InputRequired(), length(min=4, max=15)])
    password = PasswordField('パスワード：', validators=[InputRequired(), length(min=8,max=80)])
    remember = BooleanField('ログインしたままにする')
    def validate_password(self, password):
        cls = User()
        user = cls.get_user(user_name=self.username.data)
        if user:
            if not check_password_hash(user['hash_key'], password.data):
                raise ValidationError('パスワードが違います。')
        else:
            raise ValidationError('無効なユーザ名またはパスワードです。')
```

### 中級者レベル
中級者レベルについては、動的なページをフロント側で実現する方法があります。
この場合、フロント側のUI（ボタンなど）にイベントハンドラーを付与する。
UIが発火ポイントとなり、JavaScriptでFecthを使ったり、jqueryのAjaxを使ったりする

下記のそれぞれのコード例を示す
① JavaScriptでFecthを使う例
```javascript
const button = document.querySelector('#buy_now_btn');

button.addEventListener('click', event => {
    fetch('/stripe_pay')
    .then((result) => { return result.json(); })
    .then((data) => {
        var stripe = Stripe(data.checkout_public_key);
        stripe.redirectToCheckout({
            sessionId: data.checkout_session_id
        }).then(function (result) {
        });
    })
});
```
② Ajaxを使う事例
```javascript
window.onload = () => {
    var $front_elem = document.getElementById("front");
    $front_elem.onclick = () => { 
        output_info('move to front', 1);
    };

    var $back_elem = document.getElementById("back");
    $back_elem.onclick = () => { 
        output_info('move to back', 2);
    };

    var $left_elem = document.getElementById("left");
    $left_elem.onmousedown = () => { 
        output_info('move to left', 3);
    };

    var $right_elem = document.getElementById("right");
    $right_elem.onmousedown = () => { 
        output_info('move to right', 4);
    };

    var $right_elem = document.getElementById("stop");
    $right_elem.onmousedown = () => { 
        stop(0);
    };
}
 
function stop(direction) {
    var res = JSON.stringify(
        {
            "d": direction
        }
        );
    var $info = document.getElementById('info');
    $info.innerHTML = '停止';
    $.ajax(
      {
        type:'POST',
        url: '/stop',
        data: res,
        contentType: 'application/json'
      });
}

function output_info($text_info, direction) {
    var res = JSON.stringify(
        {
            "d": direction
        }
        );
    var $info = document.getElementById('info');
    $info.innerHTML = $text_info;
    $.ajax(
      {
        type:'POST',
        url: '/move',
        data: res,
        contentType: 'application/json'
      });
}
```
③FlaskFormとwtfformsとの組み合わせで使う場合

```py
from flask_wtf import FlaskForm
from wtforms import SelectField, SubmitField
from wtforms import SubmitField, SelectMultipleField, widgets

def get_comment_data():
	module = Messager().read_data()
	return module

class MemberMonthlyForm(FlaskForm):
    # 月別ラベル取得
    start_month = SelectField('Start', choices=['2023-04','2023-05','2023-06'])
    multi = SelectMultipleField("test", choices=['abc','def','ghi'])
    submit = SubmitField('Submit')

class MultiCheckboxField(SelectMultipleField):
    widget = widgets.ListWidget(prefix_label=False)
    option_widget = widgets.CheckboxInput()

class StoreForm(FlaskForm):
    # オプション
    list_of_store = ['A店','B店','C店']
    stores = MultiCheckboxField('Label', choices=list_of_store)

    start_month = SelectField('Start', choices=['2023-04','2023-05','2023-06'])
    multi = SelectMultipleField("test", choices=['abc','def','ghi'])
    submit = SubmitField('Submit')

class SelectMultiStore(FlaskForm):
    list_of_store = ['A店 B','B店 C','C店 d','D店 /sa','E店/sad']
    stores = SelectMultipleField("店舗選択：", choices=list_of_store)
    category_li = ['区分１','区分２','区分３']
    
    category = SelectMultipleField("区分：", choices=category_li)
    submit = SubmitField('Submit')
```
HTML側の実装例
```html
<html lang=“ja”>
  <head>
    <meta charset=“UTF-8“ />
    <title>Form</title>
    <script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://unpkg.com/multiple-select@1.5.2/dist/multiple-select.min.js"></script>
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://unpkg.com/multiple-select@1.5.2/dist/multiple-select.min.css">
    <link rel="stylesheet" href="/static/table.css">
  </head>
  <body style="background: #dae5ee;">
      <h4 class="page-title">KIX: vs last year</h4>
      <div class="page-category">Page description</div>
      <form method="POST" action="/comment/{{user_name}}">
        <h3>{{form.stores}}</h3>
        <h3>{{form.category}}</h3>
				&nbsp;{{ form.submit() }}</h3>
			</form>
      <script>
        $(function () {
            $('select').multipleSelect({
              width: 400,
              selectedList: 100,
              classes:'.multi__etc',
            });
        });
        </script>
  </body>
</html>
```

#### Flask-bootstrapを使う
5年ほど前になるが、Flask-strapという使い方もあります。
但し、この書き方は、フロント側で少し書き方が変わります。
（バックエンドとフロンエンドをはっきり分けて実装する場合は、下記のように簡単に実装できる）
```html
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
```

https://qiita.com/msrks/items/88831fc265a93bd62e24