Django 自訂使用者模型:AbstractUser 與 AbstractBaseUser | Django 教學
Django 內建的
User模型雖然方便,但在實際專案中往往無法滿足所有需求——你可能需要新增手機號碼欄位,或是改用 Email 取代 username 作為登入帳號。Django 提供了兩種自訂使用者模型的方式: AbstractUser 與 AbstractBaseUser 。本篇將完整介紹這兩種方式的使用時機、實作方法,以及搭配 AUTH_USER_MODEL 設定、 Signal 建立 Profile、 django-allauth 整合等實務技巧。
為什麼需要自訂 User Model
Django 內建的 User 模型提供了 username、email、password、first_name、last_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 是正確的選擇。它只提供最基本的 password 和 last_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_FIELDS:
createsuperuser管理指令會額外詢問的欄位(不包含USERNAME_FIELD和password,它們一定會問) - PermissionsMixin:提供
is_superuser、groups、user_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,建議的步驟是:
- 評估影響範圍:盤點所有引用
auth.User的 Model、View 和 Form - 備份資料庫:這是最重要的一步
- 建立自訂 User Model:先繼承
AbstractUser但不加任何新欄位 - 修改所有引用:將所有
auth.User替換為settings.AUTH_USER_MODEL或get_user_model() - 處理 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 專案架構中的關鍵決策,正確的做法能讓你的專案在未來保持彈性與擴展性。以下整理本篇的核心要點:
- 務必在專案初始化時就自訂 User Model ,即使暫時不需要額外欄位,也至少繼承
AbstractUser建立一個空的自訂模型,避免未來遷移的痛苦 - AbstractUser 適合在既有欄位上新增自訂欄位,實作簡單、風險低,是大多數專案的首選
- AbstractBaseUser 適合需要完全重新設計使用者結構的場景(如 Email 登入),需要自訂
UserManager,實作複雜度較高 - AUTH_USER_MODEL 必須在第一次
migrate前設定,中途修改將導致嚴重的 Migration 衝突 - post_save Signal 搭配 Profile Model 是管理擴展使用者資料的常見模式,保持 User Model 精簡
- django-allauth 提供完整的社交登入與 Email 驗證功能,是第三方認證整合的首選套件
- 程式碼中引用 User 時永遠使用
get_user_model()或settings.AUTH_USER_MODEL,不要直接 import 內建 User
下一篇我們將探討 Django 的 Signals(信號機制) ,學習如何透過觀察者模式實現應用程式元件之間的解耦通訊。