Django 中的自定义验证

Django自带的验证机制足以应对一般情况,但是你可能不满足于默认的可立即使用的配置。在你的项目中自定义验证机制,需要了解在已有验证系统中哪些地方是可以扩展的,哪些地方是可以代替的。这个文档提供了如何自定义验证系统的一些细节。

<authentication-backends>当用户模型中存储的用户名和密码需要不同于Django默认服务的验证时,提供了一个可扩展系统。

你可以给你的模型 定制权限 并且可以被Django的授权系统通过检查。

你可以 :ref:` 扩展 <extending-user> ` 默认的 User 模型,或者完全自定义一个模型进行 :ref:` 替换 <auth-custom-user>`

其它认证资源

有时候你需要连接到其他认证源——一个包含用户名及密码的源或者认证方法。

例如,你的公司可能已经存在一套存储所有员工用户名及密码的 LDAP 配置。如果用户在LDAP和基于Django的应用程序中都有独立账号,那对用户自己或者网络管理员都会造成麻烦。

所以,为了处理这样的情况,Django认证系统可以让你插入其他认证源。您可以重写Django的默认基于数据库的方案,或者可以与其他系统一起使用默认系统。

请参阅<authentication-backends-reference>身份验证后端引用,有关Django中包含的身份验证后端的信息。

指定授权后端

在幕后,Django维护一个“身份验证后端”列表,用于检查身份验证。当有人调用:func:django.contrib.auth.authenticate() - 如下所示:ref:如何在`用户登录<how-to-log-a-user-in> - Django尝试所有身份验证后端进行身份验证。如果第一个验证方法失败,Django会尝试第二个验证方法,依此类推,直到所有后端都被尝试。

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

默认, :setting:`AUTHENTICATION_BACKENDS`设定为:

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

Django 的默认后台只检查其数据库和内置权限,并不提供任何登录限制机制来防止暴力登录攻击。如果需要抵制暴力登录攻击,需要自己在后台实现登录限制机制,或者使用 Web 服务器提供的保护机制。

:setting:`AUTHENTICATION_BACKENDS`是有序的,如果相同的用户名和密码对于多个后端都是合法的,那么 Django 会优先使用其中的第一个后端,而不会再处理后面的后端。

如果一个后端抛出 PermissionDenied 异常,则验证流程立马终止,Django 不会继续检查其后的后端。

注解

一旦用户通过验证,Django 会将之前用于验证该用户的后端保存在用户的 session 中,以便在将来(session 有效期内)需要访问当前已验证的用户时可以重用该后端。这个优化意味着在 session 中缓存了验证后端的源代码,因此,如果你修改了 AUTHENTICATION_BACKENDS 同时希望使用另外的方法重新验证用户,那么需要清除 session 数据。清除 session 数据的一个简单方法是执行 Session.objects.all().delete()

编写一个验证后端

一个验证后端其实就是一个 class,它实现了两个必要的方法:get_user(user_id)authenticate(request, **credentials),以及其它一系列可选的权限相关的方法:ref:`authorization methods<authorization_methods> `.

“get_user”方法的参数是”user_id”,并返回一个user对象或None。参数user_id也可能是username、数据库id,或者其他值,但这个参数必须你的user对象的主键。

authenticate``方法接受 ``request 参数和 credentials 关键字参数,大多数情况下,该方法类似于下面的代码:

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

但它也可能验证一个Token,就像这样:

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

无论是哪一种方式,authenticate()``都应该检查所获得的凭证,并当凭证有效时返回一个用户对象。当凭证无效时,应该返回``None

requestHttpRequest ,默认为 None 如果没有被提供给 authenticate() (它把request传给后端).

Django admin 和 Django User object. 紧密结合。最好的处理方式是为你后端的每一个用户都创建一个 Django User 。(例如, 你的 LDAP 目录、你的外部 SQL 数据库等等。) 你能写一个脚本来提前做这件事情,或者让你的 authenticate 方法在一个用户第一次登录时做这件事。

下面是一段验证后端的示例代码,它通过在 ``settings.py``文件中定义的用户名和密码变量进行身份验证,并且当用户第一次验证时,创建一个Django的``User``对象。

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

class SettingsBackend:
    """
    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_group_permissions(), get_all_permissions(), has_perm(), and has_module_perms())委托给任何实现了这些函数的验证后端。

用户所拥有的权限将是所有验证后端返回的所有权限的一个超集。也就是说,如果任何后端之一将一个权限赋予了用户,那么Django最终也将该权限赋予这个用户。

如果一个后端在:meth:~django.contrib.auth.models.User.has_perm()PermissionDenied() 异常,则鉴权过程将立刻失败退出,并且Django将不再检查随后的后端。

上面的简单后端可以相当容易的实现管理员权限:

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

这将给予上例中被授权访问的用户以完全权限。注意,除了与对应的类 django.contrib.auth.models.User 函数相同的参数之外,后端的auth函数还接收user对象作为一个参数,这个user对象有可能是匿名user。

一个完整的鉴权实现能在 django/contrib/auth/backends.pyModelBackend 类里找到,这是默认的后端并且大多数时候查询 auth_permission 表。如果你希望为部分后端 API 提供自定义的行为,你可以利用 Python 继承和子类 ModelBackend 来替代自定义后端实现的完整 API 。

匿名用户的授权

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

Django的权限框架并没有存储匿名用户的权限。然而,传给验证后端的用户对象可能是一个:class:`django.contrib.auth.models.AnonymousUser`对象,使得后端可以自定义对匿名用户的验证。这对于那些编写可复用应用的作者来讲格外有用,因为他们可以将验证完全委托给验证后端,而不是通过设置。比如,当控制匿名访问的时候。

未激活用户的授权

非活跃用户就是:attr:~django.contrib.auth.models.User.is_active`字段设置为``False``的用户。:class:`~django.contrib.auth.backends.ModelBackend 和:class:`~django.contrib.auth.backends.RemoteUserBackend`验证后台禁止这些用户进行验证。如果用户有:attr:`~django.contrib.auth.models.CustomUser.is_active`字段,则所有的用户都允许进行验证。

如果你想用非活跃用户来验证,你可以使用:class: ~django.contrib.auth.backends.AllowAllUsersModelBackend 和:class: ~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend

权限系统支持匿名用户有权执行某些操作,而经过已验证的不活动用户则不能这样做。

在你的后端permission方法中,不要忘记测试user的``is_active``属性。

处理对象权限

Django的权限框架为对象权限提供了基础,尽管在内核中没有实现它。这意味着检查对象权限将始终返回``False``或空列表(取决于所执行的检查)。身份验证后端将为每个对象相关的授权方法接收关键字参数``obj``和``user_obj``,并可以适当地返回对象级权限。

自定义权限

为给定的模型对象创建自定权限,使用 permission , 参考: model Meta attribute<meta-options>

这个示例中的 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"),
        ]

当你运行:djadmin:manage.py migrate <migrate> 时,它只创建那些额外的权限。(创建权限的函数连接到:data:~django.db.models.signals.post_migrate 信号)。 你的代码负责在用户试图访问由应用程序提供的功能(查看任务(task),改变任务状态,关闭任务)时检查这些权限的值。继续上面的例子, 下面的语句检查是否一个用户能查看任务:

user.has_perm('app.close_task')

扩展现有的用户(User)模型

有两种方法可以扩展默认的User模型,而不需要用你的模型来替换它。如果你需要改变的只是行为,并且不需要改变数据库存储的内容,那么你可以建立一个基于User模型的代理模型。它允许代理模型提供很多功能,包括默认排序,自定义的管理器和自定义的模型方法等。

如果你想存储与User模型关联的信息,可以使用OneToOneField到包含其他信息字段的模型。这种one-to-one模型经常被称作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,您可以使用Django的标准相关模型约定访问相关信息:

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

添加资料(Profile)模型到管理后台的用户页面,需要定义一个 InlineModelAdmin  (这个例子中,我们使用 StackedInline ) 到你的app里的``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内置的 :类:`~django.contrib.auth.models.User` 模型 可能并不适合一些项目的身份验证需求。例如,在一些网站上使用邮件地址代替用户名来作为你的标识令牌更有意义。

Django 允许你为引用了自定模型的:setting: AUTH_USER_MODEL 设置一个值来重写默认的用户表。

AUTH_USER_MODEL = 'myapp.MyUser'

这个引号中描述的是Django应用的名称 ( 必须在你的配置`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由于动态依赖性而无法自动中断依赖循环。如果你遇到这个错误,则应通过移除依赖用户模型的其他模型,并进行二次迁移。(如果你想了解它通常是如何运行的,可以尝试建立两个相互指向彼此的外键的普通模型,并查看 makemigrations 如何解决该循环依赖关系。)

应用复用和 AUTH_USER_MODEL

可复用的app不能实现实现一个自定义模型。一个项目可能有很多app,如果有两个可复用的app实现了自定义用户模型,他们就不能同时使用。如果你需要在你的app里保存每一个用户信息,可以像下面这样使用 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 setting 。举例来说:

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() ,因此你可以使用 models.ForeignKey(get_user_model(), ...)

如果你的app使用多个用户模型进行测试,例如使用 @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(**kwargs):
    if kwargs['setting'] == 'AUTH_USER_MODEL':
        apps.clear_cache()
        from myapp import some_module
        some_module.UserModel = get_user_model()

指定自定义用户模型

当你使用自定义的用户模型开始项目时,请停止考虑这是否是项目的正确选择。

将所有用户相关的信息保存在一个模型中,不需要额外的或者更复杂的数据库查询来检索相关模型。另一方面,将特定 app 的用户信息存储在你自定义的用户模型相关的模型中可能更合适。这使得每个 app 指定它自己的数据需求,而不和其他 app 发生冲突或破坏。这意味着你可以尽可能保持你的用户模型简单,专注于验证,并满足 Django 期望的自定义用户模型的最低需求。

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

构造一个兼容的自定义用户模型的最简单方法是继承:class:~django.contrib.auth.models.AbstractBaseUser。:class:`~django.contrib.auth.models.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

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

REQUIRED_FIELDS

当通过命令行 createsuperuser 来创建用户时提示的必填字段列表。这个列表里的字段必须是非空或者未定义字段,也可以包含一些你想在创建用户时进行提示的附加字段。 REQUIRED_FIELDS 对Django的其他部分无效,比如在admin页面中创建用户。

比如说,这里是一个局部的用户模型,定义了两个必须的字段——生日和身高。

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 attribute on the built-in user model 了解详情。

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’。这个属性用于区分类:model.User和model.AnonymousUser对象。通常情况下,属性:’model.User.is_authenticated’应该置于只读。

set_password(raw_password)

设置用户密码,谨慎保存密码哈希。不可保存类’django.conrtib.auth.models.AbstractBaseUser’的对象。

如果密码为空,密码应设置为不可用密码。例如可以使用方法:django.contrib.auth.mode.Is.AbstractBaseUser.set_unusable_password()。

check_password(raw_password)

如果密码正确则返回’True’。(密码哈希值用于比较)

set_unusable_password()

将用户标记为没有设置密码。 这与密码使用空白字符串不同。 check_password() 此用户将永远不会返回True。 不保存 AbstractBaseUser 对象。

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

has_usable_password()

如果方法’django.contrib.auth.models.AbstractBaseUser.set_unusable_password()’被调用则返回’False’。

get_session_auth_hash()

返回密码字段的HMAC。用于密码更改后会话失效。

类:’models.AbstractUser是类:‘models.AbstractBaseUser’的子类。

class models.AbstractUser
clean()

调用方法:‘BaseUserManger.normalize_email’来标准化邮件。如果你要重写这个方法,确保调用super()以保持标准化。

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

你应该为你的用户模型定义一个自定义管理器。如果你的用户模型定义了`username``, email, is_staff, is_active, is_superuser, last_login, and ``date_joined`等变量域,这些变量Django均内置了。你只需要安装类:’django.contrib.auth,models.UserManager’;但是,如果你的用户模型还定义了其他的变量,那么你就需要扩展类:django.contrib.auth.models.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, **other_fields)

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

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

create_user() 不同的是,create_superuser() 要求调用者 必须 提供一个密码。

对于 USERNAME_FIELDREQUIRED_FIELDS 中的 ForeignKey ,这些方法接收现有实例的:attr:~.ForeignKey.to_field`(默认为 :attr:`~django.db.models.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')

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

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

扩展Django的默认用户模型

如果你对Django自带的用户模型完全满意,而你只想添加一些其他信息,你可以简单地继承 django.contrib.auth.models.AbstractUser 并添加你的自定义字段,尽管我们建议使用在“模型设计考虑因素”中描述 指定自定义用户模型 那样的单独的模型。 AbstractUser 提供默认 User 的完整实现作为 :ref:`abstract model ` 。

自定义用户和内建的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',)

自定义用户和 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_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):
        """
        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 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().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 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'