こちらの具体的な開発についての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
まずは入力フォーム部分のコードを見ていきます。
該当部分はこちら
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が2つ並んでいて、上がUsername部分。下がパスワード部分の設定になっています。
これらの項目について以下のコードに書いてあるv-card-actionsの中にあるv-btnをクリックしてデータを送信するという仕組みになっています。
<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"
この2つです。
v-modelというのは、このフォーム項目を何に対応させるのかという指定です。
詳しくはこちらを参照してみて下さい。
Vue.jsのv-modelを正しく使う
rulesは対応するバリデーションルールの指定になっています。
この二行をみて、コロンの有無が何なのか気になった人はこちらを参照。
Vue.jsに出てくるコロン(v-bind)とアットマーク(v-on)について
v-modelで指定しているのはなにか
さらにLogin.vueの下の方を探していきましょう。こちらです。
formModel: {
username: 'admin',
password: 'admin',
},
formModelというのは、値を1つのJSONに突っ込んで渡しますよという便利なブロックになっています。
adminと入っているのは初期値なので、ここを空にしておけば空欄として表示されます。
v-model="formModel.username"
というのは、formModelの中にあるusername に値を渡しますよという指定です。
パスワード側の入力フォームは
v-model="formModel.password"
となっていることが分かると思います。
補足 "$t('username')" ってなに?
画面を見るとこんな風になっています。
この文字列は何処から来たのか?と思われると思うのですが、こちらは言語ファイルにかかれています。
言語ファイルはこちらを参照。
多言語展開が可能になっていますので、必要な言語ファイルを作って設定しましょう。
言語ファイルの読み込みは
frontend/src/plugins/vuetify.jsで行っています
:rules="formRule.username"について
こちらではバリデーションルールを指定しています。
同様にLogin.vueを見ていきましょう。
フォームルールはこちらに書いてあります。
formRule: {
username: [(v) => !!v || this.$t('rule.required', ['username'])],
password: [(v) => !!v || this.$t('rule.required', ['password'])],
},
usernameとpasswordは必須項目ですよと書かれていますね。
これを入力しないとエラーが出て、送信ボタンが押せません。
データを送信しよう
ログインボタン部分のコードを見てみましょう
<v-btn large tile color="primary" :loading="loading" @click="handleLogin">
{{ $t('login') }}
</v-btn>
と書かれています。重要なのは
@click="handleLogin"
です。ボタンクリック時にhandleLoginが呼ばれますよという指定になっています。
では、handleLoginを見ていきましょう。コードはこちらです。
methodsというブロックの中に書かれています。methodsがなにかについては公式ページをみてみましょう。
該当部分のコードはこちら
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表示をする
次の行では、更に関数を読んでいます。
this.$store
.dispatch('login', this.formModel)
this.$storeってなんだろうっていうとVue.jsで持っているデータストアのことを指しています。アプリケーション稼働中に使える、簡易的なストレージと思って下さい。その中にデータを突っ込むことで、ページをリロードせずにUI側の表示を変更したりすることが出来ます。
.dispatch('login', this.formModel)
これは,第一引数で関数を呼びます。第二引数が渡すデータそのものです。
じゃぁその関数そのものは何処にあるのかというと、
frontend/src/store/modules/auth.js です。
frontend/views/auth/Login.vue から呼び出すのが
frontend/src/store/modules/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に入れてしまいましたが、1つずつ引数を入れることも出来ます。
やり方はこちらを参照して下さい。
Vuex で Action, Mutation に第3引数を渡したくなったら
Flask(python)にリクエストを投げる
このブロックでリクエストを投げています。
return request({
url: '/auth/login',
method: 'post',
data: {
username,
password,
},
})
urlに当たる部分でエンドポイントを指定し、POSTメソッドでデータブロックにデータを詰めて送信しています。
これを受け取るのがバックエンドのpythonファイルです。
ファイル自体は backend/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オブジェクトが何を示しているかというと、データベースの設定をしたこちらのファイルに書かれています。
ここに書かれているUserクラスをauth.pyの4行目でインポートして使っているのが分かります。
from .models import User
filter_byで、emailカラムが受け取ったemailと同じ
且つ
passwordカラムの内容が、受け取ったpasswordと同じであるという条件でuserを検索しています。
その後、if(user)で、userが存在していたらこれ、そうじゃなければこの処理をしてreturnするという処理が続きます。
FlaskからVue.jsにデータを返す時はjsonifyでJSON化してから送ります。
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に戻りましょう。
該当箇所はこちらです。
}).then((resp) => {
commit('SET_LOGIN', resp)
dispatch('fetchProfile',{username: username})
})
then((resp)
という部分のrespの中に、Flaskから返ってきたデータが入っています。
commit('SET_LOGIN', resp)
というのは何かというと、もう少し下の方にあるmutationsブロックの中に入っているのが見えます。
Vue.jsの見た目を変更するためのブロックです。stateの中身を書き換えるのに使います。
該当箇所はこちら
SET_LOGIN(state, { access_token, expires_in }) {
state.access_token = access_token
state.expires_in = expires_in
},
respの中身が何だったかというと、Flaskの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})
として処理を続けています。
一連の流れはこのような形で処理します。