pythonを使ってリアクティブなWebアプリを作りたい【開発編】

こちらの具体的な開発についてのpostです。
https://qiita.com/geeorgey/items/ace37bd92841c9d1a378

開発元となるソースコードはこちらです。
https://github.com/geeorgey/vue_flask_template

今回は、フロントエンド(Vue.js)からバックエンド(Flask/python)へとデータを渡す方法と、その逆となるバックエンドからフロントエンドにデータを返す方法について書いていきます。

## フロントエンドからバックエンドにデータを渡すには
ログイン画面を例にしてみましょう。
ログイン画面のソースコードはこちらです。
https://github.com/geeorgey/vue_flask_template/blob/main/frontend/src/views/auth/Login.vue

ログイン画面はこんな形になっています
![Vue_Material_Admin_Template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/6dabe636-61e7-9902-356e-8c6ad2fdaff7.png)

まずは入力フォーム部分のコードを見ていきます。
該当部分はこちら
[frontend/views/auth/Login.vue](https://github.com/geeorgey/vue_flask_template/blob/main/frontend/src/views/auth/Login.vue)

``` js:frontend/views/auth/Login.vue
            <v-form ref="form" v-model="formValid" class="my-10" lazy-validation>
              <v-text-field
                v-model="formModel.username"
                append-icon="mdi-email"
                autocomplete="off"
                name="login"
                :label="$t('username')"
                :placeholder="$t('username')"
                type="text"
                required
                outlined
                :rules="formRule.username"
              />
              <v-text-field
                v-model="formModel.password"
                append-icon="mdi-lock"
                autocomplete="off"
                name="password"
                :label="$t('password')"
                :placeholder="$t('password')"
                type="password"
                :rules="formRule.password"
                required
                outlined
                @keyup.enter="handleLogin"
              />
            </v-form>
```

v-text-fieldが２つ並んでいて、上がUsername部分。下がパスワード部分の設定になっています。
これらの項目について以下のコードに書いてあるv-card-actionsの中にあるv-btnをクリックしてデータを送信するという仕組みになっています。


``` js:frontend/views/auth/Login.vue
          <v-card-actions>
            <v-tooltip v-for="item in socialIcons" :key="item.text" bottom>
              <template #activator="{ on, attrs }">
                <v-btn color="primary" icon v-bind="attrs" v-on="on" @click="handleSocialLogin">
                  <v-icon v-text="item.icon" />
                </v-btn>
              </template>
              <span>{{ item.text }}</span>
            </v-tooltip>
            <v-spacer />
            <v-btn large text @click="handleRegister">
              {{ $t('register') }}
            </v-btn>
            <v-btn large tile color="primary" :loading="loading" @click="handleLogin">
              {{ $t('login') }}
            </v-btn>
          </v-card-actions>
```

全部見て回ると目が回るので、最初のフォーム項目の中から重要な部分だけ抜き出します。

v-model="formModel.username"
:rules="formRule.username"

この２つです。
v-modelというのは、このフォーム項目を何に対応させるのかという指定です。
詳しくはこちらを参照してみて下さい。
[Vue.jsのv-modelを正しく使う](https://qiita.com/simezi9/items/c27d69f17d2d08722b3a)

rulesは対応するバリデーションルールの指定になっています。

この二行をみて、コロンの有無が何なのか気になった人はこちらを参照。
[Vue.jsに出てくるコロン（v-bind）とアットマーク（v-on）について](https://cyzennt.co.jp/blog/2021/04/29/vue-js%E3%81%AB%E5%87%BA%E3%81%A6%E3%81%8F%E3%82%8B%E3%82%B3%E3%83%AD%E3%83%B3%EF%BC%88v-bind%EF%BC%89%E3%81%A8%E3%82%A2%E3%83%83%E3%83%88%E3%83%9E%E3%83%BC%E3%82%AF%EF%BC%88v-on%EF%BC%89%E3%81%AB/)

## v-modelで指定しているのはなにか
さらにLogin.vueの下の方を探していきましょう。[こちらです](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/frontend/src/views/auth/Login.vue#L70)。

```Login.vue
      formModel: {
        username: 'admin',
        password: 'admin',
      },
```
formModelというのは、値を１つのJSONに突っ込んで渡しますよという便利なブロックになっています。
adminと入っているのは初期値なので、ここを空にしておけば空欄として表示されます。
v-model="formModel.username"
というのは、formModelの中にあるusername に値を渡しますよという指定です。
パスワード側の入力フォームは
v-model="formModel.password"
となっていることが分かると思います。

### 補足　"$t('username')"　ってなに？
画面を見るとこんな風になっています。
![Vue_Material_Admin_Template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/79d98f85-ceb2-a59f-c447-e56c7cce48c6.png)
この文字列は何処から来たのか？と思われると思うのですが、こちらは言語ファイルにかかれています。
言語ファイルは[こちらを参照](https://github.com/geeorgey/vue_flask_template/blob/main/frontend/src/locale/en.json)。
多言語展開が可能になっていますので、必要な言語ファイルを作って設定しましょう。
言語ファイルの読み込みは
[frontend/src/plugins/vuetify.js](https://github.com/geeorgey/vue_flask_template/blob/main/frontend/src/plugins/vuetify.js)で行っています


## :rules="formRule.username"について
こちらではバリデーションルールを指定しています。
同様にLogin.vueを見ていきましょう。
フォームルールは[こちらに書いてあります](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/frontend/src/views/auth/Login.vue#L74)。

```Login.vue
      formRule: {
        username: [(v) => !!v || this.$t('rule.required', ['username'])],
        password: [(v) => !!v || this.$t('rule.required', ['password'])],
      },
```
usernameとpasswordは必須項目ですよと書かれていますね。
これを入力しないとエラーが出て、送信ボタンが押せません。

## データを送信しよう
ログインボタン部分のコードを見てみましょう

```Login.vue
            <v-btn large tile color="primary" :loading="loading" @click="handleLogin">
              {{ $t('login') }}
            </v-btn>
```
と書かれています。重要なのは
@click="handleLogin"
です。ボタンクリック時にhandleLoginが呼ばれますよという指定になっています。

では、handleLoginを見ていきましょう。[コードはこちらです](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/frontend/src/views/auth/Login.vue#L96)。

methodsというブロックの中に書かれています。[methodsがなにかについては公式ページをみてみましょう](https://jp.vuejs.org/v2/guide/computed.html#%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3-vs-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)。

該当部分のコードはこちら

```js:Login.vue
    handleLogin() {
      if (this.$refs.form.validate()) {
        this.loading = true
        this.$store
          .dispatch('login', this.formModel)
          .then(() => {
            const redirect = this.$route.query.redirect
            const route = redirect ? { path: redirect } : { path: '/' }
            this.loading = false
            this.$router.push('/dashboard')
          })
          .catch(() => {
            window._VMA.$emit('SHOW_SNACKBAR', {
              show: true,
              text: 'Auth Failed',
              color: 'error',
            })
            this.loading = false
          })
      }
    },
```
handleLoginが呼ばれるとまずvalidateルールが通っているかどうかが問われます。
if (this.$refs.form.validate()) {
次の行では、API通信中にローディング表示をするという指令になります。現実的にはほぼ時間がかからないのでお目にかかりませんが。詳細についてはこちらを参照して下さい。
[Vue.jsでAPI通信中にloading表示をする](https://qiita.com/FrozenVoice/items/13bc09614b5f936e9b0c)

次の行では、更に関数を読んでいます。
        this.$store
          .dispatch('login', this.formModel)

this.$storeってなんだろうっていうとVue.jsで持っているデータストアのことを指しています。アプリケーション稼働中に使える、簡易的なストレージと思って下さい。その中にデータを突っ込むことで、ページをリロードせずにUI側の表示を変更したりすることが出来ます。

.dispatch('login', this.formModel)
これは,第一引数で関数を呼びます。第二引数が渡すデータそのものです。
じゃぁその関数そのものは何処にあるのかというと、
[frontend/src/store/modules/auth.js](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/frontend/src/store/modules/auth.js#L30) です。

frontend/views/auth/Login.vue から呼び出すのが
frontend/src/store/modules/auth.js
です。

では、auth.jsを見ていきます。

```auth.js
  login({ commit, dispatch }, { username, password }) {
    return request({
      url: '/auth/login',
      method: 'post',
      data: {
        username,
        password,
      },
    }).then((resp) => {
      commit('SET_LOGIN', resp)
      dispatch('fetchProfile',{username: username})
    })
  },
```

  login({ commit, dispatch }, { username, password }) {
一行目では、usernameとpasswordを引数として受け取っています。
.dispatch('login', this.formModel)
の
this.formModel
の中身は

```
      formModel: {
        username: 'admin',
        password: 'admin',
      },
```

これでしたよね。JSONが詰まっています。

### 蛇足2 dispatchで複数引数を渡す時
上述の部分ではformModelに入れてしまいましたが、１つずつ引数を入れることも出来ます。
やり方はこちらを参照して下さい。
[Vuex で Action, Mutation に第3引数を渡したくなったら
](https://mseeeen.msen.jp/deal-with-multiple-arguments-with-action-or-mutation-in-vuex/)

## Flask(python)にリクエストを投げる
このブロックでリクエストを投げています。

```auth.js
    return request({
      url: '/auth/login',
      method: 'post',
      data: {
        username,
        password,
      },
    })
```

urlに当たる部分でエンドポイントを指定し、POSTメソッドでデータブロックにデータを詰めて送信しています。
これを受け取るのがバックエンドのpythonファイルです。

ファイル自体は [backend/auth.py](https://github.com/geeorgey/vue_flask_template/blob/main/backend/auth.py)です。
[該当部分はこちら](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/backend/auth.py#L15)

```auth.py
@auth.route("/auth/login", methods=["POST"])
def login():
    email = request.json.get("username", None)
    password = request.json.get("password", None)
    user = User.query.filter_by(email=email,password=password).first()
    print('/auth/login')
    print(user)
    if user:
        return jsonify(
            access_token='test'
            ,expires_in=3600
            ), 200
    else:
        return jsonify({"message": "メールアドレスとパスワードの組み合わせが間違っています。"}), 400
```

emailとpasswordは
request.json.get("引数名", None)
という形式で受け取ります。
これを使って処理を書きましょう。

user = User.query.filter_by(email=email,password=password).first()
これは何をやっているかというと、SqlalchemyというORMを使ってpostgreSQLに問い合わせをします。
Userオブジェクトが何を示しているかというと、データベースの設定をしたこちらのファイルに書かれています。

[backend/models.py](https://github.com/geeorgey/vue_flask_template/blob/main/backend/models.py)

ここに書かれているUserクラスを[auth.pyの4行目でインポート](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/backend/auth.py#L4)して使っているのが分かります。
from .models import User

filter_byで、emailカラムが受け取ったemailと同じ
且つ
passwordカラムの内容が、受け取ったpasswordと同じであるという条件でuserを検索しています。

その後、if(user)で、userが存在していたらこれ、そうじゃなければこの処理をしてreturnするという処理が続きます。

FlaskからVue.jsにデータを返す時はjsonifyでJSON化してから送ります。

```auth.py
        return jsonify(
            access_token='test'
            ,expires_in=3600
            ), 200
```

access_tokenという項目と、expores_inという項目に値を詰めてVue.jsに返すようになっています。
これでVue.js→Flask→Vue.jsへとデータを返すことができました。

## Vue.jsで返ってきたデータを受け取り、処理をする
[frontend/src/store/modules/auth.js](https://github.com/geeorgey/vue_flask_template/blob/main/frontend/src/store/modules/auth.js)に戻りましょう。
該当箇所はこちらです。

```auth.js
    }).then((resp) => {
      commit('SET_LOGIN', resp)
      dispatch('fetchProfile',{username: username})
    })
```

then((resp)
という部分のrespの中に、Flaskから返ってきたデータが入っています。
commit('SET_LOGIN', resp)
というのは何かというと、もう少し下の方にあるmutationsブロックの中に入っているのが見えます。
Vue.jsの見た目を変更するためのブロックです。stateの中身を書き換えるのに使います。
[該当箇所はこちら](https://github.com/geeorgey/vue_flask_template/blob/d84bd263161cfdfcc45eedddf3396c94a4c26451/frontend/src/store/modules/auth.js#L74)

```auth.js
  SET_LOGIN(state, { access_token, expires_in }) {
    state.access_token = access_token
    state.expires_in = expires_in
  },
```
respの中身が何だったかというと、Flaskのauth.pyで書いてありましたこれです。

```auth.py
jsonify(
    access_token='test'
    ,expires_in=3600
    )
```

SET_LOGINにうまく合致していることが分かります。
    state.access_token = access_token
    state.expires_in = expires_in

stateの中にデータを入れることで処理完了です。

auth.jsでは更に
      dispatch('fetchProfile',{username: username})
として処理を続けています。

一連の流れはこのような形で処理します。

## 関連リンク
- [pythonを使ってリアクティブなWebアプリをお手軽に1分で作りたい](https://qiita.com/geeorgey/items/ace37bd92841c9d1a378)
- [pythonを使ってリアクティブなWebアプリを作りたい【基礎編】](https://qiita.com/geeorgey/items/3f86b207cd412680b835)
- [pythonを使ってリアクティブなWebアプリを作りたい【開発編】](https://qiita.com/geeorgey/items/ea60afe0c44a5f9db483)←イマココ
- [pythonを使ってリアクティブなWebアプリを作りたい【開発編2】](https://qiita.com/geeorgey/items/961c1d1092ec60bb06bd)
- [pythonを使ってリアクティブなWebアプリを作りたい【開発編3】](https://qiita.com/geeorgey/items/1f5544b12e7d26a1216c)
- [pythonを使ってリアクティブなWebアプリを作りたい【開発編3-2】](https://qiita.com/geeorgey/items/54b4fb22351a3e941cf6)
- [pythonを使ってリアクティブなWebアプリを作りたい【開発編4】パスワードの暗号化](https://qiita.com/geeorgey/items/85905a4772903b180fad)