11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DjangoとVueでカンバンアプリケーションを作る(3)

Last updated at Posted at 2018-09-18

※はじめからはこちら

今回はログインに応じたヘッダーメニューの変更を実装していきます。

KANBAN.png

ログイン状態を戻すAPIの実装

現在ログインしているアカウント情報を戻すAPIを実装していきます。DjangoでAPIを作る場合は以下の2つを行います。

  • 必要な結果を戻すViewロジックを実装
  • urls.pyに作成したViewとURLのマッピングを登録

順番に作っていきます。

必要な結果を戻すViewロジックを実装

application/views/apiをまずは作成します。あわせてapplication/views/api/__init__.pyも空ファイルとして作成し、Pythonのモジュールとして認識させます。

その上で、以下のファイルを作成します。

application/views/api/accounts.py
from django.http import JsonResponse
from django.views.generic import View


class AccountsApi(View):

    def get(self, request):
        """
        認証済であればログイン情報を戻す
        """
        account = request.user
        # ログイン済みかのチェック
        if account.is_authenticated:
            return JsonResponse({
                'account_info': {
                    'account_id': account.id,
                    'name': account.username,
                }
            })

        return JsonResponse({
            'account_info': None,
        })

ログインしていれば以下のようなJSONが戻されるAPIです。

{"account_info": {"account_id": 2, "name": "denzow"}}

urls.pyに作成したViewとURLのマッピングを登録

続いて、作成したAccountsApi/api/accounts/でアクセスできるようにします。

application/views/urls.py
 from django.views.generic import TemplateView

 from .accounts import signup as signup_view
+from .api.accounts import AccountsApi


 urlpatterns = [
     path('accounts/signup/', signup_view.SignUpView.as_view(), name='signup'),
     path('accounts/', include('django.contrib.auth.urls')),
     path('admin/', admin.site.urls),
+
+    path('api/accounts/', AccountsApi.as_view())
 ]
 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

ブラウザでアクセスして、JSONが戻ってくればOKです。

ログイン状態をUIに反映

先程作成したAPIをフロント側に組み込んでいきます。このチュートリアルではVueを使っています。ヘッダー部分はapplication/vuejs/src/components/layouts/MyHeader.vueというファイルになっています。ここで、ログイン名がとれていばそれを表示するといったロジックを組み込んでいきます。

API Clientの作成

まずはAPIにアクセスするユーティリティを作ります。

application/vuejs/utils/kanbanClient.js
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
import camelcaseKeys from 'camelcase-keys';


const _handleSuccess = response => {
  console.log(response.data);
  // すべてのJSONのキーをスネークケースからキャメルケースに変換する
  response.data = camelcaseKeys(response.data, { deep: true });
  return response;
};

class Client {
  constructor() {
    this.service = axios.create();
    this.service.interceptors.response.use(_handleSuccess);
    loadProgressBar({ showSpinner: false }, this.service);
  }

  _get(path, payload) {
    return this.service.get(path, payload);
  }

  _patch(path, payload) {
    return this.service.patch(path, payload);
  }

  _post(path, payload, config = {}) {
    return this.service.post(path, payload, config);
  }

  _put(path, payload) {
    return this.service.put(path, payload);
  }

  _delete(path) {
    return this.service.delete(path);
  }
}

axiosというjavascriptのHTTPClientライブラリがありますが、その薄いラッパークラスを作成します。これはPythonはスネークケース、JSはキャメルケースが基本なのでその変換を噛ませたりするためです。

続いて、追加したコードのさらに下に以下のように作成したAPIのClientを作ります。

application/vuejs/utils/kanbanClient.js
class KanbanClient extends Client {
  constructor() {
    super();
    this.baseUrl = '/api';
  }

  async getAccountInfo() {
    const response = await this._get(`${this.baseUrl}/accounts/`);
    return response.data.accountInfo;
  }
}


export default new KanbanClient();

これで、getAccountInfoを呼び出すと/api/accounts/にアクセスし、その結果をキャメルケースに変換したものが戻されるようになります。

Store(vuex)の準備

作成したAPI Clientを使うStoreを作っていきます。Vueはあるコンポーネント内に閉じた状態であれば、そのコンポーネント内の変数として保持するだけでいいのですが、コンポーネントをまたがったりする場合にはStoreというものを用意して、それを参照するようにします。コンポーネント間で共有できる大きな変数のイメージです。

また、Storeはモジュールに分けることができるのでHeader用のStoreはheaderという名前で作成します。

vuejs/src/store/header.js
import KanbanClient from '../../utils/kanbanClient';


const state = {
};

const actions = {
};

const mutations = {
};


export default {
  namespaced: true,
  state,
  actions,
  mutations,
};

まずはimportだけするStoreの雛形を用意しています。Storeはだいたい3つの要素を持ちます。

name role
state 現在のStoreの状態を示すもの
actions 何らかの非同期を含む処理、最終的にmutationを呼び出すことが多い(stateを変更することは許されていない)
mutations stateへの変更処理。非同期処理を含んではならない

コンポーネントからはActionを呼び出し、その結果をMutationに伝えてStateを更新する流れになることが多いです。このあたりは実装しながら更に見ていくことにします。

なお、このままではこのStoreは使えないのでグローバルなStore(store/index.js)に登録します。

application/vuejs/src/store/index.js
 import Vuex from 'vuex';
 import createLogger from 'vuex/dist/logger';

+import header from './header';

 Vue.use(Vuex);

@@ -11,6 +12,7 @@ export default new Vuex.Store({
     ? [createLogger()]
     : [],
   modules: {
+    header,
   },
   state: {
   },

あとはheaderのStoreを実装していきます。

StoreからのAPIClientの利用

headerのStoreを考えると、取得したアカウント情報を保持する必要があると思われるので、Stateに枠を用意します。

vuejs/src/store/header.js
const state = {
  accountInfo: null,
};

このaccountInfoに値をセットするには、今回作成したKanbanClient(APIClient)のgetAccountInfoを実行すれば良いことがわかります。この処理は非同期処理なのでActionに組み込みます。

vuejs/src/store/header.js
const actions = {
  async fetchAccountInfo({ commit }) {
    const accountInfo = await KanbanClient.getAccountInfo();
    commit('setAccountInfo', accountInfo);
  },
};

commit('setAccountInfo', accountInfo);の部分は取得したaccountInfoをもって、mutationのsetAccountInfoを呼び出すという意味です。ということでmutationも実装します。

vuejs/src/store/header.js
const mutations = {
  setAccountInfo(state, accountInfo) {
    state.accountInfo = accountInfo;
  },
};

受け取ったaccountInfostate.accountInfoにセットするだけです。これでfetchAccountInfoを呼び出すと、APIからログイン情報を取得しStoreに反映させるコードができました。

ComponentからActionの利用

先程作成したActionをMyHeaderの初期化時に呼び出すようにしていきます。まずは、Storeの値をコンポーネント自身の属性のように割り当てるためのmapState,mapActionsの準備をします。

application/vuejs/src/components/layouts/MyHeader.vue
 <script>
+import { createNamespacedHelpers } from 'vuex';
+
+const { mapState, mapActions } = createNamespacedHelpers('header');

 export default {
   name: 'MyHeader',

createNamespacedHelpers('header')headerというStoreの値をうまく扱うためのヘルパー関数であるmapState, mapActionsを手に入れます。mapStateはStoreのstateを、mapActionsはStoreのActionsとのマッピングを行うためのものです。続いてこれらのヘルパーを使います。

application/vuejs/src/components/layouts/MyHeader.vue
 export default {
   name: 'MyHeader',
+  computed: {
+    isLoggedIn() {
+      return this.accountInfo !== null;
+    },
+    ...mapState(['accountInfo']),
+  },
+  methods: {
+    ...mapActions(['fetchAccountInfo']),
+  },
 };
 </script>

mapStatecomputedmapActionsmethodsの中で使用します。それぞれ割り当てたいStoreの属性名を文字列とした配列を受け取ります。ここではstate. accountInfostate. fetchAccountInfoを指定しています。また、ログインしているかどうかを示すためaccountInfoが空かを戻すisLoggedInというメソッドも定義しておきます。

なお...はスプレッド演算子と呼ばれるもので、指定した変数を展開して代入するような動きをします。

> a = {a:1}
{ a: 1 }
> b = {b:2, a}
{ b: 2, a: { a: 1 } }
> b = {b:2, ...a}
{ b: 2, a: 1 }  // aが展開されている

さて、割当はできましたので使っていきます。以下のようにcreatedfetchAccountInfoを呼び出すようにすることで、このMyHeaderコンポーネントが初期化される際に実行されるようになります。

application/vuejs/src/components/layouts/MyHeader.vue
export default {
  name: 'MyHeader',
  computed: {
    isLoggedIn() {
      return this.accountInfo !== null;
    },
    ...mapState(['accountInfo']),
  },
  methods: {
    ...mapActions(['fetchAccountInfo']),
  },
  created() {
    this.fetchAccountInfo();
  },
};

最後に、取得している値等を表示に利用するようにします。

application/vuejs/src/components/layouts/MyHeader.vue
@@ -1,9 +1,13 @@
 <template>
   <nav class="navbar navbar-expand-lg navbar-light bg-light">
-    <router-link class="navbar-brand" to="/">Django</router-link>
+    <router-link class="navbar-brand" to="/">KANBAN</router-link>

     <div class="collapse navbar-collapse" id="navbarNavDropdown">
-      <ul class="navbar-nav ml-auto">
+      <ul class="navbar-nav ml-auto" v-if="isLoggedIn">
+        <span class="navbar-text mr-1">Welcome to {{ accountInfo.name }}</span>
+        <a class="btn btn-outline-primary" href="/accounts/logout/">Logout</a>
+      </ul>
+      <ul class="navbar-nav ml-auto" v-if="!isLoggedIn">
         <li class="nav-item">
           <a class="nav-link" href="#">Register</a>
         </li>

v-if="isLoggedIn"の箇所により、ログイン済であれば、Welcome to testといったメッセージとログアウトボタンをだし、そうでなければRegsiter/Loginのリンクを出すコードになりました。

再度http://localhost:3000/にアクセスし以下のようにログインアカウント名が出れば成功です。

KANBAN.png

次回

11
8
0

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
11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?