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 不会继续检查其后的后端。
Note
一旦用户进行了身份验证,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
。
The Django admin is tightly coupled to the Django User object. For example, for a user to access the admin,
User.is_staff
and User.is_active
must be True
(see
AdminSite.has_permission()
for details).
The best way to deal with this is to create a Django User
object for each
user that exists for your backend (e.g., in your LDAP directory, your external
SQL database, etc.). You can either write a script to do this in advance, or
your authenticate
method can do it the first time a user logs in.
Here's an example backend that authenticates against a username and password
variable defined in your settings.py
file and creates a Django User
object the first time a user authenticates. In this example, the created Django
User
object is a superuser who will have full access to the admin:
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
要将配置文件模型的字段添加到管理员的用户页面中,在你的应用程序的 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 模型的名称。
启动项目时使用自定义用户模型¶
If you're starting a new project, you can set up a custom user model that
behaves identically to the default user model by subclassing
AbstractUser
:
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)
在项目中更改为自定义用户模型。¶
Changing AUTH_USER_MODEL
after you've created database tables is
possible, but can be complex, since it affects foreign keys and many-to-many
relationships, for example.
这个改动并不能自动完成,需要手动修复你的架构,将数据从旧的用户表移出,并有可能需要手动执行一些迁移操作。查看步骤概述,请查看 #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¶
用来描述用户模型中的邮件字段,该值通过
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"]
Note
REQUIRED_FIELDS
必须包含用户模型上的所有必填字段,但 不应 包含USERNAME_FIELD
或password
,因为这些字段总是会被提示。
- is_active¶
一个布尔属性,表示用户是否被认为是 “激活的”。 这个属性作为
AbstractBaseUser
的属性,默认为True
。你如何选择实现它将取决于你所选择的认证后端的细节。详情请参考内置用户模型的is_active 属性的文档
。
- get_full_name()¶
可选项。用户的较长身份标识符,比如用户的全名。如果已经设置,则会与用户名一起出现在
django.contrib.admin
中。
- get_short_name()¶
可选项。用户较短的身份标识符,比如用户的名。如果已经设置,它会在
django.contrib.admin
页面头部的欢迎词中替换用户名。
导入
AbstractBaseUser
AbstractBaseUser
andBaseUserManager
可以从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
。这个属性用于区分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
。(在进行比较时,这将处理密码散列。)Changed in 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。用于 session-invalidation-onpassword-change。
- 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`等字段,你可以安装 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_FIELD
或 REQUIRED_FIELDS
中的 ForeignKey
,这些方法接收现有实例的:attr:~.ForeignKey.to_field (默认为 primary_key
)的值。
BaseUserManager
提供以下实用方法:
扩展Django的默认用户模型¶
如果您对 Django 的 User
模型完全满意,但想要添加一些额外的个人资料信息,您可以子类化 django.contrib.auth.models.AbstractUser
并添加您自定义的个人资料字段,尽管我们建议按照 指定自定义用户模型 中描述的方式使用一个单独的模型。AbstractUser
提供了默认 User
的完整实现作为一个 抽象模型。
自定义用户和内建的auth表单¶
Django的内置 :ref:`forms ` 和 :ref:`views ` 对他们正在使用的用户模型做了一些假设。
以下表单与 AbstractBaseUser
的任何子类兼容:
AuthenticationForm
: 使用USERNAME_FIELD
指定的username字段。
以下表单对用户模型进行了假设,如果满足这些假设,则可以按原样使用:
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
上的字段的定义,这些字段不在你自定义的用户类中。
Note
如果正在使用自定义的 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
参数,则只返回指定对象所属组的权限。
- has_perm(perm, obj=None)¶
如果用户具有指定的权限,则返回
True
,其中perm
的格式为"<app label>.<permission codename>"
(see 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 app 标签 (the Django app label) 里的任何权限,则会返回
True
。如果User.is_active
和is_superuser
都为True
,则该方法一直返回True
。
自定义用户和代理模型¶
自定义用户模型的一个限制是安装自定义用户模型将破坏任何扩展 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"