※はじめからはこちら
今回はログインに応じたヘッダーメニューの変更を実装していきます。
ログイン状態を戻すAPIの実装
現在ログインしているアカウント情報を戻すAPIを実装していきます。DjangoでAPIを作る場合は以下の2つを行います。
- 必要な結果を戻すViewロジックを実装
- urls.pyに作成したViewとURLのマッピングを登録
順番に作っていきます。
必要な結果を戻すViewロジックを実装
application/views/api
をまずは作成します。あわせてapplication/views/api/__init__.py
も空ファイルとして作成し、Pythonのモジュールとして認識させます。
その上で、以下のファイルを作成します。
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/
でアクセスできるようにします。
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にアクセスするユーティリティを作ります。
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を作ります。
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という名前で作成します。
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)に登録します。
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に枠を用意します。
const state = {
accountInfo: null,
};
このaccountInfo
に値をセットするには、今回作成したKanbanClient(APIClient)のgetAccountInfo
を実行すれば良いことがわかります。この処理は非同期処理なのでActionに組み込みます。
const actions = {
async fetchAccountInfo({ commit }) {
const accountInfo = await KanbanClient.getAccountInfo();
commit('setAccountInfo', accountInfo);
},
};
commit('setAccountInfo', accountInfo);
の部分は取得したaccountInfo
をもって、mutationのsetAccountInfo
を呼び出すという意味です。ということでmutationも実装します。
const mutations = {
setAccountInfo(state, accountInfo) {
state.accountInfo = accountInfo;
},
};
受け取ったaccountInfo
をstate.accountInfo
にセットするだけです。これでfetchAccountInfo
を呼び出すと、APIからログイン情報を取得しStoreに反映させるコードができました。
ComponentからActionの利用
先程作成したActionをMyHeader
の初期化時に呼び出すようにしていきます。まずは、Storeの値をコンポーネント自身の属性のように割り当てるためのmapState,mapActions
の準備をします。
<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とのマッピングを行うためのものです。続いてこれらのヘルパーを使います。
export default {
name: 'MyHeader',
+ computed: {
+ isLoggedIn() {
+ return this.accountInfo !== null;
+ },
+ ...mapState(['accountInfo']),
+ },
+ methods: {
+ ...mapActions(['fetchAccountInfo']),
+ },
};
</script>
mapState
はcomputed
、mapActions
はmethods
の中で使用します。それぞれ割り当てたいStoreの属性名を文字列とした配列を受け取ります。ここではstate. accountInfo
とstate. 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が展開されている
さて、割当はできましたので使っていきます。以下のようにcreated
でfetchAccountInfo
を呼び出すようにすることで、このMyHeaderコンポーネントが初期化される際に実行されるようになります。
export default {
name: 'MyHeader',
computed: {
isLoggedIn() {
return this.accountInfo !== null;
},
...mapState(['accountInfo']),
},
methods: {
...mapActions(['fetchAccountInfo']),
},
created() {
this.fetchAccountInfo();
},
};
最後に、取得している値等を表示に利用するようにします。
@@ -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/
にアクセスし以下のようにログインアカウント名が出れば成功です。
次回