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 用户数据库并查询内置权限。它不提供任何速率限制机制来防止暴力攻击。你可以在自定义身份验证后端中实现自己的速率限制机制,或者使用大多数 Web 服务器提供的机制。

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 admin 与 Django User 对象 紧密耦合。处理这个问题的最好方法是为每个存在于后台的用户创建一个 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 最终也将该权限赋予这个用户。

如果后端在 has_perm()has_module_perms() 中引发 PermissionDenied 异常,认证就会立即失败,Django 也不会检查后面的后端。

后端可以像这样为管理员实现权限:

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 函数提供相同的参数外,后台的认证函数都以用户对象(可能是一个匿名用户)作为参数。

一个完整的鉴权实现可以在 django/contrib/auth/backends.pyModelBackend 里找到,这是默认的后端,并且在大多数时候会查询 auth_permission 表。

匿名用户的授权

匿名用户是指那些没有验证过的用户,也就是说,他们没有提供任何有效的验证信息。然而,这并不一定意味着他们就无权做任何事。在最基本的层面上,大多数站点允许匿名用户浏览大部分页面,而且很多站点也允许匿名评论。

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

非激活用户认证

非激活用户是指 is_active 字段设置为 False 的用户。ModelBackendRemoteUserBackend 认证后端禁止这些用户进行认证。如果一个自定义用户模型没有 is_active 字段,那么所有用户都将被允许认证。

如果你想让非激活用户进行身份认证,可以使用 AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend

权限系统中对匿名用户的支持,可以实现匿名用户有权限做某事,而非激活的认证用户没有权限的场景。

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

处理对象权限

Django 的权限框架有一个对象权限的基础,尽管在核心中没有实现。这意味着对对象权限的检查总是返回 False 或一个空列表(取决于所执行的检查)。认证后端将为每个与对象相关的授权方法接收关键字参数 objuser_obj,并可以适当返回对象级别的权限。

自定义权限

要为给定的模型对象创建自定义权限,使用 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 到一个包含字段的模型,以获得额外的信息。这种一对一的模型通常被称为 profile 模型,因为它可能会存储一个站点用户的非认证相关信息。例如,你可以创建一个 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),并将其添加到一个 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)

这些 profile 模型并没有任何特殊之处——它们只是 Django 模型,恰好与用户模型有一对一的联系。因此,当用户被创建时,它们并不会自动创建,但可以用一个 django.db.models.signals.post_save 来适当地创建或更新相关模型。

使用相关模型会导致额外的查询或连接来检索相关数据。根据你的需求,包含相关字段的自定义用户模型可能是你更好的选择,然而,在你的项目应用程序中与默认用户模型的现有关系可能会证明额外的数据库负载是合理的。

替换一个自定义的 User 模型。

有些项目可能会有认证需求,而 Django 内置的 User 模型并不总是合适。例如,在一些网站上,使用电子邮件地址作为你的身份识别标记比使用用户名更有意义。

Django 允许你通过为 AUTH_USER_MODEL 配置提供一个引用自定义模型的值来覆盖默认的用户模型:

AUTH_USER_MODEL = "myapp.MyUser"

这个点对描述了 Django 应用程序的 label (必须在您的 INSTALLED_APPS 中),以及您希望用作用户模型的 Django 模型的名称。

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

如果你准备启动一个新的项目,强烈推荐你设置一个自定义的用户模型,即使默认的用户模型对你来说已经足够了。这个模型的行为与默认用户模型相通,但是你能在未来需要的时候自定义它:

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

不要忘记将 AUTH_USER_MODEL 指向它。在创建任何迁移或者首次运行 manage.py migrate 之前执行这个操作。

同样的,在 app 中的 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

可重用应用不应该实现自定义用户模型。一个项目可能会使用很多应用,两个实现了自定义用户模型的可重用应用不能一起使用。如果你需要在你的应用中存储每个用户的信息,可以使用一个 ForeignKeyOneToOneField 到 settings.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 配置来引用用户模型是最简单的,不过,也可以在 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 期望自定义用户模型满足的最低要求。

如果你使用默认的认证后端,那么你的模型必须有一个可用于识别目的的唯一字段。这可以是一个用户名,一个电子邮件地址,或任何其他独特的属性。如果你使用可以支持的自定义认证后端,则允许使用非唯一的用户名字段。

最简单的方法是继承 AbstractBaseUserAbstractBaseUser 提供了用户模型的核心实现,包括哈希密码和令牌化密码重置。然后你必须提供一些关键的实现细节。

class models.CustomUser
USERNAME_FIELD

一个字符串,描述了在用户模型上用作唯一标识符的字段的名称。通常,这将是某种用户名,但它也可以是电子邮件地址或任何其他唯一标识符。该字段 必须 是唯一的(例如,在其定义中设置了 unique=True),除非您使用可以支持非唯一用户名的自定义身份验证后端。

接下来的样例中,identifier 字段将被用作识别字段:

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

用来描述用户模型中的邮件字段,该值通过 get_email_field_name() 返回。

REQUIRED_FIELDS

在通过 createsuperuser 管理命令创建用户时,将提示用户输入的字段名称列表。用户将被提示为每个字段提供一个值。它必须包括 blankFalse 或未定义的任何字段,并可能包括你希望在交互式创建用户时提示的其他字段。REQUIRED_FIELDS 在 Django 的其他部分没有效果,比如在管理中创建用户。

例如,这里是一个用户模型的部分定义,它定义了两个必填字段——出生日期和身高:

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

备注

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

is_active

一个布尔属性,表示用户是否被认为是 “激活的”。 这个属性作为 AbstractBaseUser 的属性,默认为 True。你如何选择实现它将取决于你所选择的认证后端的细节。详情请参考内置用户模型的 is_active 属性的文档

get_full_name()

可选项。用户的较长身份标识符,比如用户的全名。如果已经设置,则会与用户名一起出现在 django.contrib.admin 中。

get_short_name()

可选项。用户较短的身份标识符,比如用户的名。如果已经设置,它会在 django.contrib.admin 页面头部的欢迎词中替换用户名。

导入 AbstractBaseUser

AbstractBaseUser and BaseUserManager 可以从 django.contrib.auth.base_user 中导入,所以你无需在 INSTALLED_APPS 添加 django.contrib.auth 就能导入它们。

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 (匿名用户 AnonymousUser.is_authenticated 始终返回 False )。这是一种判断用户是否已通过身份验证的方法。这并不意味着任何权限,也不会检查用户是否处于活动状态或是否具有有效会话。即使通常您会根据 request.user 检查这个属性,以确定它是否被 AuthenticationMiddleware 填充(表示当前登录的用户),但是你应该知道该属性对于任何 User 实例都返回 True

is_anonymous

只读属性总是 False。这个属性用于区分 UserAnonymousUser 对象。通常情况下, is_authenticated 应该置于只读。

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-onpassword-change

get_session_auth_fallback_hash()
New in Django 4.1.8.

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

AbstractUserAbstractBaseUser 的子类。

class models.AbstractUser
clean()

通过调用 BaseUserManager.normalize_email() 来规范化邮件。如果你覆盖了这个方法,一定要调用 super() 来保留规范化。

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

你也该为你的用户模型定义一个自定义管理器。如果你的用户模型定义了和默认用户相同的 username, email, is_staff, is_active, is_superuser, last_login, 和 ``date_joined`等字段,你可以安装 UserManager ;但是,如果你的用户模型还定义了其他字段,那么就需要定义一个自定义管理器,它扩展了 BaseUserManager ,提供了两个额外的方法:

class models.CustomUserManager
create_user(username_field, password=None, **other_fields)

create_user() 的原型应该接受username字段,加上其他所有必须的字段作为参数。举例,如果你的用户模型使用 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() 的原型应该接受username字段,加上其他所有必须的字段作为参数。举例,如果你的用户模型使用 email 作为用户名字段,date_of_birth 字段作为必填字段,那么 create_superuser 应该如下定义:

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

对于 USERNAME_FIELDREQUIRED_FIELDS 中的 ForeignKey ,这些方法接收现有实例的:attr:~.ForeignKey.to_field (默认为 primary_key )的值。

BaseUserManager 提供以下实用方法:

class models.BaseUserManager
classmethod normalize_email(email)

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

get_by_natural_key(username)

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

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

4.2 版后已移除.

返回具有给定长度和给定字符串的随机密码。请注意, allowed_chars 的默认值不包含可能导致用户混淆的字母,包括:

  • i, l, I, 和 1 (小写i, 小写L, 大写i和数字1)
  • o, O, 和 0 (小写 o, 大写 o, 和数字0 )

扩展Django的默认用户模型

如果您对 Django 的 User 模型完全满意,但想要添加一些额外的个人资料信息,您可以子类化 django.contrib.auth.models.AbstractUser 并添加您自定义的个人资料字段,尽管我们建议按照 指定自定义用户模型 中描述的方式使用一个单独的模型。AbstractUser 提供了默认 User 的完整实现作为一个 抽象模型

自定义用户和内建的auth表单

Django的内置 :ref:`forms ` 和 :ref:`views ` 对他们正在使用的用户模型做了一些假设。

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

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

  • PasswordResetForm :假设用户模型有一个字段存储用户的电子邮件地址,其名称由 get_email_field_name() 返回(默认为电子邮件),这个方法用来标识用户,以及名为``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",)
Changed in Django 4.2:

在旧版本中,对于自定义用户模型,UserCreationForm 没有保存多对多表单字段。

自定义用户和 django.contrib.admin

如果你希望自定义的用户模型也与管理后台一起使用,那么你的用户模型必须定义一些额外的属性和方法。这些方法允许管理员控制用户对管理后台内容的访问:

class models.CustomUser
is_staff

如果允许用户有访问 admin 页面就返回 True

is_active

返回``True``,如果该用户的账号当前是激活状态

has_perm(perm, obj=None):

如果用户有指定的权限,则返回 True 。如果提供了参数 obj  ,则需要对指定的对象实例进行权限检查。

has_module_perms(app_label):

如果用户有权限访问指定 app 里的模型,那么返回 True 。

你也需要在 admin 文件里注册自定义的用户模型。如果自定义的用户模型扩展了 django.contrib.auth.models.AbstractUser ,你可以直接使用Django已有的类 django.contrib.auth.admin.UserAdmin 。如果你的用户模型扩展了 AbstractBaseUser ,你将需要定义一个自定义的类 ModelAdmin 。不管怎样,你都将需要重写任何引用 django.contrib.auth.models.AbstractUser 上的字段的定义,这些字段不在你自定义的用户类中。

备注

如果正在使用自定义的 ModelAdmindjango.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>" (see permissions)。如果 User.is_activeis_superuser 都为 True,则这个方法一直返回 True

如果传入 obj 参数,则这个方法不会检查该模型权限,而只会检查这个出传入对象的权限。

has_perms(perm_list, obj=None)

如果用户具有指定权限列表里的每个权限,则返回 True ,其中perm的格式为 "<app label>.<permission codename>" 。如果 User.is_activeis_superuser 都返回 True ,则这个方法一直返回 True

如果传入参数 obj  ,则这个方法不会检查指定的权限列表,只检查指定对象的权限。

has_module_perms(package_name)

如果用户拥有所给的 Django app 标签 (the Django app label) 里的任何权限,则会返回 True 。如果 User.is_activeis_superuser 都为 True ,则该方法一直返回 True

PermissionsMixinModelBackend

如果你没有引入 PermissionsMixin ,必须确保没有调用 ModelBackend 的权限方法。ModelBackend 假定你的用户模型某些字段可用。如果你的用户模型没有提供这些字段,则当你检查权限的时候,会收到数据库错误提示。

自定义用户和代理模型

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

如果你的项目正在使用代理模型,你必须修改扩展用户模型的代理,或者把代理的行为都合并到 User 子类里去。

一个完整的例子

这里是一个兼容admin的自定义的用户app的例子。这个用户模型使用 email 地址作为username,并且生日是必填字段;除了用户账户上的 admin flag之外,它本身不提供权限检查。除用户创建的表单外,此模型和所有内置的身份验证表单和视图兼容。此例只是说明了大多数组件如何协同工作,不要直接复制到生产环境里。

这段代码将一直存在于 models.py 文件中,用于自定义身份验证 app:

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 管理后台里注册这个用户模型,下面这些代码必须在 app 的 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 里指定自定义的用户模型为默认的用户模型。

AUTH_USER_MODEL = "customauth.MyUser"
Back to Top