LoginSignup
28
36

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-07-07

こちらの具体的な開発についての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

まずは入力フォーム部分のコードを見ていきます。
該当部分はこちら
frontend/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をクリックしてデータを送信するという仕組みになっています。

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"

この2つです。
v-modelというのは、このフォーム項目を何に対応させるのかという指定です。
詳しくはこちらを参照してみて下さい。
Vue.jsのv-modelを正しく使う

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

この二行をみて、コロンの有無が何なのか気になった人はこちらを参照。
Vue.jsに出てくるコロン(v-bind)とアットマーク(v-on)について

v-modelで指定しているのはなにか

さらにLogin.vueの下の方を探していきましょう。こちらです

Login.vue
      formModel: {
        username: 'admin',
        password: 'admin',
      },

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

補足 "$t('username')" ってなに?

画面を見るとこんな風になっています。
Vue_Material_Admin_Template.png
この文字列は何処から来たのか?と思われると思うのですが、こちらは言語ファイルにかかれています。
言語ファイルはこちらを参照
多言語展開が可能になっていますので、必要な言語ファイルを作って設定しましょう。
言語ファイルの読み込みは
frontend/src/plugins/vuetify.jsで行っています

:rules="formRule.username"について

こちらではバリデーションルールを指定しています。
同様にLogin.vueを見ていきましょう。
フォームルールはこちらに書いてあります

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を見ていきましょう。コードはこちらです

methodsというブロックの中に書かれています。methodsがなにかについては公式ページをみてみましょう

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

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表示をする

次の行では、更に関数を読んでいます。
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を見ていきます。

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)にリクエストを投げる

このブロックでリクエストを投げています。

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

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

ファイル自体は backend/auth.pyです。
該当部分はこちら

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

ここに書かれているUserクラスをauth.pyの4行目でインポートして使っているのが分かります。
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に戻りましょう。
該当箇所はこちらです。

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

then((resp)
という部分のrespの中に、Flaskから返ってきたデータが入っています。
commit('SET_LOGIN', resp)
というのは何かというと、もう少し下の方にあるmutationsブロックの中に入っているのが見えます。
Vue.jsの見た目を変更するためのブロックです。stateの中身を書き換えるのに使います。
該当箇所はこちら

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})
として処理を続けています。

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

関連リンク

28
36
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
36