自定义认证系统
Django自带的认证系统足够应对大多数情况,但难免有些项目有特殊需求需要自定义。
Django运行扩展默认的User模型,或实现一个完全定制的模型。甚至,“认证“的后端也能够被扩展。
扩展已有的用户模型
如果默认的User模型不能满足项目的需求,需要存储新的字段到已有的User里,那么你可以选着one-to-one relationship
来扩展用户信息。这种one-to-one
模型一般被称为资料模型(profile model),它通常被用来存储一些有关网站用户的非验证(non-auth)资料。例如,你可以创建一个员工模型 (Employee model):
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User)
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
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(UserAdmin):
inlines = (EmployeeInline, )
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
这些Profile models在任何方面都不特殊,它们就是和User model多了一个一对一链接的普通Django models。 这种情形下,它们不会在一名用户创建时自动创建, 但是 django.db.models.signals.post_save 可以在适当的时候用于创建或更新相关模型。
注意使用相关模型的成果需另外的查询或者联结来获取相关数据,基于你的需求替换用户模型并添加相关字段可能是你更好的选择。但是,在你项目应用程序中,指向默认用户模型的链接可能带来额外的数据库负载。
重写用户模型
Django内建的User模型可能不适合某些类型的项目。例如,在某些网站上使用邮件地址而不是用户名作为身份的标识可能更加合理。
Django 允许你通过AUTH_USER_MODEL
设置覆盖默认的User 模型, 其值引用一个自定义的模型。
AUTH_USER_MODEL = 'myapp.MyUser'
上面的值表示Django 应用的名称(必须位于INSTALLED_APPS 中) 和你想使用的User 模型的名称。
注意
改变 AUTH_USER_MODEL
对你的数据库结构有很大的影响。它改变了一些会使用到的表格,并且会影响到一些外键和多对多关系的构造。如果你打算设置AUTH_USER_MODEL
, 你应该在创建任何迁移或者第一次运行 manage.py migrate
前设置它。在你有表格被创建后更改此设置是不被makemigrations
支持的,并且会导致你需要手动修改数据库结构,从旧用户表中导出数据,可能重新应用一些迁移。
警告
由于Django的可交换模型的动态依赖特性的局限, 你必须确保 AUTH_USER_MODEL
引用的模型在所属app中第一个迁移文件中被创建(通常命名为 0001_initial); 否则, 你会碰到错误.
此外,在运行迁移时可能会遇到CircularDependencyError
,因为Django由于动态依赖性而无法自动断开依赖性循环。如果您看到此错误,您应该通过将依赖于您的用户模型的模型移动到第二个迁移中来打破循环(您可以尝试制作两个具有ForeignKey的正常模型,并查看makemigrations
.
可重复使用的应用不应实施自定义用户模型。项目可能使用许多应用程序,并且实施自定义用户模型的两个可重用应用程序不能一起使用。如果您需要在应用中存储每个用户的信息,请使用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
设置来指定自定义的模型。
from django.conf import settings
from django.db import models
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
连接用户模型发出的信号时, 应该使用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 已经导入所有的模型后才工作。
指定自定义的用户模型
创建一个规范的自定义模型最简单的方法是继承AbstractBaseUser
,AbstractBaseUser
提供User模型的核心实现,包括散列密码和令牌化密码重置。然后,您必须提供一些关键的实施细节:
class models.CustomUser
USERNAME_FIELD
描述User模型上用作唯一标识符的字段名称的字符串。这通常是某种用户名,但它也可以是电子邮件地址或任何其他唯一标识符。字段必须必须是唯一的(即在其定义中设置unique=True)。
在以下示例中,字段identifier用作标识字段:
class MyUser(AbstractBaseUser):
identifier = models.CharField(max_length=40, unique=True)
...
USERNAME_FIELD = 'identifier'
USERNAME_FIELD
现在支持ForeignKey。由于在createsuperuser提示期间没有办法传递模型实例,因此希望用户在默认情况下输入to_field
值(primary_key )的现有实例。
REQUIRED_FIELDS
当通过createsuperuser
管理命令创建一个用户时,用于提示的一个字段名称列。将会提示给列表里面的每一个字段提供一个值。 它包含的必须是 为 False 或者blank未定义的字段, 也可包含你想要在交互地创建一个新的用户时想要展示的其他字段。
例如,以下是定义两个必需字段(出生日期和身高)的User模型的部分定义:
class MyUser(AbstractBaseUser):
...
date_of_birth = models.DateField()
height = models.FloatField()
...
REQUIRED_FIELDS = ['date_of_birth', 'height']
注意:
REQUIRED_FIELDS
必须包含User模型中的所有必填字段,但不应包含USERNAME_FIELD或password,因为将始终提示输入这些字段。REQUIRED_FIELDS
现在支持ForeignKey
。由于在createsuperuser
提示期间没有办法传递模型实例,因此希望用户在默认情况下输入to_field值(primary_key )的现有实例。(1.8后版本)
is_active
指示用户是否被视为“活动”的布尔属性。此属性作为AbstractBaseUser
上的属性提供,默认为True。如何选择实施它将取决于您选择的身份验证后端的详细信息。
get_full_name()
用户更长且正式的标识.常见的解释会是用户的完整名称,但它可以是任何字符串,用于标识用户。
get_short_name()
一个短的且非正式用户的标识符。常见的解释会是第一个用户的名称,但它可以是任意字符串,用于以非正式的方式标识用户。它也可能会返回与django.contrib.auth.models.User.get_full_name()
相同的值。
class models.AbstractBaseUser
以下方法适用于AbstractBaseUser
的任何子类:
get_username()
返回由USERNAME_FIELD指定的字段的值。
is_anonymous()
始终返回False。这是区分AnonymousUser对象的一种方法。通常,您应该优先使用is_authenticated()
到此方法。
is_authenticated()
始终返回True。这是一种判断用户是否已通过身份验证的方法。这并不意味着任何权限,并且不检查用户是否处于活动状态 - 它仅指示用户已提供有效的用户名和密码。
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 on password change。
class models.CustomUserManager
你应该再为你的User模型自定义一个管理器。 如果你的User模型定义了这些字段username, email, is_staff, is_active, is_superuser, last_login, and date_joined
跟默认的User的字段是一样的话, 那么你就使用Django的UserManager就行了;反之, 如果你的User定义了不同的字段, 你就要去自定义一个管理器,它继承自BaseUserManager并提供两个额外的方法:
create_user(username_field, password=None, **other_fields)
create_user()
原本接受username,以及其它所有必填字段作为参数。例如,如果你的user模型使用 email 作为username字段, 并且使用 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()
的原型应该接受用户名字段,以及所有必需的字段作为参数。例如,如果您的用户模型使用email作为用户名字段,并且date_of_birth为必填字段,则create_superuser
应定义为:
def create_superuser(self, email, date_of_birth, password):
# create superuser here
...
与create_user()
不同,create_superuser()
必须要求调用方提供密码。
class models.BaseUserManager
BaseUserManager提供以下实用程序方法:
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,第一号)
- o,O和0(小写字母o,大写字母o和零)