在 Django 中自訂驗證¶
Django 內建的驗證功能已足夠應付大多數常見情況,但您可能會有一些預設值無法滿足的需求。在您的專案中自訂驗證需要了解所提供系統的哪些點是可擴展或可替換的。本文件詳細說明如何自訂驗證系統。
驗證後端提供一個可擴展的系統,用於當需要針對 Django 預設以外的不同服務驗證儲存在使用者模型中的使用者名稱和密碼時。
您可以給您的模型自訂權限,這些權限可以透過 Django 的授權系統進行檢查。
您可以擴展預設的 User
模型,或者替代一個完全自訂的模型。
其他驗證來源¶
有時您可能需要掛接到另一個驗證來源 – 也就是其他使用者名稱和密碼或驗證方法的來源。
例如,您的公司可能已經有一個 LDAP 設定,為每位員工儲存使用者名稱和密碼。如果使用者在 LDAP 和基於 Django 的應用程式中擁有單獨的帳戶,對於網路管理員和使用者本身來說都會很麻煩。
因此,為了處理這種情況,Django 驗證系統可讓您插入其他驗證來源。您可以覆蓋 Django 基於預設資料庫的方案,或者您可以將預設系統與其他系統一起使用。
請參閱驗證後端參考,以了解 Django 包含的驗證後端的資訊。
指定驗證後端¶
在幕後,Django 維護一個「驗證後端」的列表,它會檢查這些後端以進行驗證。當有人呼叫 django.contrib.auth.authenticate()
時 – 如如何登入使用者中所述 – Django 會嘗試在其所有驗證後端上進行驗證。如果第一個驗證方法失敗,Django 會嘗試第二個方法,依此類推,直到嘗試過所有後端。
要使用的驗證後端列表在 AUTHENTICATION_BACKENDS
設定中指定。這應該是指向知道如何驗證的 Python 類別的 Python 路徑名稱列表。這些類別可以位於您 Python 路徑上的任何位置。
預設情況下,AUTHENTICATION_BACKENDS
設定為
["django.contrib.auth.backends.ModelBackend"]
這是基本的驗證後端,它會檢查 Django 使用者資料庫並查詢內建權限。它沒有提供透過任何速率限制機制來防止暴力攻擊的保護。您可以實作自己的速率限制機制在自訂驗證後端中,或使用大多數網頁伺服器提供的機制。
AUTHENTICATION_BACKENDS
的順序很重要,因此如果相同的使用者名稱和密碼在多個後端中有效,Django 會在第一個正向匹配時停止處理。
如果後端引發 PermissionDenied
例外,驗證會立即失敗。Django 不會檢查後續的後端。
注意
一旦使用者通過驗證,Django 會在使用者會話中儲存用於驗證使用者的後端,並且只要需要存取目前經過驗證的使用者,就會在該會話期間重複使用相同的後端。這實際上表示驗證來源是基於每個會話進行快取的,因此如果您更改 AUTHENTICATION_BACKENDS
,如果您需要強制使用者使用不同的方法重新驗證,則需要清除會話資料。一種簡單的方法是執行 Session.objects.all().delete()
。
撰寫驗證後端¶
驗證後端是一個類別,它實作兩個必要的方法:get_user(user_id)
和 authenticate(request, **credentials)
,以及一組可選的權限相關授權方法。
get_user
方法接受一個 user_id
– 可以是使用者名稱、資料庫 ID 或任何東西,但必須是您的使用者物件的主鍵 – 並傳回一個使用者物件或 None
。
authenticate
方法接受一個 request
引數和憑證作為關鍵字引數。大多數情況下,它會看起來像這樣
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Check the username/password and return a user.
...
但它也可以驗證權杖,如下所示
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
# Check the token and return a user.
...
無論哪種方式,authenticate()
都應該檢查它收到的憑證,如果憑證有效,則傳回一個與這些憑證相符的使用者物件。如果憑證無效,則應該傳回 None
。
request
是 HttpRequest
,如果沒有將其提供給 authenticate()
(將其傳遞給後端),則可以是 None
。
Django 管理員與 Django 使用者物件緊密耦合。例如,若要讓使用者存取管理員,User.is_staff
和 User.is_active
必須為 True
(詳情請參閱 AdminSite.has_permission()
)。
處理此問題的最佳方法是為每個存在於您的後端的使用者 (例如,在您的 LDAP 目錄、您的外部 SQL 資料庫等中) 建立一個 Django User
物件。您可以編寫指令碼預先執行此操作,或者您的 authenticate
方法可以在使用者第一次登入時執行此操作。
以下是一個範例後端,它會針對在您的 settings.py
檔案中定義的使用者名稱和密碼變數進行驗證,並在使用者第一次驗證時建立 Django User
物件。在此範例中,建立的 Django User
物件是一個超級使用者,他將擁有管理員的完全存取權
from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class SettingsBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None):
login_valid = settings.ADMIN_LOGIN == username
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. There's no need to set a password
# because only the password from settings.py is checked.
user = User(username=username) # is_active defaults to True.
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
自訂權限¶
要為給定的模型物件建立自訂權限,請使用 permissions
模型 Meta 屬性。
此 Task
模型範例會建立兩個自訂權限,也就是使用者可以使用或不能使用 Task
實例執行的動作,這些動作是您的應用程式特有的
class Task(models.Model):
...
class Meta:
permissions = [
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
]
這唯一的作用是在您執行 manage.py migrate
時建立這些額外的權限(建立權限的函數連接到 post_migrate
信號)。您的程式碼負責在使用者嘗試存取應用程式提供的功能時檢查這些權限的值(變更任務的狀態或關閉任務)。繼續上面的範例,以下檢查使用者是否可以關閉任務
user.has_perm("app.close_task")
擴充現有的 User
模型¶
有兩種方法可以擴充預設的 User
模型,而無需替換您自己的模型。如果您的變更純粹是行為上的,並且不需要變更資料庫中儲存的內容,則可以建立基於 User
的 代理模型。這允許使用代理模型提供的任何功能,包括預設排序、自訂管理器或自訂模型方法。
如果您希望儲存與 User
相關的資訊,您可以使用 OneToOneField
指向包含額外資訊欄位的模型。此一對一模型通常稱為設定檔模型,因為它可能會儲存有關網站使用者的非驗證相關資訊。例如,您可以建立一個 Employee 模型
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)
假設現有的員工 Fred Smith 同時擁有 User 和 Employee 模型,您可以使用 Django 的標準相關模型慣例來存取相關資訊
>>> u = User.objects.get(username="fsmith")
>>> freds_department = u.employee.department
若要將設定檔模型的欄位新增至管理頁面中的使用者頁面,請在您的應用程式的 admin.py
中定義一個 InlineModelAdmin
(在此範例中,我們將使用 StackedInline
),並將其新增至使用 User
類別註冊的 UserAdmin
類別
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from my_user_profile_app.models import Employee
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = "employee"
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [EmployeeInline]
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
這些設定檔模型在任何方面都不是特殊的 - 它們只是恰好與使用者模型具有一對一連結的 Django 模型。因此,它們不會在建立使用者時自動建立,但可以使用 django.db.models.signals.post_save
來適當地建立或更新相關模型。
使用相關模型會導致額外的查詢或聯結來擷取相關資料。根據您的需求,包含相關欄位的自訂使用者模型可能是您更好的選擇,但是,您專案應用程式內與預設使用者模型的現有關係可能會使額外的資料庫負載合理化。
替換自訂 User
模型¶
某些專案可能具有 Django 的內建 User
模型並不總是適用的驗證需求。例如,在某些網站上,使用電子郵件地址作為您的識別令牌,而不是使用者名稱更有意義。
Django 允許您藉由為 AUTH_USER_MODEL
設定提供參考自訂模型的值來覆寫預設使用者模型
AUTH_USER_MODEL = "myapp.MyUser"
此點對描述 Django 應用程式的 label
(必須在您的 INSTALLED_APPS
中)和您希望用作使用者模型的 Django 模型名稱。
在開始專案時使用自訂使用者模型¶
如果您要開始一個新專案,您可以藉由子類別化 AbstractUser
來設定與預設使用者模型行為相同的自訂使用者模型
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
別忘了將 AUTH_USER_MODEL
指向它。在第一次建立任何遷移或執行 manage.py migrate
之前執行此操作。
此外,在應用程式的 admin.py
中註冊模型
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
在專案中期變更為自訂使用者模型¶
在您建立資料庫表格後變更 AUTH_USER_MODEL
是可能的,但可能很複雜,因為它會影響外鍵和多對多關係等。
這個變更無法自動完成,需要手動修正您的結構描述、將您的資料從舊的使用者表格移轉,並可能需要手動重新套用一些遷移。請參閱 #25313 以了解步驟概要。
由於 Django 的可替換模型動態依賴功能存在限制,AUTH_USER_MODEL
所參考的模型必須在其應用程式的首次遷移中建立(通常稱為 0001_initial
);否則,您將會遇到依賴問題。
此外,當執行遷移時,您可能會遇到 CircularDependencyError
,因為 Django 無法由於動態依賴而自動中斷依賴迴圈。如果您看到此錯誤,您應該將您的使用者模型所依賴的模型移至第二個遷移,以中斷迴圈。(您可以嘗試建立兩個彼此之間具有 ForeignKey
的普通模型,並查看 makemigrations
如何解決該循環依賴,如果您想了解通常如何完成。)
可重複使用的應用程式和 AUTH_USER_MODEL
¶
可重複使用的應用程式不應實作自訂使用者模型。一個專案可能會使用許多應用程式,而兩個實作自訂使用者模型的可重複使用應用程式無法同時使用。如果需要在您的應用程式中儲存每個使用者的資訊,請使用 ForeignKey
或 OneToOneField
連接到 settings.AUTH_USER_MODEL
,如下所述。
參照 User
模型¶
如果您直接參照 User
(例如,在外部鍵中參照它),您的程式碼將無法在 AUTH_USER_MODEL
設定已變更為不同使用者模型的專案中運作。
- get_user_model()[原始碼]¶
您應該使用
django.contrib.auth.get_user_model()
來參照使用者模型,而不是直接參照User
。此方法將傳回目前作用中的使用者模型 – 如果有指定自訂使用者模型,則為自訂使用者模型,否則為User
。當您定義使用者模型的外來鍵或多對多關係時,您應該使用
AUTH_USER_MODEL
設定來指定自訂模型。例如from django.conf import settings from django.db import models class Article(models.Model): author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, )
當您連接到使用者模型傳送的訊號時,您應該使用
AUTH_USER_MODEL
設定來指定自訂模型。例如from django.conf import settings from django.db.models.signals import post_save def post_save_receiver(sender, instance, created, **kwargs): pass post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)
一般而言,在匯入時執行的程式碼中使用
AUTH_USER_MODEL
設定來參照使用者模型最容易,但是,也可以在 Django 匯入模型的同時呼叫get_user_model()
,因此您可以使用models.ForeignKey(get_user_model(), ...)
。如果您的應用程式使用多個使用者模型進行測試,例如使用
@override_settings(AUTH_USER_MODEL=...)
,並且您在模組級變數中快取get_user_model()
的結果,則可能需要監聽setting_changed
訊號來清除快取。例如from django.apps import apps from django.contrib.auth import get_user_model from django.core.signals import setting_changed from django.dispatch import receiver @receiver(setting_changed) def user_model_swapped(*, setting, **kwargs): if setting == "AUTH_USER_MODEL": apps.clear_cache() from myapp import some_module some_module.UserModel = get_user_model()
指定自訂使用者模型¶
當您開始使用自訂使用者模型建立專案時,請停下來思考這是否是您專案的正確選擇。
將所有與使用者相關的資訊保留在一個模型中,可以免除檢索相關模型時需要額外的或更複雜的資料庫查詢。另一方面,將應用程式特定的使用者資訊儲存在與您的自訂使用者模型具有關係的模型中可能更合適。這允許每個應用程式指定自己的使用者資料需求,而不會與其他應用程式的假設發生衝突或破壞。這也表示您會盡可能讓您的使用者模型保持簡單,專注於驗證,並遵循 Django 期望自訂使用者模型滿足的最低要求。
如果您使用預設的驗證後端,則您的模型必須具有一個可用於識別目的的唯一欄位。這可以是使用者名稱、電子郵件地址或任何其他唯一屬性。如果您使用可以支援非唯一使用者名稱的自訂驗證後端,則允許使用非唯一的使用者名稱欄位。
建立相容的自訂使用者模型最簡單的方法是繼承自 AbstractBaseUser
。AbstractBaseUser
提供了使用者模型的核心實作,包括雜湊密碼和權杖化密碼重設。然後,您必須提供一些關鍵實作詳細資訊
- class models.CustomUser¶
- USERNAME_FIELD¶
一個字串,描述使用者模型上用作唯一識別碼的欄位名稱。這通常會是某種類型的使用者名稱,但也可以是電子郵件地址或任何其他唯一識別碼。除非您使用可以支援非唯一使用者名稱的自訂驗證後端,否則該欄位必須是唯一的(例如,在其定義中設定
unique=True
)。在以下範例中,欄位
identifier
用作識別欄位class MyUser(AbstractBaseUser): identifier = models.CharField(max_length=40, unique=True) ... USERNAME_FIELD = "identifier"
- EMAIL_FIELD¶
一個字串,描述
User
模型上的電子郵件欄位名稱。此值由get_email_field_name()
傳回。
- REQUIRED_FIELDS¶
當透過
createsuperuser
管理命令建立使用者時,將提示輸入的欄位名稱清單。系統會提示使用者為每個欄位提供一個值。它必須包含blank
為False
或未定義的任何欄位,並且可以包含您想要在互動式建立使用者時提示的其他欄位。REQUIRED_FIELDS
在 Django 的其他部分(例如在管理介面中建立使用者)沒有任何作用。例如,這是定義兩個必要欄位(出生日期和身高)的使用者模型的部分定義
class MyUser(AbstractBaseUser): ... date_of_birth = models.DateField() height = models.FloatField() ... REQUIRED_FIELDS = ["date_of_birth", "height"]
注意
REQUIRED_FIELDS
必須包含使用者模型上的所有必要欄位,但不應包含USERNAME_FIELD
或password
,因為這些欄位始終會提示輸入。
- is_active¶
一個布林值屬性,指示使用者是否被視為「活動」。此屬性作為
AbstractBaseUser
的屬性提供,預設值為True
。您選擇如何實作它將取決於您選擇的驗證後端的詳細資訊。請參閱內建使用者模型上的 is_active 屬性
的說明文件以了解詳細資訊。
- get_full_name()¶
選用。使用者更正式的識別碼,例如他們的全名。如果實作,這會與
django.contrib.admin
中物件的歷史記錄中的使用者名稱一起顯示。
- get_short_name() ¶
選用。使用者簡短、非正式的識別名稱,例如他們的名字。如果實作此方法,它將會取代
django.contrib.admin
標頭中對使用者的問候語中的使用者名稱。
匯入
AbstractBaseUser
AbstractBaseUser
和BaseUserManager
可以從django.contrib.auth.base_user
匯入,因此可以在不將django.contrib.auth
包含在INSTALLED_APPS
中也能匯入。
下列屬性和方法在 AbstractBaseUser
的任何子類別上皆可使用
- class models.AbstractBaseUser¶
- get_username() ¶
傳回由
USERNAME_FIELD
指定的欄位值。
- clean() ¶
透過呼叫
normalize_username()
來正規化使用者名稱。如果您覆寫此方法,請務必呼叫super()
以保留正規化。
- classmethod get_email_field_name() ¶
傳回由
EMAIL_FIELD
屬性指定的電子郵件欄位名稱。如果未指定EMAIL_FIELD
,則預設為'email'
。
- classmethod normalize_username(username) ¶
對使用者名稱套用 NFKC Unicode 正規化,使外觀相同但 Unicode 碼位不同的字元被視為相同。
- is_authenticated ¶
唯讀屬性,始終為
True
(與始終為False
的AnonymousUser.is_authenticated
相反)。這是一種判斷使用者是否已通過驗證的方式。這並不意味著任何權限,也不會檢查使用者是否處於活動狀態或是否具有有效的會期。即使通常您會在request.user
上檢查此屬性,以判斷它是否已由AuthenticationMiddleware
填入(表示目前已登入的使用者),您應該知道此屬性對於任何User
實例皆為True
。
- is_anonymous ¶
唯讀屬性,始終為
False
。這是一種區分User
和AnonymousUser
物件的方法。一般來說,您應該優先使用is_authenticated
而非此屬性。
- set_password(raw_password) ¶
將使用者的密碼設定為給定的原始字串,並處理密碼雜湊。不會儲存
AbstractBaseUser
物件。當 raw_password 為
None
時,密碼將會被設定為無法使用的密碼,如同使用set_unusable_password()
一樣。
- check_password(raw_password) ¶
- acheck_password(raw_password) ¶
非同步版本:
acheck_password()
如果給定的原始字串是使用者的正確密碼,則傳回
True
。(這會處理密碼雜湊以進行比較。)在 Django 5.0 中變更新增了
acheck_password()
方法。
- set_unusable_password() ¶
將使用者標記為未設定密碼。這與密碼為空白字串不同。此使用者的
check_password()
永遠不會傳回True
。不會儲存AbstractBaseUser
物件。如果您的應用程式的驗證是針對現有的外部來源(例如 LDAP 目錄)進行,您可能需要此方法。
- has_usable_password() ¶
如果已對此使用者呼叫
set_unusable_password()
,則傳回False
。
- get_session_auth_hash() ¶
傳回密碼欄位的 HMAC。用於 密碼變更時的會期失效。
- get_session_auth_fallback_hash() ¶
使用
SECRET_KEY_FALLBACKS
產生密碼欄位的 HMAC。由get_user()
使用。
AbstractUser
繼承自 AbstractBaseUser
- class models.AbstractUser¶
- clean() ¶
透過呼叫
BaseUserManager.normalize_email()
來正規化電子郵件。如果您覆寫此方法,請務必呼叫super()
以保留正規化。
為自訂使用者模型撰寫管理器 ¶
您也應該為您的使用者模型定義一個自訂的管理員。如果您的使用者模型定義了與 Django 預設使用者相同的 username
、email
、is_staff
、is_active
、is_superuser
、last_login
和 date_joined
欄位,您可以安裝 Django 的 UserManager
;但是,如果您的使用者模型定義了不同的欄位,您需要定義一個繼承自 BaseUserManager
的自訂管理員,並提供兩個額外的方法
- class models.CustomUserManager¶
- create_user(username_field, password=None, **other_fields)¶
create_user()
的原型應該接受使用者名稱欄位,以及所有必要的欄位作為引數。例如,如果您的使用者模型使用email
作為使用者名稱欄位,並且有date_of_birth
作為必要的欄位,那麼create_user
應該定義為def create_user(self, email, date_of_birth, password=None): # create user here ...
- create_superuser(username_field, password=None, **other_fields)¶
create_superuser()
的原型應該接受使用者名稱欄位,以及所有必要的欄位作為引數。例如,如果您的使用者模型使用email
作為使用者名稱欄位,並且有date_of_birth
作為必要的欄位,那麼create_superuser
應該定義為def create_superuser(self, email, date_of_birth, password=None): # create superuser here ...
對於 ForeignKey
在 USERNAME_FIELD
或 REQUIRED_FIELDS
中,這些方法會接收 to_field
(預設為 primary_key
)的值,該值為現有實例的值。
BaseUserManager
提供以下實用方法
擴充 Django 的預設 User
¶
如果您對 Django 的 User
模型完全滿意,但您想新增一些額外的個人資料資訊,您可以子類化 django.contrib.auth.models.AbstractUser
並新增您的自訂個人資料欄位,儘管我們建議使用單獨的模型,如 指定自訂使用者模型 中所述。AbstractUser
作為一個 抽象模型,提供了預設 User
的完整實作。
自訂使用者和內建驗證表單¶
Django 的內建 表單 和 視圖 對他們正在使用的使用者模型做了一些假設。
以下表單與 AbstractBaseUser
的任何子類別相容
AuthenticationForm
:使用USERNAME_FIELD
指定的使用者名稱欄位。
如果符合這些假設,則以下表單對使用者模型進行假設,並且可以直接使用
PasswordResetForm
:假設使用者模型有一個欄位,用於儲存使用者的電子郵件地址,該名稱由get_email_field_name()
(預設為email
)傳回,可用於識別使用者,並且有一個名為is_active
的布林值欄位,以防止為非活動使用者重設密碼。
最後,以下表單與 User
綁定,需要重寫或擴充才能使用自訂使用者模型
如果您的自訂使用者模型是 AbstractUser
的子類別,那麼您可以透過這種方式擴充這些表單
from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = UserCreationForm.Meta.fields + ("custom_field",)
自訂使用者和 django.contrib.admin
¶
如果您希望您的自訂使用者模型也能與管理員一起使用,您的使用者模型必須定義一些額外的屬性和方法。這些方法允許管理員控制使用者對管理員內容的存取權限
- class models.CustomUser
- is_staff¶
如果允許使用者存取管理網站,則傳回
True
。
- is_active¶
如果使用者帳戶目前處於活動狀態,則傳回
True
。
- has_perm(perm, obj=None):
如果使用者具有指定的權限,則傳回
True
。如果提供了obj
,則需要針對特定物件實例檢查權限。
- has_module_perms(app_label)
如果使用者具有存取給定應用程式中模型的權限,則傳回
True
。
您還需要向管理員註冊您的自訂使用者模型。如果您的自訂使用者模型擴充了 django.contrib.auth.models.AbstractUser
,您可以使用 Django 現有的 django.contrib.auth.admin.UserAdmin
類別。但是,如果您的使用者模型擴充了 AbstractBaseUser
,您需要定義一個自訂的 ModelAdmin
類別。可能可以子類化預設的 django.contrib.auth.admin.UserAdmin
;但是,您需要覆寫任何引用不在您的自訂使用者類別上的 django.contrib.auth.models.AbstractUser
上的欄位的定義。
注意
如果您使用的是自訂的 ModelAdmin
,它是 django.contrib.auth.admin.UserAdmin
的子類別,那麼您需要將您的自訂欄位新增到 fieldsets
(用於編輯使用者時使用的欄位)和 add_fieldsets
(用於建立使用者時使用的欄位)。例如
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
...
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)
有關更多詳細資訊,請參閱 完整範例。
自訂使用者和權限¶
為了方便將 Django 的權限框架包含到您自己的使用者類別中,Django 提供了 PermissionsMixin
。這是一個抽象模型,您可以將其包含在使用者模型的類別階層中,為您提供支援 Django 權限模型的所有方法和資料庫欄位。
PermissionsMixin
提供以下方法和屬性
- class models.PermissionsMixin¶
- is_superuser¶
布林值。指定此使用者擁有所有權限,而無需明確指派。
- get_user_permissions(obj=None)¶
返回使用者直接擁有的權限字串集合。
如果傳入
obj
,則僅返回此特定物件的使用者權限。
- get_group_permissions(obj=None)¶
返回使用者透過其群組擁有的權限字串集合。
如果傳入
obj
,則僅返回此特定物件的群組權限。
- get_all_permissions(obj=None)¶
返回使用者擁有的權限字串集合,包括群組權限和使用者權限。
如果傳入
obj
,則僅返回此特定物件的權限。
- has_perm(perm, obj=None)¶
如果使用者擁有指定的權限,則返回
True
,其中perm
的格式為"<應用程式 標籤>.<權限 代碼名稱>"
(請參閱 權限)。如果User.is_active
和is_superuser
皆為True
,則此方法始終返回True
。如果傳入
obj
,此方法將不會檢查模型的權限,而是檢查此特定物件的權限。
- has_perms(perm_list, obj=None)¶
如果使用者擁有指定的每個權限,則返回
True
,其中每個權限的格式為"<應用程式 標籤>.<權限 代碼名稱>"
。如果User.is_active
和is_superuser
皆為True
,則此方法始終返回True
。如果傳入
obj
,此方法將不會檢查模型的權限,而是檢查此特定物件的權限。
- has_module_perms(package_name)¶
如果使用者在給定的套件(Django 應用程式標籤)中擁有任何權限,則返回
True
。如果User.is_active
和is_superuser
皆為True
,則此方法始終返回True
。
自訂使用者和 Proxy 模型¶
自訂使用者模型的一個限制是,安裝自訂使用者模型會破壞任何擴展 User
的 Proxy 模型。Proxy 模型必須基於具體的基底類別;透過定義自訂使用者模型,您移除了 Django 可靠識別基底類別的能力。
如果您的專案使用 Proxy 模型,您必須修改 Proxy 以擴展專案中使用的使用者模型,或者將 Proxy 的行為合併到您的 User
子類別中。
完整範例¶
以下是符合管理員規範的自訂使用者應用程式範例。此使用者模型使用電子郵件地址作為使用者名稱,並具有必要的出生日期;除了使用者帳戶上的 admin
標記外,它不提供任何權限檢查。此模型將與所有內建的身份驗證表單和視圖相容,除了使用者建立表單。此範例說明了大多數元件如何協同工作,但不打算直接複製到生產環境的專案中使用。
此程式碼將全部存在於自訂驗證應用程式的 models.py
檔案中
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth"]
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
然後,要將此自訂使用者模型註冊到 Django 的管理員介面,需要在應用程式的 admin.py
檔案中加入以下程式碼
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from customauth.models import MyUser
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(
label="Password confirmation", widget=forms.PasswordInput
)
class Meta:
model = MyUser
fields = ["email", "date_of_birth"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ["email", "date_of_birth", "is_admin"]
list_filter = ["is_admin"]
fieldsets = [
(None, {"fields": ["email", "password"]}),
("Personal info", {"fields": ["date_of_birth"]}),
("Permissions", {"fields": ["is_admin"]}),
]
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = [
(
None,
{
"classes": ["wide"],
"fields": ["email", "date_of_birth", "password1", "password2"],
},
),
]
search_fields = ["email"]
ordering = ["email"]
filter_horizontal = []
# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
最後,使用 settings.py
中的 AUTH_USER_MODEL
設定,指定自訂模型作為專案的預設使用者模型
AUTH_USER_MODEL = "customauth.MyUser"