# 5.5 Pinia(状態管理)と認証ストア拡張
前回は Vue から Django REST Framework の API を呼び出し、一覧データを表示するところまで確認した。
今回は次のステップとして、認証情報を Pinia ストアに保持する仕組み を導入する。
さらに /auth/status/ API を拡張し、ユーザーの 部署(dept)・ロール(role) も返すようにして、メニュー制御につなげていく。
1. /auth/status/ API の設計
Django 側でログイン確認用の API を実装する。
最低限返すのは以下の情報:
-
username(ログインID) -
full_name(表示名) -
is_staff(管理者フラグ) -
departments(所属部署リスト) -
roles(付与ロールリスト)
レスポンス例:
{
"username": "taro",
"full_name": "山田 太郎",
"is_staff": true,
"departments": [
{ "code": "A001", "name": "営業一課" },
{ "code": "A002", "name": "営業二課" }
],
"roles": ["order_approve", "asset_manager"]
}
📌 解説
- 単にユーザー名だけでなく、部署とロールを同時に返すのがポイント。
- フロント側ではこの情報を基に「誰にどのメニューを見せるか」を制御できる。
部署情報と兼務の扱い
実務では「1人1部署」ではなく、複数部署を兼務するユーザー が存在する。
"departments": [
{ "code": "A001", "name": "営業一課" },
{ "code": "A002", "name": "営業二課" }
]
- 主所属:営業一課
- 兼務所属:営業二課
📌 解説
- 兼務情報を返しておくと、Vue 側で「どの部署で操作中か」を切り替える UI を作れる。
- 単一所属のユーザーはセレクト不要、複数部署の場合のみ選択 UI を出せばよい。
Vue 側での対応例:
<v-select
v-if="auth.departments.length > 1"
:items="auth.departments"
item-title="name"
item-value="code"
v-model="currentDept"
label="操作中の部署を選択"
/>
📌 解説
- 部署が1つなら選択 UI は非表示、複数ならセレクトを出す。
-
item-title="name"/item-value="code"によって、Vue 側で表示名とコードを分けて管理できる。
2. Pinia ストアの拡張
src/store/auth.js を以下のように修正する。
import { defineStore } from "pinia";
import apiClient from "@/plugins/axios";
export const useAuthStore = defineStore("auth", {
state: () => ({
user: null,
departments: [],
roles: [],
loading: false,
error: "",
}),
actions: {
async fetchUser() {
this.loading = true;
this.error = "";
try {
const res = await apiClient.get("/auth/status/");
this.user = {
username: res.data.username,
full_name: res.data.full_name,
is_staff: res.data.is_staff,
};
this.departments = res.data.departments || [];
this.roles = res.data.roles || [];
} catch {
this.error = "ログイン情報を取得できませんでした";
} finally {
this.loading = false;
}
},
logout() {
this.user = null;
this.departments = [];
this.roles = [];
},
},
getters: {
isLoggedIn: (state) => !!state.user,
username: (state) => state.user?.full_name || "",
hasRole: (state) => (role) => state.roles.includes(role),
belongsTo: (state) => (deptCode) =>
state.departments.some((d) => d.code === deptCode),
},
});
📌 解説
-
state に
user/departments/rolesを持たせる。 -
actions
-
fetchUser()…/auth/status/を叩いてログイン情報を取得。 -
logout()… state をクリア。
-
-
getters
-
isLoggedIn… ログイン状態判定。 -
username… フルネームを返す。 -
hasRole(role)… 特定のロールを持っているか。 -
belongsTo(deptCode)… 特定の部署に所属しているか。
-
👉 こうすることで、Vue 側では 簡単に条件判定 ができるようになる。
3. Vue 側での利用例
<template>
<div>
<p v-if="auth.isLoggedIn">
{{ auth.username }} さん(所属: {{ auth.departments[0]?.name }})
</p>
<v-btn v-if="auth.hasRole('order_approve')" color="primary">
受注承認
</v-btn>
</div>
</template>
<script setup>
import { useAuthStore } from "@/store/auth";
const auth = useAuthStore();
auth.fetchUser();
</script>
📌 解説
-
auth.isLoggedInでログイン済みか確認。 -
auth.usernameで表示名を取得。 -
auth.hasRole('order_approve')でロール判定し、承認ボタンを表示。 -
auth.fetchUser()を起動時に実行してユーザー情報を反映。
4. 実務的なポイント
-
部署とロールはまとめて返す
→ Vue 側で「部署 A かつ role B」のような複雑条件を簡単に制御できる。 -
getter で判定を共通化
→ 各画面で毎回.includes("role")と書くのは非効率。 -
キャッシュ化
→/auth/status/はアプリ起動時に1回叩くのが基本。
→ 頻繁に呼ばず、Pinia に保持して利用する。
💡 AI 活用のポイント
-
App.vue での初期化前提を忘れられる
実務ではApp.vue起動時に/auth/status/を必ず叩いて、Pinia にユーザー情報を保持しておくのが基本。
しかし AI はこの前提をよく忘れ、各コンポーネントでauth()を直接呼ぶコードを生成することがある。
👉 「認証情報はApp.vueで初期化済み」「Pinia に保存されているものを使う」と強調して指示することが必要なケースがある。 -
既存のストアを無視して新規に書き出すことがある
authストアがあるにも関わらず、AI が再度useAuthStore()を定義し直したり、ローカル state を作ってしまうことがある。
👉 「既存のauthストアを必ず利用して」と条件をつけると回避しやすい。 -
レスポンス構造が勝手に解釈される
/auth/status/がdepartments: [],roles: []の配列を返す前提なのに、AI が 単一オブジェクトや文字列を扱うコードを生成してしまうことがある。
👉 「departments と roles は必ず配列で返る」と念押ししておかないと、フロントコードが食い違う。 -
たちが悪いのは “部分的に正しいコード” を混ぜてくること
一見正しそうな実装でも、よく見ると「ストアを再定義している」「配列を配列として扱っていない」などの地雷が混ざる。
👉 生成コードをそのままコピペせず、必ず既存の前提と突き合わせて確認することが必須。
まとめ
-
/auth/status/API を拡張し、部署・ロール情報も返すようにした - Pinia ストアを使い、認証情報を全画面で共有可能にした
- Vue 側からは
auth.hasRole()やauth.belongsTo()を呼ぶだけで判定可能 - これにより メニュー制御や画面制御の基盤 が整った
👉 次回はこの仕組みを利用して、部署・ロールごとのメニュー制御 を実装していく。
Discussion