Django 自訂使用者模型:AbstractUser 與 AbstractBaseUser | Django 教學

2026/06/19 2026/05/25
Django 自訂使用者模型:AbstractUser 與 AbstractBaseUser | Django 教學

Django 內建的 User 模型雖然方便,但在實際專案中往往無法滿足所有需求——你可能需要新增手機號碼欄位,或是改用 Email 取代 username 作為登入帳號。Django 提供了兩種自訂使用者模型的方式: AbstractUserAbstractBaseUser 。本篇將完整介紹這兩種方式的使用時機、實作方法,以及搭配 AUTH_USER_MODEL 設定、 Signal 建立 Profile、 django-allauth 整合等實務技巧。

為什麼需要自訂 User Model

Django 內建的 User 模型提供了 usernameemailpasswordfirst_namelast_name 等基本欄位。但在真實專案中,你幾乎一定會遇到以下需求:

  • 需要額外的欄位:手機號碼、頭像、生日、會員等級
  • 需要改變登入方式:用 Email 登入而非 username
  • 需要整合企業內部的帳號系統

Django 官方在文件中 強烈建議 :即使你目前不需要額外欄位,也要在專案 一開始 就建立自訂 User Model。原因很簡單—— AUTH_USER_MODEL 必須在第一次執行 migrate 之前設定,中途修改會導致極難修復的 Migration 衝突

AbstractUser 與 AbstractBaseUser 的差異

面向 AbstractUser AbstractBaseUser
繼承內容 完整 User 欄位(username、email、first_name 等) 最小化欄位(password、last_login)
客製化程度 中(在現有欄位上新增) 高(完全重寫欄位結構)
適用場景 需要額外欄位但保留 username 登入 需要以 Email 或其他欄位作為登入識別
實作複雜度 高(需自訂 Manager)

AbstractUser 擴展(新增欄位)

AbstractUser 是最簡單的自訂方式。它繼承了內建 User 的所有欄位和行為,你只需要在上面新增自訂欄位即可。

建立自訂 User Model

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

class User(AbstractUser):
    """擴展內建 User Model,新增自訂欄位"""
    bio = models.TextField('個人簡介', blank=True, default='')
    avatar = models.ImageField('頭像', upload_to='avatars/', blank=True, null=True)
    phone = models.CharField('手機號碼', max_length=20, blank=True, default='')
    birthday = models.DateField('生日', blank=True, null=True)
    is_premium = models.BooleanField('Premium 會員', default=False)

    class Meta:
        verbose_name = '使用者'
        verbose_name_plural = '使用者'

    def get_display_name(self):
        """取得顯示名稱:優先使用全名,否則使用 username"""
        return self.get_full_name() or self.username

設定 AUTH_USER_MODEL

settings.py 中指定你的自訂 User Model:

# settings.py
AUTH_USER_MODEL = 'accounts.User'  # 格式:app_label.ModelName

最重要的一點 :這個設定必須在專案第一次執行 python manage.py migrate 之前 完成。

建立並執行 Migration

# 建立 migration 檔案
python manage.py makemigrations accounts

# 執行 migration
python manage.py migrate

在 Admin 中註冊

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User

@admin.register(User)
class UserAdmin(BaseUserAdmin):
    # 在預設的 fieldsets 基礎上新增自訂欄位
    fieldsets = BaseUserAdmin.fieldsets + (
        ('自訂欄位', {
            'fields': ('bio', 'avatar', 'phone', 'birthday', 'is_premium'),
        }),
    )

    # 新增使用者時顯示的欄位
    add_fieldsets = BaseUserAdmin.add_fieldsets + (
        ('額外資訊', {
            'fields': ('email', 'phone'),
        }),
    )

    list_display = ['username', 'email', 'phone', 'is_premium', 'is_staff']
    list_filter = BaseUserAdmin.list_filter + ('is_premium',)

AbstractBaseUser 完全自訂

當你需要完全掌控使用者模型的結構時——例如以 Email 作為主要登入識別欄位而非 username—— AbstractBaseUser 是正確的選擇。它只提供最基本的 passwordlast_login 欄位,其餘全部由你自行定義。

自訂 UserManager

使用 AbstractBaseUser 時,你必須自訂 UserManager(使用者管理器) ,至少實作 create_user()create_superuser() 兩個方法:

# accounts/managers.py
from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        """建立一般使用者"""
        if not email:
            raise ValueError('Email 為必填欄位')

        # normalize_email 會將 @ 後面的域名轉為小寫
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)  # 雜湊處理密碼
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        """建立超級使用者"""
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('超級使用者必須設定 is_staff=True')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('超級使用者必須設定 is_superuser=True')

        return self.create_user(email, password, **extra_fields)

定義 User Model

# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from .managers import UserManager

class User(AbstractBaseUser, PermissionsMixin):
    """以 Email 作為登入識別欄位的自訂 User Model"""
    email = models.EmailField('Email', unique=True)
    full_name = models.CharField('姓名', max_length=100, blank=True)
    phone = models.CharField('手機號碼', max_length=20, blank=True, default='')
    is_active = models.BooleanField('啟用', default=True)
    is_staff = models.BooleanField('管理員', default=False)
    date_joined = models.DateTimeField('加入日期', auto_now_add=True)

    objects = UserManager()

    USERNAME_FIELD = 'email'            # 登入時使用的識別欄位
    REQUIRED_FIELDS = ['full_name']     # createsuperuser 指令時的必填欄位

    class Meta:
        verbose_name = '使用者'
        verbose_name_plural = '使用者'

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.full_name

    def get_short_name(self):
        return self.full_name.split()[0] if self.full_name else self.email

關鍵設定說明:

  • USERNAME_FIELD:指定登入時用來識別使用者的欄位。設為 'email' 表示使用 Email 登入
  • REQUIRED_FIELDScreatesuperuser 管理指令會額外詢問的欄位(不包含 USERNAME_FIELDpassword,它們一定會問)
  • PermissionsMixin:提供 is_superusergroupsuser_permissions 等權限相關欄位

同樣需要在 settings.py 中設定:

# settings.py
AUTH_USER_MODEL = 'accounts.User'

在程式碼中正確引用 User Model

無論你使用哪種自訂方式,在程式碼中引用 User Model 時都應該使用 get_user_model() 而非直接 import,確保引用的是你設定的自訂模型:

# 正確做法:使用 get_user_model()
from django.contrib.auth import get_user_model

User = get_user_model()

# 在 ForeignKey 中引用
class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,  # 使用 settings 字串引用
        on_delete=models.CASCADE,
    )

# 錯誤做法:直接 import 內建 User
# from django.contrib.auth.models import User  # 不要這樣做

post_save Signal 建立 Profile

在許多專案中,除了 User Model 本身的欄位外,還需要額外的 Profile(個人檔案) 資料。常見的做法是建立一個獨立的 Profile Model,並透過 post_save Signal 在使用者建立時自動產生對應的 Profile。

定義 Profile Model

# accounts/models.py
class UserProfile(models.Model):
    """使用者個人檔案(一對一關聯)"""
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='profile'
    )
    bio = models.TextField('自我介紹', blank=True, default='')
    website = models.URLField('個人網站', blank=True, default='')
    location = models.CharField('所在地', max_length=100, blank=True, default='')
    twitter_handle = models.CharField('Twitter', max_length=50, blank=True, default='')

    class Meta:
        verbose_name = '個人檔案'
        verbose_name_plural = '個人檔案'

    def __str__(self):
        return f"{self.user} 的個人檔案"

使用 Signal 自動建立 Profile

# accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from .models import UserProfile

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
    """新建 User 時自動建立 UserProfile"""
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def save_user_profile(sender, instance, **kwargs):
    """儲存 User 時同步儲存 UserProfile"""
    if hasattr(instance, 'profile'):
        instance.profile.save()

在 AppConfig.ready() 中載入 Signal

Signal 必須在 Django 完全啟動後才能連接,正確的載入方式是在 AppConfig.ready() 中匯入:

# accounts/apps.py
from django.apps import AppConfig

class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    def ready(self):
        import accounts.signals  # noqa: F401

使用時,可以透過 related_name 直接存取:

# 取得使用者的 Profile
user = User.objects.get(email='alice@example.com')
print(user.profile.bio)
print(user.profile.location)

# 更新 Profile
user.profile.bio = '我是一名 Django 開發者'
user.profile.save()

django-allauth 整合簡介

django-allauth 是 Django 生態系中最受歡迎的第三方認證套件,它提供了完整的 社交登入(Social Login) 、Email 驗證、多帳號連結等功能。

安裝與基本設定

pip install django-allauth
# settings.py
INSTALLED_APPS = [
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    'allauth.socialaccount.providers.github',
    # ...
]

SITE_ID = 1

AUTHENTICATION_BACKENDS = [
    'allauth.account.auth_backends.AuthenticationBackend',
]

MIDDLEWARE = [
    # ...原有 Middleware
    'allauth.account.middleware.AccountMiddleware',
]

# allauth 設定
ACCOUNT_EMAIL_REQUIRED = True                    # 註冊時 Email 為必填
ACCOUNT_AUTHENTICATION_METHOD = 'email'          # 使用 Email 登入
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'         # 強制 Email 驗證
ACCOUNT_UNIQUE_EMAIL = True                       # Email 必須唯一
# urls.py
urlpatterns = [
    path('accounts/', include('allauth.urls')),
    # ...
]

社交登入設定(以 Google 為例)

# settings.py
SOCIALACCOUNT_PROVIDERS = {
    'google': {
        'APP': {
            'client_id': 'your-google-client-id',
            'secret': 'your-google-secret',
        },
        'SCOPE': ['profile', 'email'],
        'AUTH_PARAMS': {'access_type': 'online'},
    }
}

django-allauth 會自動處理 OAuth 流程,使用者點擊「Google 登入」後,完成授權就會自動建立帳號或綁定既有帳號。


遷移注意事項

自訂 User Model 涉及到資料庫的核心表格,以下是常見的陷阱與注意事項。

一定要在第一次 migrate 前設定

# settings.py(在 migrate 之前設定)
AUTH_USER_MODEL = 'accounts.User'

如果你忘了在第一次 migrate 前設定,後續修改會遇到以下問題:

  • Django 已經建立了 auth_user 表格
  • 新的自訂 User Model 會嘗試建立另一張表格
  • 所有指向 auth.User 的 ForeignKey 都會出錯
  • Migration 衝突極難修復

既有專案的遷移策略

如果你的專案已經在運作,需要從內建 User 切換到自訂 User Model,建議的步驟是:

  1. 評估影響範圍:盤點所有引用 auth.User 的 Model、View 和 Form
  2. 備份資料庫:這是最重要的一步
  3. 建立自訂 User Model:先繼承 AbstractUser 但不加任何新欄位
  4. 修改所有引用:將所有 auth.User 替換為 settings.AUTH_USER_MODELget_user_model()
  5. 處理 Migration:這是最複雜的部分,可能需要手動編輯 migration 檔案

由於遷移過程風險極高,實務上如果專案還在初期,建議直接重建資料庫比嘗試遷移更安全。

使用 get_user_model() 而非硬編碼

# 在 models.py 中
from django.conf import settings

class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,  # 字串引用,不直接 import
        on_delete=models.CASCADE,
    )

# 在 views.py、forms.py 等其他地方
from django.contrib.auth import get_user_model

User = get_user_model()  # 動態取得當前使用的 User Model
users = User.objects.all()

總結

自訂 User Model 是 Django 專案架構中的關鍵決策,正確的做法能讓你的專案在未來保持彈性與擴展性。以下整理本篇的核心要點:

  1. 務必在專案初始化時就自訂 User Model ,即使暫時不需要額外欄位,也至少繼承 AbstractUser 建立一個空的自訂模型,避免未來遷移的痛苦
  2. AbstractUser 適合在既有欄位上新增自訂欄位,實作簡單、風險低,是大多數專案的首選
  3. AbstractBaseUser 適合需要完全重新設計使用者結構的場景(如 Email 登入),需要自訂 UserManager,實作複雜度較高
  4. AUTH_USER_MODEL 必須在第一次 migrate 前設定,中途修改將導致嚴重的 Migration 衝突
  5. post_save Signal 搭配 Profile Model 是管理擴展使用者資料的常見模式,保持 User Model 精簡
  6. django-allauth 提供完整的社交登入與 Email 驗證功能,是第三方認證整合的首選套件
  7. 程式碼中引用 User 時永遠使用 get_user_model()settings.AUTH_USER_MODEL,不要直接 import 內建 User

下一篇我們將探討 Django 的 Signals(信號機制) ,學習如何透過觀察者模式實現應用程式元件之間的解耦通訊。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。