📘

# 6.3 accounts アプリの作成とカスタムユーザモデル

に公開

今回は ユーザ管理を担う accounts アプリ を作成する。
業務システムでは「ユーザ・部署・ロール」は必ず出てくる要素なので、最初から専用アプリに切り出しておくのが望ましい。


1. アプリ作成

cd backend
python manage.py startapp accounts

👉 これで accounts/ ディレクトリが作成される。
この中にユーザ関連のモデルをすべてまとめる。


2. モデル定義

2.1 User(カスタムユーザ)

# accounts/models/user.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    full_name = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.username
  • Django 標準のユーザモデル AbstractUser を継承
  • full_name を追加(業務ではフルネーム管理が必須)
  • __str__ を username にしておくと管理画面で見やすい

2.2 部署モデル

# accounts/models/dept.py
from django.db import models

class Dept(models.Model):
    code = models.CharField(max_length=20, unique=True)
    name = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.code} {self.name}"

📌 解説

  • このモデルでは Django が自動的に id(数値の主キー)を付与しており、実際の主キーは id
  • code業務上の一意な識別子 として利用する想定(unique 制約あり)
  • 実務的には「部署コードで一意に管理する」ケースが多いが、DB のキーは数値 ID に任せたほうが扱いやすい場面も多い
    → M2M 関係や外部キー参照で bigint が自然に使われるため、クエリの型不一致を避けやすい
  • もし「部署コードそのものを主キーにしたい」なら primary_key=True を付けて id を消す方法もあるが、今回は扱いやすさ重視で数値 ID を主キーのままにしている

💡 補足
本来の業務システムでは「部署の階層構造(親部署を持つ)」をモデル化することが多い。
例:営業本部 → 東京営業部 → 第一課
ただし、今の教材段階ではまずシンプルに部署単体だけを扱うことにして、親子関係は導入していない。
後から必要になったら parent = models.ForeignKey("self", ...) を追加すれば階層構造を表現できる。


2.3 ロールモデル

# accounts/models/role.py
from django.db import models

class Role(models.Model):
    code = models.CharField(max_length=20, unique=True)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

📌 解説
部署と同様に 数値 ID を主キーにしつつ、code を業務キーに使う 形で進める

💡 補足
ロール設計はシステムごとに幅がある。
単純に「画面ごとの権限」だけでなく、「部署ごとの権限」や「承認フローのステップ」を兼ねる場合もある。
今の段階ではシンプルに codename のみで進める。


2.4 ユーザと部署の紐付け

# accounts/models/user_dept.py
from django.db import models
from .user import User
from .dept import Dept

class UserDept(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    dept = models.ForeignKey(
        Dept, to_field="code", db_column="dept_code", on_delete=models.CASCADE
    )
    is_primary = models.BooleanField(default=False)  # 主所属フラグ
    assigned_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("user", "dept")

📌 解説

  • 中間テーブルを明示的に定義し、ユーザと部署を紐付け

  • to_field="code" により 部署コードで参照する設計

    • 例:sales / tokyo のように人間が読める文字列キー
    • 組織変更などで id が変わっても code が安定していれば参照関係が崩れにくい
  • is_primary で「主所属 / 兼務」を区別可能

  • unique_together で同じ組み合わせを重複登録できないよう制約

💡 補足
一般的な実装では、以下のように id(数値主キー)を参照する方が多い。

dept = models.ForeignKey(Dept, on_delete=models.CASCADE)

この場合、DB 的には bigint 同士で結合されるので効率的だし、Django の外部キー周りとも相性が良い。
to_field="code" を使うのは、コードを安定したキーとして扱いたいとき。
自分が関わったシステムではこの方式だったので、今回はその形で書いている。


2.5 ユーザとロールの紐付け

# accounts/models/user_role.py
from django.db import models
from .user import User
from .role import Role

class UserRole(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    role = models.ForeignKey(
        Role, to_field="code", db_column="role_code", on_delete=models.CASCADE
    )
    granted_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("user", "role")

📌 解説

  • ロールも同様に コード基準で参照している
  • granted_at で「いつロールが付与されたか」を記録可能

💡 補足
こちらも実務では id 外部キーで持つ方が一般的。


2.6 モデル集約

# accounts/models/__init__.py
from .user import User
from .dept import Dept
from .role import Role
from .user_dept import UserDept
from .user_role import UserRole

👉 これにより外部からは

from accounts.models import User, Dept, Role

のように一括で呼び出せる。


3. settings.py の修正

INSTALLED_APPS = [
    ...,
    "accounts",
]

AUTH_USER_MODEL = "accounts.User"
  • INSTALLED_APPS に追加
  • カスタムユーザを使うために AUTH_USER_MODEL を指定

👉 これは プロジェクト開始時に必須。途中変更はできない。


4. 管理画面の設定

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User, Dept, Role, UserDept, UserRole

@admin.register(User)
class UserAdmin(BaseUserAdmin):
    fieldsets = BaseUserAdmin.fieldsets + (
        ("業務情報", {"fields": ("full_name",)}),
    )

admin.site.register(Dept)
admin.site.register(Role)
admin.site.register(UserDept)
admin.site.register(UserRole)
  • UserAdmin を拡張し、full_name を管理画面で編集可能にした
  • 部署・ロール・中間テーブルも登録

5. マイグレーション

python manage.py makemigrations accounts
python manage.py migrate

👉 これで DB に accounts_user, accounts_dept, accounts_role, accounts_userdept, accounts_userrole テーブルが作成される。


6. 管理ユーザーの作成

python manage.py createsuperuser

👉 メールアドレスやパスワードを入力し、admin にログイン可能なユーザを作る。


7. 動作確認

  1. 開発サーバーを起動

    python manage.py runserver
    
  2. 管理画面にアクセス
    http://127.0.0.1:8000/admin/

  3. 部署・ロールを登録

    • 部署:A001 営業一課
    • 部署:M001 製造部
    • ロール:order_approve
    • ロール:asset_manager
  4. ユーザを作成し、部署とロールを割り当てる

👉 これで「ユーザがどの部署に所属し、どのロールを持つか」が管理可能になった。


今回のゴール

  • accounts アプリを作成し、ユーザ管理を分離
  • AbstractUser 継承でカスタムユーザモデルを定義
  • 部署(Dept)、ロール(Role)、紐付けモデル(UserDept / UserRole)を実装
  • 紐付けは 部署コード / ロールコード基準 で管理
  • マイグレーション & superuser 作成 を完了し、admin にログインできた
  • 管理画面からユーザ・部署・ロールを操作可能になった

次の展開

次は 6.4 ログイン API の実装 に進む。
今回のユーザモデルを使い、ログイン時に部署・ロールを返す API を構築する。

Discussion