在Django中自定义身份验证

Django附带的身份验证对于大多数常见情况来说已经足够好了,但是开箱即用的默认值可能还没有满足您的需求。在项目中定制身份验证需要了解所提供系统的哪些点是可扩展或可替换的。此文档提供有关如何自定义身份验证系统的详细信息。

Authentication backends 当用户模型中存储的用户名和密码需要针对不同于Django默认值的服务进行身份验证时,提供一个可扩展系统。

你可以给你的模型 custom permissions 可以通过Django的授权系统进行检查。

你可以 extend 默认值 User 模型,或 substitute 完全定制的模型。

其他身份验证源

有时您可能需要连接到另一个身份验证源——也就是用户名、密码或身份验证方法的另一个源。

例如,您的公司可能已经有一个LDAP设置,用于存储每个员工的用户名和密码。如果用户在LDAP和基于Django的应用程序中拥有单独的帐户,那么对网络管理员和用户本身来说都是一个麻烦。

因此,为了处理这种情况,Django身份验证系统允许您插入其他身份验证源。您可以覆盖Django的默认基于数据库的方案,也可以与其他系统一起使用默认系统。

authentication backend reference 有关Django所包含的身份验证后端的信息。

指定身份验证后端

在幕后,Django维护了一个“身份验证后端”列表,用于检查身份验证。当有人调用时 django.contrib.auth.authenticate() --如中所述 How to log a user in --Django尝试在其所有身份验证后端进行身份验证。如果第一个身份验证方法失败,Django将尝试第二个方法,依此类推,直到尝试了所有后端。

要使用的身份验证后端列表在 AUTHENTICATION_BACKENDS 设置。这应该是一个python路径名列表,指向知道如何进行身份验证的python类。这些类可以在您的python路径上的任何地方。

默认情况下, AUTHENTICATION_BACKENDS 设置为:

["django.contrib.auth.backends.ModelBackend"]

这是检查Django用户数据库并查询内置权限的基本身份验证后端。它不通过任何速率限制机制提供针对暴力攻击的保护。您可以在自定义身份验证后台实现自己的速率限制机制,也可以使用大多数Web服务器提供的机制。

秩序 AUTHENTICATION_BACKENDS 重要的是,如果相同的用户名和密码在多个后端中有效,Django将在第一个正匹配时停止处理。

如果后端引发 PermissionDenied 异常,身份验证将立即失败。Django不会检查后面的后端。

备注

一旦用户通过了身份验证,Django就会存储在用户会话中使用哪个后端对用户进行身份验证,并在需要访问当前经过身份验证的用户时,在该会话期间重复使用相同的后端。这实际上意味着在每个会话的基础上缓存身份验证源,因此如果您更改 AUTHENTICATION_BACKENDS 如果需要强制用户使用不同的方法重新进行身份验证,则需要清除会话数据。要做到这一点,一个简单的方法是执行 Session.objects.all().delete()

正在写入身份验证后端

身份验证后端是实现两个必需方法的类: get_user(user_id)authenticate(request, **credentials) 以及一组与权限相关的可选权限 authorization methods .

这个 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 可能是 None 如果没有提供给 authenticate() (将其传递到后端)。

django管理与django紧密耦合 User object . 解决这个问题的最佳方法是创建一个Django User 对象对于存在于后端的每个用户(例如,在LDAP目录、外部SQL数据库等中),您可以提前编写一个脚本来执行此操作,或者 authenticate 方法可以在用户第一次登录时执行。

下面是一个后端示例,它根据您的 settings.py 文件并创建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)
                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_user_permissions()get_group_permissions()get_all_permissions()has_perm()has_module_perms()with_perm() )到实现这些功能的任何身份验证后端。

授予用户的权限将是所有后端返回的所有权限的超集。也就是说,Django向任何一个后端授予的用户授予权限。

如果后端引发 PermissionDenied 例外 has_perm()has_module_perms() ,授权将立即失败,Django不会检查后面的后端。

后端可以实现magic admin的权限,如下所示:

from django.contrib.auth.backends import BaseBackend


class MagicAdminBackend(BaseBackend):
    def has_perm(self, user_obj, perm, obj=None):
        return user_obj.username == settings.ADMIN_LOGIN

在上面的示例中,这将向授予访问权限的用户授予完全权限。注意,除了为关联的 django.contrib.auth.models.User 函数,后端身份验证函数都将用户对象(可能是匿名用户)作为参数。

完整的授权实现可以在 ModelBackend 班在 django/contrib/auth/backends.py ,这是默认的后端并查询 auth_permission 大部分时间都在吃饭。

匿名用户授权

匿名用户是指未经身份验证的用户,即他们没有提供有效的身份验证详细信息。然而,这并不一定意味着他们无权做任何事情。在最基本的层面上,大多数网站授权匿名用户浏览大部分网站,许多网站允许匿名发布评论等。

Django的权限框架没有存储匿名用户权限的地方。然而,传递到身份验证后端的用户对象可以是 django.contrib.auth.models.AnonymousUser 对象,允许后端为匿名用户指定自定义授权行为。这对可重用应用程序的作者特别有用,他们可以将所有授权问题委托给身份验证后端,而不需要设置,例如,控制匿名访问。

非活动用户授权

非活动用户是指 is_active 字段设置为 False . 这个 ModelBackendRemoteUserBackend 身份验证后端禁止这些用户进行身份验证。如果自定义用户模型没有 is_active 字段,将允许所有用户进行身份验证。

你可以使用 AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend 如果要允许非活动用户进行身份验证。

在权限系统中对匿名用户的支持允许匿名用户有权限执行某项操作,而非活动的已验证用户没有权限。

不要忘记测试 is_active 您自己的后端权限方法中的用户属性。

处理对象权限

Django的权限框架为对象权限提供了基础,尽管在内核中没有实现它。这意味着检查对象权限将始终返回 False 或空列表(取决于执行的检查)。认证后端将接收关键字参数 objuser_obj 对于每个与对象相关的授权方法,可以根据需要返回对象级权限。

自定义权限

若要为给定模型对象创建自定义权限,请使用 permissions model Meta attribute .

这个例子 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 模型,而不替换您自己的模型。如果所需的更改纯粹是行为性的,并且不需要对数据库中存储的内容进行任何更改,则可以创建一个 proxy model 基于 User . 这允许代理模型提供的任何功能,包括默认排序、自定义管理器或自定义模型方法。

如果您希望存储与 User ,可以使用 OneToOneField 到包含字段以获取附加信息的模型。这种一对一的模型通常被称为配置文件模型,因为它可能存储有关站点用户的非身份验证相关信息。例如,您可以创建一个员工模型:

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同时具有用户和员工模型,您可以使用Django的标准相关模型约定访问相关信息:

>>> u = User.objects.get(username="fsmith")
>>> freds_department = u.employee.department

要将配置文件模型的字段添加到管理员的用户页,请定义 InlineModelAdmin (对于这个示例,我们将使用 StackedInline 在你的应用程序中 admin.py 并将其添加到 UserAdmin 在中注册的类 User 类:

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"

这个虚线对描述了 label Django应用程序(它必须位于您的 INSTALLED_APPS ),以及您希望用作用户模型的Django模型的名称。

启动项目时使用自定义用户模型

如果您正在启动一个新项目,强烈建议您设置自定义用户模型,即使是默认的 User 模型对你来说足够了。此模型的行为与默认用户模型相同,但如果需要,您将来可以对其进行自定义:

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

可重用应用程序不应该实现自定义用户模型。一个项目可能使用许多应用程序,而实现自定义用户模型的两个可重用应用程序不能一起使用。如果需要在应用程序中存储每个用户的信息,请使用 ForeignKeyOneToOneFieldsettings.AUTH_USER_MODEL 如下所述。

引用 User 模型

如果你提到 User 直接(例如,通过在外键中引用它),您的代码将不适用于 AUTH_USER_MODEL 设置已更改为其他用户模型。

get_user_model()[源代码]

而不是指 User 直接,您应该使用 django.contrib.auth.get_user_model() . 此方法将返回当前活动的用户模型——如果指定了自定义用户模型,则返回自定义用户模型;或者 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 但是,在导入时执行的代码中设置,也可以调用 get_user_model() 当Django正在导入模型时,您可以使用 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

一个字符串,描述用作唯一标识符的用户模型上的字段的名称。这通常是某种类型的用户名,但也可以是电子邮件地址或任何其他唯一标识符。田野 must 独一无二的(例如 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 管理命令。将提示用户为每个字段提供一个值。它必须包括 blankFalse 或者未定义,并且可能包括需要在交互创建用户时提示的其他字段。 REQUIRED_FIELDS 在Django的其他部分没有效果,比如在admin中创建用户。

例如,下面是用户模型的部分定义,它定义了两个必需字段-出生日期和身高:

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

备注

REQUIRED_FIELDS 必须包含用户模型上的所有必需字段,但应该 not 包含 USERNAME_FIELDpassword 因为这些字段总是会被提示输入。

is_active

指示用户是否被视为“活动”的布尔属性。此属性作为上的属性提供 AbstractBaseUser 拖欠 True . 您选择如何实现它将取决于所选认证后端的详细信息。参见 is_active attribute on the built-in user model 有关详细信息。

get_full_name()

可选的。用户的较长形式标识符,如全名。如果实现,它将出现在对象历史记录中的用户名旁边。 django.contrib.admin .

get_short_name()

可选的。用户的简短非正式标识符,如他们的名字。如果实现,则将替换标题中的用户问候语中的用户名。 django.contrib.admin .

进口 AbstractBaseUser

AbstractBaseUserBaseUserManager 可从导入 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' 如果 EMAIL_FIELD 未指定。

classmethod normalize_username(username)

将NFKC Unicode规范化应用于用户名,以便将具有不同Unicode代码点的视觉相同字符视为相同。

is_authenticated

只读属性 True (而不是 AnonymousUser.is_authenticated 总是这样 False )这是一种判断用户是否已通过身份验证的方法。这并不意味着任何权限,也不检查用户是否处于活动状态或是否具有有效会话。即使正常情况下您将在 request.user 以确定它是否由 AuthenticationMiddleware (表示当前登录的用户),您应该知道该属性是 True 对于任何 User 实例。

is_anonymous

只读属性 False . 这是一种区分 UserAnonymousUser 对象。一般来说,您应该更喜欢使用 is_authenticated 到这个属性。

set_password(raw_password)

将用户密码设置为给定的原始字符串,注意密码散列。不保存 AbstractBaseUser 对象。

当原始密码为 None ,密码将设置为不可用的密码,就像 set_unusable_password() 使用。

check_password(raw_password)
acheck_password(raw_password)

Asynchronous versionacheck_password()

返回 True 如果给定的原始字符串是用户的正确密码。(这将在进行比较时考虑密码散列。)

Changed in Django 5.0:

acheck_password() 添加了方法。

set_unusable_password()

将用户标记为未设置密码。这与密码的空字符串不同。 check_password() 因为这个用户永远不会回来 True . 不保存 AbstractBaseUser 对象。

如果应用程序针对现有外部源(如LDAP目录)进行身份验证,则可能需要这样做。

has_usable_password()

返回 False 如果 set_unusable_password() 已为此用户调用。

get_session_auth_hash()

返回密码字段的HMAC。用于 密码更改时会话无效 .

get_session_auth_fallback_hash()

使用生成密码字段的HMAC SECRET_KEY_FALLBACKS 。使用者 get_user()

AbstractUser 子类 AbstractBaseUser

class models.AbstractUser
clean()

通过调用规范电子邮件 BaseUserManager.normalize_email() . 如果重写此方法,请确保调用 super() 保持正常化。

为自定义用户模型编写管理器

您还应该为您的用户模型定义一个自定义管理器。如果用户模型定义 usernameemailis_staffis_activeis_superuserlast_logindate_joined 字段与Django的默认用户相同,您可以安装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_FIELDREQUIRED_FIELDS ,这些方法接收 to_field (the primary_key 默认情况下)。

BaseUserManager 提供以下实用方法:

class models.BaseUserManager
classmethod normalize_email(email)

通过降低电子邮件地址的域部分来规范电子邮件地址。

get_by_natural_key(username)

使用指定字段的内容检索用户实例 USERNAME_FIELD .

扩展Django的默认值 User

如果你对Django的 User 模型,但是您想要添加一些附加的配置文件信息,您可以子类 django.contrib.auth.models.AbstractUser 并添加您的自定义配置文件字段,尽管我们建议使用单独的模型,如中所述 指定自定义用户模型AbstractUser 提供默认设置的完整实现。 User 作为一个 abstract model

自定义用户和内置的身份验证表单

Django的内置 formsviews 对他们正在使用的用户模型进行某些假设。

以下表单与的任何子类都兼容 AbstractBaseUser

以下表单对用户模型进行假设,如果满足这些假设,则可以按原样使用:

  • 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"]}),)

a full example 了解更多详细信息。

自定义用户和权限

为了方便将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 格式为 "<app label>.<permission codename>" (见 permissions )如果 User.is_activeis_superuser 都是 True ,此方法始终返回 True .

如果 obj 如果传入,此方法将不会检查模型的权限,而是检查此特定对象的权限。

has_perms(perm_list, obj=None)

返回 True 如果用户具有每个指定的权限,则每个权限的格式为 "<app label>.<permission codename>" .如果 User.is_activeis_superuser 都是 True ,此方法始终返回 True .

如果 obj 如果传入,此方法将不会检查模型的权限,而是检查特定对象的权限。

has_module_perms(package_name)

返回 True 如果用户在给定包(django app标签)中有任何权限。如果 User.is_activeis_superuser 都是 True ,此方法始终返回 True .

PermissionsMixin and ModelBackend

如果不包括 PermissionsMixin ,必须确保不在上调用权限方法 ModelBackend . ModelBackend 假设某些字段在您的用户模型上可用。如果您的用户模型没有提供这些字段,那么检查权限时将收到数据库错误。

自定义用户和代理模型

自定义用户模型的一个限制是,安装自定义用户模型将破坏任何代理模型扩展 User . 代理模型必须基于具体的基类;通过定义自定义的用户模型,可以消除Django可靠地标识基类的能力。

如果项目使用代理模型,则必须修改代理以扩展项目中使用的用户模型,或者将代理的行为合并到 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)

最后,使用 AUTH_USER_MODEL 设置在你的 settings.py ::

AUTH_USER_MODEL = "customauth.MyUser"