Django入门教程

前言

第一章: django快速入门

第二章: django MTV架构

第三章: django视图

第四章: django模板

第五章: django模型

第六章: django后台管理系统

第七章: 项目实战-简易的博客系统

第八章:django表单

第九章:django用户认证系统

第十章:django中的会话

第十一章:django安全

第十二章:django性能优化

第十三章:django实用工具

首页 > Django入门教程 > 第九章:django用户认证系统 > 9.3节: 对User模型进行扩展

9.3节: 对User模型进行扩展

薯条老师 2020-10-05 10:00:04 232360 0

编辑 收藏

广州番禺Python, Java小班周末班培训

薯条老师在广州做Python和Java的小班培训,一个班最多10人,学员的平均就业薪资有11K不在广州的同学可以报名线上直播班,跟线下小班的同学们同步学习。培训的课程有Python爬虫,Python后端开发,Python办公自动化,Python大数据分析,Python量化投资,Python机器学习,Java中高级后端开发。授课详情请点击:http://chipscoco.com/?cate=6

9.3.1 对用户模型进行扩展

django内置的用户模型只提供了有限的字段,例如用户的账号名,密码,邮件等,添加额外的字段和操作方法时需要对用户模型进行扩展。

 本节重点介绍对用户模型进行扩展的三种方法:

(1) 从AbstractUser中派生子类进行扩展

(2) 使用OneToOneField与信号进行扩展

(3) 从AbstractBaseUser中派生子类进行扩展

9.3.2 从AbstractUser中派生子类

打开django.contrib.auth路径下的models.py,可以找到User模型的源码:

class User(AbstractUser):
    """
    Users within the django authentication system are represented by this model.
    Username and password are required. Other fields are optional.
    """
    
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

上文代码中的MyUser即为我们新定义的用户模型,如需新增一个手机号的字段,则直接在模型中添加相应的字段即可。举一个简单的例子,在MyUser中扩展一个手机号字段,代码如下:

# 导出AbstractUser类
from django.contrib.auth.models import AbstractUser
 
class MyUser(AbstractUser):
# 新增一个手机号phone_number
phone_number = models.CharField(max_length = 11, blank=True)
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

这样就实现了扩展。以MySQL为例,我们可以进入MySQL交互模式,查看数据表blog_myuser的所有字段:

image.png

从输出可以分析出,我们为内置的User模型扩展了一个名为phone_number的字段。User模型默认使用名为username的字段进行登录认证,我们在对User模型进行扩展时,可以通过USERNAME_FIELD属性来指定登录认证的字段。

代码实例-使用手机号作为username进行登录认证:

# 导出AbstractUser类
from django.contrib.auth.models import AbstractUser
 
class MyUser(AbstractUser):
    # 新增一个手机号phone_number
    phone_number = models.CharField(max_length = 11, blank=True)
    # 采用手机号+密码的方式进行登录认证
    USERNAME_FIELD = "phone_number"
    
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

对User模型进行扩展以后,需要在settings.py中修改全局变量AUTH_USER_MODEL的值,以告诉django, 使用新的用户模型来表示用户。AUTH_USER_MODEL的输出格式:

'app.MyUser'

app表示django应用的名称,MyUser为自定义的用户模型名。假设django应用名为chipscoco, 扩展的用户模型为MyUser, 则AUTH_USER_MODEL的值为:

'chipscoco.MyUser'

由于在模型中新增了用户模型,故还需要使用makemigrations和migrate命令进行模型迁移。django的后台管理系统中使用的仍是旧的User模型,此时还需要在django应用的admin.py中对扩展的用户模型进行注册。

 代码实例

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import MyUser
 
admin.site.register(MyUser, UserAdmin)

至此完成了用户模型的扩展。

在项目中使用AbstractUser派生子类进行扩展,需要在首次数据迁移的时候就定义好扩展的用户模型。

9.3.3 使用OneToOneField与信号进行扩展

使用OneToOneField进行扩展,是指将内置的User模型作为主表,然后将扩展的字段定义在从表中。在models.py中定义一个用来进行字段扩展的从表,代码实例:

from django.contrib.auth.models import User
from django.db import models
 
# 定义一个UserExtension模型,作为User模型的从表  
class UserExtension(models.Model):
    # 此行代码为关键,将User模型与UserExtension模型定义为一对一的外键关系,关联字段别名为extension
    user = models.OneToOneField(User,on_delete=models.CASCADE, related_name='extension')
    # 在从表中进行扩展,新增一个phone_number字段
    phone_number = models.CharField(blank=True)

定义完从表以后,需要再定义一个信号处理器,即当User对象执行save方法以后,可以自动地执行扩展对象的创建。信号处理器的代码实例:

from django.dispatch import receiver
from django.db.models.signals import post_save
 
@receiver(post_save,sender=User)
    def create_user_extension(sender,instance,created,**kwargs):
        # 在信号接收器内部创建扩展的用户对象
        if created:
            UserExtension.objects.create(user=instance)
        else:
            instance.extension.save()

在上文代码中使用@receiver装饰器来定义一个信号接收器,sender参数传递的为信号发送的对象,post_save表示User对象执行完save方法后才发送信号。视图中的代码实例:

from django.http import JsonResponse
from django.contrib.auth.models import User
  
def register(request):
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password") 
        
        user = User.objects.create_user(username=username,password=password)
        # 通过用户对象的关联字段extension来为扩展的字段赋值
        user.extension.phone_number = request.POST.get("phone_number","")
        user.save()
        
        # user对象执行完save方法后,会将信号发送给create_user_extension方法
        # 在create_user_extension内部会创建扩展的用户对象
        return JsonResponse("注册成功")

以OneToOneField的方式扩展以后,如需将扩展字段作为登录认证字段,该如何操作呢?以手机号为例,可以在User模型中通过关联字段反向查询用户对象,然后再调用用户对象的验证方法。

例如执行User.objects.filter(extension__phone_number="xx").first()获取到与扩展字段phone_number对应的用户对象,再通过用户对象的check_password方法验证密码是否正确 。

9.3.4 从AbstractBaseUser中派生子类进行扩展

前面两种用户模型的扩展方法都比较简单,如需修改默认的验证方式,且只保留新定义的字段,那么应该从AbstractBaseUser中继承一个子类。使用AbstractBaseUser进行扩展的核心流程:

(1) 定义用户模型的管理器

用户模型的管理器负责用户对象的创建,定义用户模型的管理器,需要从BaseUserManager中进行继承,并覆盖create_user以及create_superuser方法。现在对管理器进行扩展,使得在创建用户时必须传递一个电话号码的参数,以下代码定义在models.py中:

from django.contrib.auth.models import  BaseUserManager
 
class MyUserManager(BaseUserManager):
    def create_user(self, username, phone_number, password=None):
        """
        对管理器方法进行扩展,使用用户名,电话号码,密码来创建用户对象
        """
        
        if not phone_number:
            raise ValueError('用户必须有一个手机号')
        
    # 构造一个用户对象 
    user = self.model( phone_number=phone_number, username=username)
    user.set_password(password)
    user.save(using=self._db)
    return user
    
    def create_superuser(self, username, phone_number, password=None):
        """
        参数同create_user,创建一个管理员用户
        """
        
        user = self.create_user( username, phone_number=phone_number, password=password,)
        user.is_admin = True
        user.save(using=self._db)
        return user

(2) 从AbstractBaseUser进行继承,扩展其属性或方法

 AbstractBaseUser的主要属性:

属性名

描述

 

USERNAME_FIELD

用户的账号认证字段,默认使用username进行认证

EMAIL_FIELD

用户的邮箱地址

REQUIRED_FIELDS

使用createsuperuser命令来创建用户时的提示字段,使用REQUIRED_FIELDS时无需添加USERNAME_FIELD定义的字段和password字段,django会自动将其进行提示。

is_active

布尔类型,指示用户是否已激活

现在从AbastractBaseUser中进行继承,并扩展一个phone_number字段,以下代码定义在models.py中:

from django.contrib.auth.models import AbstractBaseUser
 
class MyUser(AbstractBaseUser):
    # 扩展一个phone_number字段
    phone_number = models.CharField(max_length=11)
    email = models.EmailField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    
    # 将objects赋值为我们在上文中定义的管理器对象
    objects = MyUserManager()
    
    # 使用手机号来作为认证字段
    USERNAME_FIELD = 'phone_number'
    # 执行createsuperuser命令时,将用户的邮箱地址作为提示
    REQUIRED_FIELDS = ['email']
    
    def __str__(self):
        return self.email
    
    def has_perm(self, perm, obj=None):
        # 通过该方法来获取用户是否有指定的权限
        return True
    
  def has_module_perms(self, app_label):
      # 通过方法来判断用户是否有app_label的view权限
      return True
 
    @property
    def is_staff(self):
        # 默认情况下管理员都可以访问后台管理系统
        return self.is_admin

在完成了(1)与(2)步的操作以后,如需使用后台管理系统管理用户,那么需要在admin.py中将自定义的用户模型进行注册,并修改后台系统的用户管理表单:

# 导出forms,重定义后台系统的用户管理表单
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
 
# 从modes.py中导出我们自定义的用户模型
from .models import MyUser
 
# 定义用户的创建表单
class UserCreationForm(forms.ModelForm):
    """创建用户的表单,包含所有必需的字段,外加一个需重复输入的密码字段"""
    password1 = forms.CharField(label='输入密码', widget=forms.PasswordInput)
    password2 = forms.CharField(label='确认密码', widget=forms.PasswordInput)
    
    class Meta:
        model = MyUser
        fields = ('username', 'phone_number','email')
        
        def clean_password2(self):
            # 检查两次输入的密码是否匹配
            password1 = self.cleaned_data.get("password1")
            password2 = self.cleaned_data.get("password2")
            if not password1 or not password2 or (password1 != password2):
                raise forms.ValidationError("密码不相等")
            return password2
            
            
        def save(self, commit=True):
            # 以哈希字符串的形式来保存用户的密码
            user = super().save(commit=False)
            user.set_password(self.cleaned_data["password1"])
            if commit:
                user.save()
            return user
 
 
# 定义用户的编辑表单
class UserChangeForm(forms.ModelForm):
    """
    用来对用户的信息进行编辑
    """
    
    # 显示用户的密码时,将其设置为只读的哈希字段
    password = ReadOnlyPasswordHashField()
    
    class Meta:
        model = MyUser
        # 定义用户模型在编辑页中出现的字段
        fields = ('username', 'password', 'phone_number','email', 'is_active', 'is_admin')
        
        def clean_password(self):
            # 不论用户输入什么都返回密码的输入值
            # 在编辑页中进行操作,而非对密码字段进行操作
            # 因为密码字段无法访问密码的原始值
            return self.initial["password"]
 
 
class UserAdmin(BaseUserAdmin):
    # 定义用户的编辑页表单和创建表单
    form = UserChangeForm
    add_form = UserCreationForm
    list_display = ('phone_number', 'username', 'is_admin')
    list_filter = ('is_admin',)
    
    fieldsets = (
        (None, {'fields': ('phone_number', 'password')}),
        ('个人信息', {'fields': ('username','email')}),
        ('权限', {'fields': ('is_admin',)}),)
        
        add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('phone_number', 'password1', 'password2')}
        ),)
  
  
        search_fields = ('phone_number',)
        ordering = ('phone_number',)
        filter_horizontal = ()
 
 
# 注册自定义的用户模型
admin.site.register(MyUser, UserAdmin)
# 由于我们在自定义的用户模型中并未使用django内置的权限
# 所以要取消Group模型的注册
admin.site.unregister(Group)

从AbstractBaseUser中派生子类后,同样需要在settings.py中进行配置,具体的配置方法可参考9.3.1 从AbstractUser中派生子类的内容。

9.3.5 最具实力的小班培训

薯条老师在广州做Python和Java的小班培训,一个班最多10人。不在广州的同学可以报名线上直播班,跟线下小班的同学们同步学习。打算参加小班培训的同学,必须遵守薯条老师的学习安排,认真做作业和项目。把知识学好,学扎实,那么找到一份高薪的工作就是很简单的一件事。

(1) Python后端工程师高薪就业班,月薪11K-18K,免费领取课程大纲
(2) Python爬虫工程师高薪就业班,年薪十五万,包拿Offer
(3) 数据分析高薪就业班,月薪11K-15K, 免费领取课程大纲
(4)
Python大数据挖掘,量化投资就业班,月薪12K-25K,免费领取课程大纲

扫码免费领取Python学习资料:



欢迎 发表评论: