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 管理员与 Django 用户对象紧密耦合。例如,要让用户访问管理员,User.is_staff
和User.is_active
必须为True
(有关详细信息,请参见AdminSite.has_permission()
)。
解决此问题的最佳方法是为后端中存在的每个用户(例如,在您的 LDAP 目录、外部 SQL 数据库等中)创建一个 Django User
对象。您可以编写脚本提前执行此操作,或者您的authenticate
方法可以在用户第一次登录时执行此操作。
这是一个后端示例,它根据在您的settings.py
文件中定义的用户名和密码变量进行身份验证,并在用户第一次进行身份验证时创建 Django User
对象。在此示例中,创建的 Django User
对象是一个超级用户,它将对管理员拥有完全访问权限
from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class SettingsBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None):
login_valid = settings.ADMIN_LOGIN == username
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. There's no need to set a password
# because only the password from settings.py is checked.
user = User(username=username) # is_active defaults to True.
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
自定义权限¶
要为给定的模型对象创建自定义权限,请使用 permissions
模型 Meta 属性。
此示例 Task
模型创建了两个自定义权限,即用户可以或不可以对 Task
实例执行的操作,特定于您的应用程序。
class Task(models.Model):
...
class Meta:
permissions = [
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
]
这唯一的作用是在运行 manage.py migrate
(创建权限的函数连接到 post_migrate
信号)时创建这些额外的权限。当用户尝试访问应用程序提供的功能(更改任务状态或关闭任务)时,您的代码负责检查这些权限的值。继续上面的示例,以下代码检查用户是否可以关闭任务:
user.has_perm("app.close_task")
扩展现有的 User
模型¶
有两种方法可以扩展默认的 User
模型,而无需替换您自己的模型。如果您需要的更改纯粹是行为上的,并且不需要更改数据库中存储的内容,您可以基于 User
创建一个 代理模型。这允许使用代理模型提供的任何功能,包括默认排序、自定义管理器或自定义模型方法。
如果您希望存储与 User
相关的的信息,您可以使用 OneToOneField
连接到包含附加信息字段的模型。这个一对一模型通常被称为 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
要在管理员的用户页面中添加 profile 模型的字段,请在应用程序的 admin.py
中定义一个 InlineModelAdmin
(对于此示例,我们将使用 StackedInline
),并将其添加到与 User
类注册的 UserAdmin
类中:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from my_user_profile_app.models import Employee
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = "employee"
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [EmployeeInline]
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
这些 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 模型的名称。
在启动项目时使用自定义用户模型¶
如果您要启动一个新项目,您可以通过子类化 AbstractUser
来设置一个行为与默认用户模型相同的自定义用户模型:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
不要忘记将 AUTH_USER_MODEL
指向它。在创建任何迁移或第一次运行 manage.py migrate
之前执行此操作。
此外,在应用程序的 admin.py
中注册模型:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
在项目中途更改为自定义用户模型¶
在创建数据库表后更改 AUTH_USER_MODEL
是可能的,但可能很复杂,因为它会影响外键和多对多关系等。
此更改无法自动完成,需要手动修复模式,将数据从旧用户表移动,并可能手动重新应用一些迁移。有关步骤概述,请参见 #25313。
由于Django的可交换模型动态依赖特性存在限制,由AUTH_USER_MODEL
引用的模型必须在其应用的第一次迁移(通常称为0001_initial
)中创建;否则,将会出现依赖问题。
此外,在运行迁移时,您可能会遇到CircularDependencyError
错误,因为Django无法自动打破动态依赖导致的循环依赖。如果看到此错误,您应该通过将用户模型依赖的模型移动到第二次迁移来打破循环。(您可以尝试创建两个具有相互ForeignKey
关系的普通模型,看看makemigrations
如何解决该循环依赖,如果您想了解通常是如何完成的。)
可复用应用和AUTH_USER_MODEL
¶
可复用应用不应该实现自定义用户模型。一个项目可能使用许多应用,而两个实现了自定义用户模型的可复用应用无法一起使用。如果您需要在您的应用中存储每个用户的用户信息,请使用ForeignKey
或OneToOneField
关联到settings.AUTH_USER_MODEL
,如下所述。
引用User
模型¶
如果您直接引用User
(例如,在外部键中引用它),您的代码将无法在AUTH_USER_MODEL
设置已更改为其他用户模型的项目中工作。
- get_user_model()[source]¶
不要直接引用
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期望自定义用户模型满足的最低要求。
如果您使用默认的身份验证后端,则您的模型必须具有一个可用于标识的唯一字段。这可以是用户名、电子邮件地址或任何其他唯一属性。如果您使用可以支持它的自定义身份验证后端,则允许使用非唯一用户名字段。
构建符合要求的自定义用户模型最简单的方法是从AbstractBaseUser
继承。AbstractBaseUser
提供了用户模型的核心实现,包括哈希密码和令牌化的密码重置。然后,您必须提供一些关键的实现细节
- class models.CustomUser¶
- USERNAME_FIELD¶
一个字符串,描述用作唯一标识符的用户模型上字段的名称。这通常是某种用户名,但也可能是电子邮件地址或任何其他唯一标识符。除非您使用可以支持非唯一用户名的自定义身份验证后端,否则该字段*必须*是唯一的(例如,在其定义中设置了
unique=True
)。在下面的示例中,字段
identifier
用作标识字段class MyUser(AbstractBaseUser): identifier = models.CharField(max_length=40, unique=True) ... USERNAME_FIELD = "identifier"
- EMAIL_FIELD¶
一个字符串,描述
User
模型上电子邮件字段的名称。此值由get_email_field_name()
返回。
- REQUIRED_FIELDS¶
一个字段名称列表,在通过
createsuperuser
管理命令创建用户时将提示输入这些字段的值。系统会提示用户为这些字段中的每一个提供值。它必须包含blank
为False
或未定义的任何字段,并且可以包含在交互式创建用户时要提示的附加字段。REQUIRED_FIELDS
在Django的其他部分(如在管理界面创建用户)中没有效果。例如,这是一个用户模型的部分定义,它定义了两个必填字段——出生日期和身高
class MyUser(AbstractBaseUser): ... date_of_birth = models.DateField() height = models.FloatField() ... REQUIRED_FIELDS = ["date_of_birth", "height"]
注意
REQUIRED_FIELDS
必须包含用户模型上的所有必填字段,但不应包含USERNAME_FIELD
或password
,因为这些字段将始终提示输入。
- is_active¶
一个布尔属性,指示用户是否被认为是“活动”的。此属性作为
AbstractBaseUser
上的属性提供,默认为True
。您如何选择实现它将取决于您选择的身份验证后端的细节。有关详细信息,请参阅is_active attribute on the built-in user model
的文档。
- get_full_name()¶
可选。用户的较长的正式标识符,例如他们的全名。如果实现,这将与
django.contrib.admin
中对象的记录中的用户名一起显示。
- get_short_name()¶
可选。用户的简短非正式标识符,例如他们的名字。如果实现此功能,则会在
django.contrib.admin
的页眉中用它替换用户问候语中的用户名。
导入
AbstractBaseUser
可以从
django.contrib.auth.base_user
导入AbstractBaseUser
和BaseUserManager
,这样就可以在不包含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
(与始终为False
的AnonymousUser.is_authenticated
相反)。这是一种判断用户是否已通过身份验证的方法。这并不意味着任何权限,也不会检查用户是否处于活动状态或具有有效的会话。即使通常您会在request.user
上检查此属性以了解它是否已由AuthenticationMiddleware
填充(表示当前登录的用户),您也应该知道此属性对于任何User
实例都为True
。
- is_anonymous¶
只读属性,始终为
False
。这是一种区分User
和AnonymousUser
对象的方法。通常,您应该优先使用is_authenticated
而不是此属性。
- set_password(raw_password)¶
将用户的密码设置为给定的原始字符串,负责密码哈希。不保存
AbstractBaseUser
对象。当raw_password为
None
时,密码将设置为不可用的密码,就像使用了set_unusable_password()
一样。
- check_password(raw_password)¶
- acheck_password(raw_password)¶
异步版本:
acheck_password()
如果给定的原始字符串是用户的正确密码,则返回
True
。(这在进行比较时负责密码哈希。)Django 5.0 中的更改添加了
acheck_password()
方法。
- set_unusable_password()¶
将用户标记为未设置密码。这与密码为空字符串不同。
check_password()
对于此用户将永远不会返回True
。不保存AbstractBaseUser
对象。如果应用程序的身份验证针对现有外部源(例如LDAP目录)进行,则可能需要此功能。
- has_usable_password()¶
如果为此用户调用了
set_unusable_password()
,则返回False
。
- get_session_auth_hash()¶
返回密码字段的HMAC。用于更改密码时的会话失效。
- get_session_auth_fallback_hash()¶
使用
SECRET_KEY_FALLBACKS
生成密码字段的HMAC。get_user()
使用。
AbstractUser
是AbstractBaseUser
的子类
- class models.AbstractUser¶
- clean()¶
通过调用
BaseUserManager.normalize_email()
来规范化电子邮件。如果重写此方法,请务必调用super()
以保留规范化。
为自定义用户模型编写管理器¶
您还应该为您的用户模型定义一个自定义管理器。如果您的用户模型定义的username
、email
、is_staff
、is_active
、is_superuser
、last_login
和date_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_FIELD
或 REQUIRED_FIELDS
中,这些方法接收现有实例的 to_field
(默认为 primary_key
)的值。
BaseUserManager
提供以下实用程序方法
扩展 Django 的默认 User
¶
如果您对 Django 的 User
模型完全满意,但想要添加一些额外的个人资料信息,您可以继承 django.contrib.auth.models.AbstractUser
并添加自定义的个人资料字段,尽管我们建议使用 指定自定义用户模型 中描述的单独模型。AbstractUser
将默认 User
的完整实现作为 抽象模型 提供。
自定义用户和内置身份验证表单¶
Django 的内置 表单 和 视图 对其使用的用户模型做出了某些假设。
以下表单与 AbstractBaseUser
的任何子类兼容
AuthenticationForm
:使用USERNAME_FIELD
指定的用户名字段。
以下表单对用户模型做出了假设,如果满足这些假设,则可以按原样使用
PasswordResetForm
:假设用户模型具有一个字段,该字段使用get_email_field_name()
(默认为email
)返回的名称存储用户的电子邮件地址,该地址可用于识别用户,以及一个名为is_active
的布尔字段,以防止对非活动用户的密码重置。
最后,以下表单与 User
绑定,需要重写或扩展才能与自定义用户模型一起使用
如果您的自定义用户模型是 AbstractUser
的子类,则可以按这种方式扩展这些表单
from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = UserCreationForm.Meta.fields + ("custom_field",)
自定义用户和 django.contrib.admin
¶
如果您希望您的自定义用户模型也能与管理员一起使用,则您的用户模型必须定义一些附加属性和方法。这些方法允许管理员控制用户访问管理员内容的权限。
- class models.CustomUser
- is_staff¶
如果允许用户访问管理员站点,则返回
True
。
- is_active¶
如果用户帐户当前处于活动状态,则返回
True
。
- has_perm(perm, obj=None):
如果用户拥有指定的权限,则返回
True
。如果提供了obj
,则需要针对特定的对象实例检查权限。
- has_module_perms(app_label)
如果用户有权访问给定应用中的模型,则返回
True
。
您还需要将自定义用户模型注册到管理员。如果您的自定义用户模型扩展了 django.contrib.auth.models.AbstractUser
,则可以使用 Django 现有的 django.contrib.auth.admin.UserAdmin
类。但是,如果您的用户模型扩展了 AbstractBaseUser
,则需要定义一个自定义的 ModelAdmin
类。可以继承默认的 django.contrib.auth.admin.UserAdmin
;但是,您需要覆盖任何引用 django.contrib.auth.models.AbstractUser
上但在您的自定义用户类上不存在的字段的定义。
注意
如果您使用的是自定义 ModelAdmin
(它是 django.contrib.auth.admin.UserAdmin
的子类),则需要将自定义字段添加到 fieldsets
(用于编辑用户的字段)和 add_fieldsets
(用于创建用户时使用的字段)。例如
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
...
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)
有关更多详细信息,请参阅 完整示例。
自定义用户和权限¶
为了方便地将Django的权限框架集成到您自己的用户类中,Django 提供了 PermissionsMixin
。这是一个抽象模型,您可以将其包含在您的用户模型的类层次结构中,从而获得支持Django权限模型所需的所有方法和数据库字段。
PermissionsMixin
提供以下方法和属性:
- class models.PermissionsMixin¶
- is_superuser¶
布尔值。指定此用户拥有所有权限,无需显式分配。
- get_user_permissions(obj=None)¶
返回用户直接拥有的权限字符串集合。
如果传入
obj
,则仅返回此特定对象的用户的权限。
- get_group_permissions(obj=None)¶
返回用户通过其组拥有的权限字符串集合。
如果传入
obj
,则仅返回此特定对象的组权限。
- get_all_permissions(obj=None)¶
返回用户拥有的权限字符串集合,包括组权限和用户权限。
如果传入
obj
,则仅返回此特定对象的权限。
- has_perm(perm, obj=None)¶
如果用户拥有指定的权限,则返回
True
,其中perm
的格式为"<app label>.<permission codename>"
(参见 permissions)。如果User.is_active
和is_superuser
都为True
,则此方法始终返回True
。如果传入
obj
,此方法不会检查模型的权限,而是检查此特定对象的权限。
- has_perms(perm_list, obj=None)¶
如果用户拥有每个指定的权限,则返回
True
,其中每个perm的格式为"<app label>.<permission codename>"
。如果User.is_active
和is_superuser
都为True
,则此方法始终返回True
。如果传入
obj
,此方法不会检查模型的权限,而是检查特定对象的权限。
- has_module_perms(package_name)¶
如果用户在给定的包(Django应用标签)中拥有任何权限,则返回
True
。如果User.is_active
和is_superuser
都为True
,则此方法始终返回True
。
自定义用户和代理模型¶
自定义用户模型的一个限制是,安装自定义用户模型会破坏任何扩展 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)
最后,使用settings.py
中的 AUTH_USER_MODEL
设置指定自定义模型作为项目的默认用户模型。
AUTH_USER_MODEL = "customauth.MyUser"