Django自定義用戶認證系統之自定義用戶模型


參考文檔:http://python.usyiyi.cn/django/topics/auth/customizing.html

Django 自帶的認證系統足夠應付大多數情況,但你或許不打算使用現成的認證系統。定制自己的項目的權限系統需要了解哪些一些關鍵點,即Django中哪些部分是能夠擴展或替換的。這個文檔提供了如何定制權限系統的細節。

“認證”后端 在以下情形時可被擴展:當一個 User 模型對象帶有用戶名和密碼時,且需要有別於 Django 默認的認證功能。

你可為你的模型提供可通過 Django 權限系統檢查的 定制的權限。

你能夠擴展 默認的 User 模型,或實現 一個完全定制的模型。

其他認證源

有時候你需要掛接到其他認證資源 -- 另一包含用戶名,密碼的數據源或者其他認證方法。

例如,您的公司可能已經有一個LDAP設置存儲了每一位員工的用戶名和密碼。 對於一個在LDAP和Django網站都擁有賬號的用戶來說,如果他/她不能使用LDAP賬號登錄Django網站,對他/她以及網站管理員來說都是一件麻煩事。

為了解決類似情形,django的認證系統允許你添加其他認證方式。您可以覆蓋Django的基於數據庫的默認方案,也可以連接使用其他系統的認證服務。

指定認證后端

在底層,Django 維護一個“認證后台”的列表。當調用django.contrib.auth.authenticate() 時 —— 如何登入一個用戶 中所描述的 —— Django 會嘗試所有的認證后台進行認證。如果第一個認證方法失敗,Django 將嘗試第二個,以此類推,直至試完所有的認證后台。

使用的認證后台通過AUTHENTICATION_BACKENDS 設置指定。它應該是一個包含Python 路徑名稱的元組,它們指向的Python 類知道如何進行驗證。這些類可以位於Python 路徑上任何地方。

默認情況下,AUTHENTICATION_BACKENDS 設置為:

('django.contrib.auth.backends.ModelBackend',)

這個基本的認證后台會檢查Django 的用戶數據庫並查詢內建的權限。它不會通過任何的速率限制機制防護暴力破解。你可以在自定義的認證后端中實現自己的速率控制機制,或者使用大部分Web 服務器提供的機制。

AUTHENTICATION_BACKENDS 的順序很重要,所以如果用戶名和密碼在多個后台中都是合法的,Django 將在第一個匹配成功后停止處理。

如果后台引發PermissionDenied 異常,認證將立即失敗。Django 不會檢查后面的認證后台。

注意:

一旦用戶被認證過,Django會在用戶的session中存儲他使用的認證后端,然后在session有效期中一直會為該用戶提供此后端認證。這種高效意味着驗證源被緩存基於per-session基礎, 所以如果你改變 AUTHENTICATION_BACKENDS, 如果你需要迫使用戶重新認證,需要清除掉 session 數據.一個簡單的方式是使用這個方法: Session.objects.all().delete().

編寫一個認證后端

一個認證后端是個實現兩個方法的類: get_user(user_id) and authenticate(**credentials)

get_user 方法要求一個參數 user_id –這個參數可以是用戶名,數據庫中的ID或其它標識 User 對象的主鍵– 方法返回一個 User 對象.

身份驗證 方法使用憑據作為關鍵字參數。大多數情況下,代碼如下︰

class MyBackend(object):
    def authenticate(self, username=None, password=None):
        # Check the username/password and return a User.
        ...

當然,它也可以接收token的方式作為參數,例如:

class MyBackend(object):
    def authenticate(self, token=None):
        # Check the token and return a User.
        ...

不管怎樣, authenticate 至少應該檢查憑證, 如果憑證合法,它應該返回一個匹配於登錄信息的 User 實例。如果不合法,則返回 None.

正如文檔開頭所描述的,Django的 admin 與Django User 對象是緊耦合的。到目前為止, 最好的解決方法是給每一個在你后台的用戶創建一個 User 對象 (e.g., in your LDAP directory, your external SQL database, etc.) 你可以先寫一個腳本來做這件事, 或者用你的 authenticate 方法在用戶登陸的時候完成這件事。

這里有一個例子,后台對你定義在 settings.py 文件里的用戶和密碼進行驗證,並且在用第一次驗證的時候創建一個 User 對象:

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend(object):
    """
    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 = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """

    def authenticate(self, 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. Note that we can set password
                # to anything, because it won't be checked; the password
                # from settings.py will.
                user = User(username=username, password='get from settings.py')
                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

在定制后端中處理授權

自定義驗證后端能提供自己的權限。

當認證后端完成了這些功能 (get_group_permissions(), get_all_permissions(), has_perm(), and has_module_perms()) 那么user model就會給它授予相對應的許可。

提供給用戶的權限將是所有后端返回的所有權限的超集也就是說,只要任意一個backend授予了一個user權限,django就給這個user這個權限。

如果后端在has_perm()has_module_perms()中引發PermissionDenied異常,授權將立即失敗,Django不會檢查后端接下來。

上述的簡單backend可以相當容易的完成授予admin權限。

class SettingsBackend(object):
    ...
    def has_perm(self, user_obj, perm, obj=None):
        if user_obj.username == settings.ADMIN_LOGIN:
            return True
        else:
            return False

在上例中,授予了用戶所有訪問權限。注意, 由於django.contrib.auth.models.User 同名函數將接收同樣的參數,認證后台接收到的 user_obj,有可能是匿名用戶 anonymous

一個完整的認證過程,可以參考 ModelBackend類,它位於django/contrib/auth/backends.py,ModelBackend是默認的認證后台,並且大多數情況下會對auth_permission表進行查詢。 如果你想對后台API提供自定義行為,你可以利用Python繼承的優勢,繼承ModelBackend並自定義后台API

授權匿名用戶

匿名用戶是指不經過身份驗證即他們有沒有提供有效的身份驗證細節。然而,這並不一定意味着他們不被授權做任何事情。在最基本的層面上,大多數網站授權匿名用戶瀏覽大部分網站,許多網站允許匿名張貼評論等。

Django 的權限框架沒有一個地方來存儲匿名用戶的權限。然而,傳遞給身份驗證后端的用戶對象可能是 django.contrib.auth.models.AnonymousUser 對象,該對象允許后端指定匿名用戶自定義的授權行為。這對可重用應用的作者是很有用的, 因為他可以委托所有的請求, 例如控制匿名用戶訪問,給這個認證后端, 而不需要設置它

非活動用戶的授權

非活動用戶是經過身份驗證但屬性is_active設置為False的用戶。然而這並不意味着他們無權做任何事情。例如他們可以被允許激活他們的帳戶。

對權限系統中的匿名用戶的支持允許匿名用戶具有執行某些操作的權限的情況,而未被認證的用戶不具有。

不要忘記在自己的后端權限方法中測試用戶的is_active屬性。

操作對象權限

django的權限框架對對象權限有基礎的支持, 盡管在它的核心沒有實現它.這意味着對象權限檢查將始終返回 False 或空列表 (取決於檢查的行為)。一個認證后端將傳遞關鍵字參數objuser_obj 給每一個對象相關的認證方法, 並且能夠返回適當的對象級別的權限

自定義權限

要為給定模型對象創建自定義權限,請使用權限 模型元屬性。

此示例任務模型創建三個自定義權限,即用戶是否可以對您的應用程序任務實例執行操作:

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

唯一需要做的就是在運行manage.py migrate時創建這些額外的權限。當用戶嘗試訪問應用程序提供的功能(查看任務,更改任務狀態,關閉任務)時,您的代碼負責檢查這些權限的值。繼續上面的示例,以下檢查用戶是否可以查看任務:

user.has_perm('app.view_task')

擴展已有的用戶模型(user model)

有兩種方法來擴展默認的User模型,而不用替換你自己的模型。 如果你需要的只是行為上的改變,而不需要對數據庫中存儲的內容做任何改變,你可以創建基於User 的代理模型。代理模型提供的功能包括默認的排序、自定義管理器以及自定義模型方法。

如果你想存儲新字段到已有的User里,那么你可以選擇one-to-one relationship來擴展用戶信息。這種 one-to-one 模型一般被稱為資料模型(profile model),它通常被用來存儲一些有關網站用戶的非驗證性( non-auth )資料。例如,你可以創建一個員工模型 (Employee model):

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User)
    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 )並將其添加到UserAdmin類並向User類注冊的:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
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(UserAdmin):
    inlines = (EmployeeInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

這些Profile models在任何方面都不特殊,它們就是和User model多了一個一對一鏈接的普通Django models。 這種情形下,它們不會在一名用戶創建時自動創建, 但是 django.db.models.signals.post_save 可以在適當的時候用於創建或更新相關模型。

注意使用相關模型的成果需另外的查詢或者聯結來獲取相關數據,基於你的需求替換用戶模型並添加相關字段可能是你更好的選擇。但是,在你項目應用程序中,指向默認用戶模型的鏈接可能帶來額外的數據庫負載。

自定義重寫用戶模型

Django 內建的User 模型可能不適合某些類型的項目。例如,在某些網站上使用郵件地址而不是用戶名作為身份的標識可能更合理。

Django 允許你通過AUTH_USER_MODEL 設置覆蓋默認的User 模型, 其值引用一個自定義的模型。

AUTH_USER_MODEL = 'myapp.MyUser'

上面的值表示Django 應用的名稱(必須位於INSTALLED_APPS 中) 和你想使用的User 模型的名稱。

注意:

改變 AUTH_USER_MODEL 對你的數據庫結構有很大的影響。它改變了一些會使用到的表格,並且會影響到一些外鍵和多對多關系的構造。如果你打算設置AUTH_USER_MODEL, 你應該在創建任何遷移或者第一次運行 manage.py migrate 前設置它。在你有表格被創建后更改此設置是不被makemigrations支持的,並且會導致你需要手動修改數據庫結構,從舊用戶表中導出數據,可能重新應用一些遷移。

警告:

由於Django的可交換模型的動態依賴特性的局限, 你必須確保 AUTH_USER_MODEL引用的模型在所屬app中第一個遷移文件中被創建(通常命名為 0001_initial); 否則, 你會碰到錯誤.

此外,在運行遷移時可能會遇到CircularDependencyError,因為Django由於動態依賴性而無法自動斷開依賴性循環。如果您看到此錯誤,您應該通過將依賴於您的用戶模型的模型移動到第二個遷移中來打破循環(您可以嘗試制作兩個具有ForeignKey的正常模型,並查看makemigrations

可重復使用的應用程式和AUTH_USER_MODEL

可重復使用的應用不應實施自定義用戶模型。項目可能使用許多應用程序,並且實施自定義用戶模型的兩個可重用應用程序不能一起使用。如果您需要在應用中存儲每個用戶的信息,請使用ForeignKey或OneToOneField設置settings.AUTH_USER_MODEL,如下所述。

引用user模型

AUTH_USER_MODEL 設置改成其它用戶模型的項目中,如果你直接引用User(例如,通過一個外鍵引用它),你的代碼將不能工作。

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)

連接用戶模型發出的信號時, 應該使用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 設置引用用戶模型。get_user_model() 只在Django 已經導入所有的模型后才工作。

指定自定義的用戶模型

模型設計考慮:

處理不直接相關的認證在自定義用戶模型信息之前,應仔細考慮。

這可能是更好的存儲應用程序特定的用戶信息在與用戶模式的關系的典范。這使得每一個應用,而不用擔心與其他應用程序沖突指定自己的用戶數據需求.另一方面,查詢來檢索此相關的信息將涉及的數據庫連接,這可能對性能有影響。

Django 期望你自定義的User model滿足一些最低要求:

  1. 模型必須有一個唯一的字段可被用於識別目的。可以是一個用戶名,電子郵件地址,或任何其它獨特屬性。
  2. 你的模型必須提供一種方法可以在"short"and"long"form可以定位到用戶。最普遍的方法是用用戶的名來作為簡稱,用用戶的全名來作為全稱。然而,對這兩種方式沒有特定的要求,如果你想,他們可以返回完全相同的值。

創建一個規范的自定義模型最簡單的方法是繼承AbstractBaseUser,AbstractBaseUser提供User模型的核心實現,包括散列密碼和令牌化密碼重置。然后,您必須提供一些關鍵的實施細節:

class models.CustomUser

USERNAME_FIELD

描述User模型上用作唯一標識符的字段名稱的字符串。這通常是某種用戶名,但它也可以是電子郵件地址或任何其他唯一標識符。字段必須必須是唯一的(即在其定義中設置unique=True)。

在以下示例中,字段identifier用作標識字段:

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    USERNAME_FIELD = 'identifier'

USERNAME_FIELD現在支持ForeignKey。由於在createsuperuser提示期間沒有辦法傳遞模型實例,因此希望用戶在默認情況下輸入to_field值(primary_key )的現有實例。

REQUIRED_FIELDS

當通過createsuperuser管理命令創建一個用戶時,用於提示的一個字段名稱列表.。將會提示給列表里面的每一個字段提供一個值。 它包含的必須是 為 False 或者blank未定義的字段, 也可包含你想要在交互地創建一個新的用戶時想要展示的其他字段。REQUIRED_FIELDS在Django的其他部分沒有任何影響,例如在管理員中創建用戶。

例如,以下是定義兩個必需字段(出生日期和身高)的User模型的部分定義:

class MyUser(AbstractBaseUser):
    ...
    date_of_birth = models.DateField()
    height = models.FloatField()
    ...
    REQUIRED_FIELDS = ['date_of_birth', 'height']

注意

REQUIRED_FIELDS必須包含User模型中的所有必填字段,但應不應包含USERNAME_FIELD或password,因為將始終提示輸入這些字段。

REQUIRED_FIELDS現在支持ForeignKey。由於在createsuperuser提示期間沒有辦法傳遞模型實例,因此希望用戶在默認情況下輸入to_field值(primary_key )的現有實例。(1.8后版本)

is_active

指示用戶是否被視為“活動”的布爾屬性。此屬性作為AbstractBaseUser上的屬性提供,默認為True。如何選擇實施它將取決於您選擇的身份驗證后端的詳細信息。

get_full_name()

用戶更長且正式的標識.常見的解釋會是用戶的完整名稱,但它可以是任何字符串,用於標識用戶。

get_short_name()

一個短的且非正式用戶的標識符。常見的解釋會是第一個用戶的名稱,但它可以是任意字符串,用於以非正式的方式標識用戶。它也可能會返回與django.contrib.auth.models.User.get_full_name()相同的值。

class models.AbstractBaseUser

以下方法適用於AbstractBaseUser的任何子類:

get_username()

返回由USERNAME_FIELD指定的字段的值。

is_anonymous()

始終返回False。這是區分AnonymousUser對象的一種方法。通常,您應該優先使用is_authenticated()到此方法。

is_authenticated()

始終返回True。這是一種判斷用戶是否已通過身份驗證的方法。這並不意味着任何權限,並且不檢查用戶是否處於活動狀態 - 它僅指示用戶已提供有效的用戶名和密碼。

set_password(raw_password)

將用戶的密碼設置為給定的原始字符串,注意密碼哈希。不保存AbstractBaseUser對象。

當raw_password為None時,密碼將被設置為不可用的密碼,如同使用set_unusable_password()。

check_password(raw_password)

如果給定的原始字符串是用戶的正確密碼,則返回True。(這將在進行比較時處理密碼散列。)

set_unusable_password()

將用戶標記為沒有設置密碼。這與為密碼使用空白字符串不同。check_password()此用戶將永遠不會返回True。不保存AbstractBaseUser對象。

如果針對現有外部源(例如LDAP目錄)進行應用程序的身份驗證,則可能需要這樣做。

has_usable_password()

如果set_unusable_password()已為此用戶調用,則返回False。

get_session_auth_hash()

返回密碼字段的HMAC。用於Session invalidation on password change。

你應該再為你的User模型自定義一個管理器。 如果你的User模型定義了這些字段:username, email, is_staff, is_active, is_superuser, last_login, and date_joined跟默認的User的字段是一樣的話, 那么你就使用Django的UserManager就行了;反之, 如果你的User定義了不同的字段, 你就要去自定義一個管理器,它繼承自BaseUserManager並提供兩個額外的方法:

class models.CustomUserManager

create_user(*username_field*, password=None, **other_fields)

create_user() 原本接受username,以及其它所有必填字段作為參數。例如,如果你的user模型使用 email 作為username字段, 並且使用 date_of_birth 作為必填字段, 那么create_user 應該定義為:

def create_user(self, email, date_of_birth, password=None):
    # create user here
    ...
create_superuser(*username_field*, password, **other_fields)

create_superuser()的原型應該接受用戶名字段,以及所有必需的字段作為參數。例如,如果您的用戶模型使用email作為用戶名字段,並且date_of_birth為必填字段,則create_superuser應定義為:

def create_superuser(self, email, date_of_birth, password):
    # create superuser here
    ...

create_user()不同,create_superuser() 必須要求調用方提供密碼。

class models.BaseUserManager

BaseUserManager提供以下實用程序方法:

normalize_email(email)

一個通過將部分電子郵箱地址轉為小寫來使其規范化的類方法 classmethod

get_by_natural_key(username)

使用由USERNAME_FIELD指定的字段內容檢索用戶實例。

make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')

返回具有給定長度和給定字符串的允許字符的隨機密碼。請注意,默認值allowed_chars不包含可能導致用戶混淆的字母,包括:

  • i,l,I和1(小寫字母i,小寫字母L,大寫字母i,第一號)
  • o,O和0(小寫字母o,大寫字母o和零)

擴展Django的默認用戶

如果你完全滿意Django 的 用戶 模型且你只想添加一些額外的配置文件信息,您可以簡單的繼承 django.contrib.auth.models.AbstractUser 並添加您的自定義字段,盡管我們建議像"Model design considerations"中描述的 Specifying a custom User model那樣, 使用一個單獨的模型。AbstractUser作為一種 抽象模型 提供默認 用戶 的完整實現。

自定義用戶與內置身份驗證表單

正如您所期望的,內置Django的forms和views對他們正在使用的用戶模型做出某些假設。

如果您的用戶模型不遵循相同的假設,可能需要定義替換表單,並作為auth視圖配置的一部分傳遞該表單。

UserCreationForm

取決於User模型。必須為任何自定義用戶模型重寫。

UserChangeForm

取決於User模型。必須為任何自定義用戶模型重寫。

AuthenticationForm

AbstractBaseUser的任何子類一起使用,並將適應使用USERNAME_FIELD中定義的字段。

PasswordResetForm

假設用戶模型具有可用於標識用戶的email字段和名為is_active的布爾字段,以防止對非活動用戶進行密碼重置。

SetPasswordForm

適用於AbstractBaseUser的任何子類

PasswordChangeForm

適用於AbstractBaseUser的任何子類

AdminPasswordChangeForm

適用於AbstractBaseUser的任何子類

自定義用戶和django.contrib.admin

如果你想讓你自定義的User模型也可以在站點管理上工作,那么你的模型應該再定義一些額外的屬性和方法。 這些方法允許管理員去控制User到管理內容的訪問:

class models.CustomUser

is_staff

如果允許用戶訪問管理網站,則返回True。

is_active

如果用戶帳戶當前處於活動狀態,則返回True。

has_perm(perm, obj=None)

如果用戶具有命名權限,則返回True。如果提供obj,則需要針對特定​​對象實例檢查權限。

has_module_perms(app_label)

如果用戶有權訪問給定應用中的模型,則返回True。

您還需要向管理員注冊您的自定義用戶模型。如果您的自定義User模型擴展django.contrib.auth.models.AbstractUser,則可以使用Django現有的django.contrib.auth.admin.UserAdmin類。但是,如果您的User模型擴展AbstractBaseUser,則需要定義一個自定義的ModelAdmin類。可以將默認django.contrib.auth.admin.UserAdmin;但是,您需要覆蓋引用django.contrib.auth.models.AbstractUser上不在您的自定義User類上的字段的任何定義。

自定義用戶和權限

為了方便將Django的權限框架包含到你自己的User類中,Django提供了PermissionsMixin。這是一個抽象模型,您可以包含在用戶模型的類層次結構中,為您提供支持Django權限模型所需的所有方法和數據庫字段。

PermissionsMixin提供了以下方法和屬性:

class models.PermissionsMixin

is_superuser

布爾值。指定此用戶具有所有權限,而不顯式分配它們。

get_group_permissions(obj=None)

通過用戶的組返回用戶擁有的一組權限字符串。如果傳入obj,則僅返回此特定對象的組權限。

get_all_permissions(obj=None)

通過組和用戶權限返回用戶擁有的一組權限字符串。如果傳入obj,則僅返回此特定對象的權限。

has_perm(perm, obj=None)

如果用戶具有指定的權限,則返回Tue,其中perm的格式為“< app label& < permission codename>“(請參閱permissions)。如果用戶處於非活動狀態,此方法將始終返回False。如果傳入obj,此方法將不會檢查模型的權限,而是檢查此特定對象。

has_perms(perm_list, obj=None)

如果用戶具有每個指定的權限,則返回True,其中每個perm的格式為“< app label>。< permission codename>“。如果用戶處於非活動狀態,此方法將始終返回False。如果傳入obj,此方法將不會檢查模型的權限,而是檢查特定對象。

has_module_perms(package_name)

如果用戶在給定的包(Django應用標簽)中有任何權限,則返回True。如果用戶處於非活動狀態,此方法將始終返回False。

ModelBackend

如果您不包含PermissionsMixin,則必須確保不要調用ModelBackend上的權限方法。ModelBackend假定某些字段在您的用戶模型上可用。如果您的用戶模型未提供這些字段,則在檢查權限時將收到數據庫錯誤。

自定義user和proxy models

自定義用戶模型的一個限制是,安裝自定義用戶模型將破壞擴展User的任何代理模型。代理模型必須基於具體的基類;通過定義自定義User模型,您可以刪除Django可靠地識別基類的能力。

如果項目使用代理模型,則必須修改代理以擴展項目中當前使用的用戶模型,或將代理的行為合並到用戶子類中。

定制users 和 testing/fixtures

如果您正在編寫與用戶模型交互的應用程序,則必須采取一些預防措施,以確保測試套件將運行,而不管項目正在使用的用戶模型。如果用戶模型已換出,任何實例化User實例的測試都將失敗。這包括使用夾具創建User實例的任何嘗試。

為確保您的測試套件能夠在任何項目配置中傳遞,django.contrib.auth.tests.utils定義了一個@skipIfCustomUser的裝飾器。如果正在使用除默認Django用戶以外的任何用戶模型,此裝飾器將導致跳過測試用例。這個裝飾器可以應用於單個測試或整個測試類。

根據您的應用程序,還可能需要添加測試,以確保應用程序與任何用戶模型配合使用,而不僅僅是默認的用戶模型。為了幫助這個,Django提供了兩個可以在測試套件中使用的替代用戶模型:

class tests.custom_user.CustomUser

使用email字段作為用戶名的自定義用戶模型,並且具有基本的管理員兼容的權限設置

class tests.custom_user.ExtensionUser

擴展django.contrib.auth.models.AbstractUser的自定義用戶模型,添加了date_of_birth字段。

然后,您可以使用@override_settings裝飾器使該測試與自定義User模型一起運行。例如,這里是一個測試的骨架,它將測試三個可能的用戶模型 - 默認值,加上auth app提供的兩個用戶模型:

from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import CustomUser, ExtensionUser
from django.test import TestCase, override_settings


class ApplicationTestCase(TestCase):
    @skipIfCustomUser
    def test_normal_user(self):
        "Run tests for the normal user model"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.CustomUser')
    def test_custom_user(self):
        "Run tests for a custom user model with email-based authentication"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.ExtensionUser')
    def test_extension_user(self):
        "Run tests for a simple extension of the built-in User."
        self.assertSomething()

完整的例子

這是一個管理器允許的自定義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):
        """
        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 get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __str__(self):              # __unicode__ on Python 2
        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的管理員注冊此自定義User模型,應用程序的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
from django.contrib.auth.forms import ReadOnlyPasswordHashField

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 forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).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
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]


class MyUserAdmin(UserAdmin):
    # 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, MyUserAdmin)
# ... 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'
存儲為密文,需要配置admin

- auth.py(修改name,UserProfileManager,UserProfile)   custom_user_admin.py
- custom_user_admin.py導入,刪除原來
    - from web.models import UserProfile
- settings
    - AUTH_USER_MODEL = 'web.UserProfile'
- admin.py
    - from web import custom_user_admin
    - 注釋原來的admin.register UserProfile
- models.py
    - from web.auth import UserProfile


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM