Bootstrap

10. Django Auth认证系统

image-20240509191905388

10. Auth认证系统

Django除了内置的Admin后台系统之外, 还内置了Auth认证系统.
整个Auth认证系统可分为三大部分: 用户信息, 用户权限和用户组, 在数据库中分别对应数据表auth_user, auth_permission和auth_group.

10.1 内置User实现用户管理

用户管理是网站必备的功能之一, Django内置的Auth认证系统不仅功能完善, 而且具有灵活的扩展性, 可以满足多方面的开发需求.
创建项目时, Django已默认使用内置Auth认证系统, 
在settings.py的INSTALLED_APPS, MIDDLEWARE和AUTH_PASSWORD_VALIDATORS中都能看到相关的配置信息.

本节将使用内置的Auth认证系统实现用户注册, 登录, 修改密码和注销功能.
在D盘下创建新的MyDjango项目, 添加项目应用user, 并新建templates和static文件夹.
在templates中放置模板文件user.html, 在static中放置模板文件user.html所需的JS和CSS文件.
项目的目录结构如图10-1所示.

image-20240507125223225

10-1 目录结构
打开MyDjango的配置文件settings.py, 将项目应用user,
模板文件夹templates和静态资源文件夹static添加到Django的运行环境, 配置信息如下:
# MyDjango 的 settings.py 

# 注册应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user.apps.UserConfig',
]

# 模板配置
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# 配置静态文件夹static
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']

2024-05-07_130046

完成MyDjango的基本配置后, 在PyCharm的Terminal下执行数据迁移指令, 项目内置的数据表创建在MyDjango的db.sqlite3数据库文件中.
# 数据迁移:
python manage.py makemigrations
python manage.py migrate

image-20240507130319409

打开并查看db.sqlite3数据库文件的数据表信息, 如图10-2所示.

image-20240507130424392

10-2 数据表信息
项目环境搭建成功后, 在项目应用user中创建urls.py文件, 
并分别在MyDjango文件夹的urls.py和user的urls.py中定义用户注册, 登录, 修改密码和注销的路由信息, 代码如下:
# MyDjango的urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(('user.urls', 'user'), namespace='user')),
]

image-20240507162811328

没有使用的路由先注释掉, 否则会报错.
# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    # path('login.html', login_view, name='login'),  # 登录
    path('register.html', register_view, name='register'),  # 注册
    # path('set_password.html', set_password_view, name='set_password'),  # 修改密码
    # path('logout.html', logout_view, name='logout'),  # 注销
]

image-20240507170956933

项目应用user定义了4条路由信息, 分别代表用户注册, 登录, 修改密码和注销, 
4条路由所对应的网页信息都由模板文件user.html生成, 因此在模板文件user.html中编写以下代码:
<!-- templates 的 user.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>{{ Title }}</title>
    <link rel="stylesheet" href="{% static "css/reset.css" %}">
    <link rel="stylesheet" href="{% static "css/user.css" %}">
    <script src="{% static "js/jquery.min.js" %}"></script>
    <script src="{% static "js/user.js" %}"></script>
</head>
<body>
<div class="page">
    <div class="loginwarrp">
        <div class="logo">{{ page_title }}</div>
        <div class="login_form">
            <form id="Login" name="Login" method="post" action="">
                {% csrf_token %}
                <li class="login-item">
                    <label for="id_username">用户名称:</label>
                    <input type="text" id="id_username" name="username" class="login_input">
                    {# <p id="count-msg" class="error">{{ tips }}</p> #}
                </li>
                <li class="login-item">
                    <label for="id_password">用户密码:</label>
                    <input type="password" id="id_password" name="password" class="login_input">
                    {# <p id="password-msg" class="error">{{ tips }}</p> #}
                </li>
                {% if password2 %}
                    <li class="login-item">
                    <label for="id_password2">新密码:</label>
                    <input type="password" id="id_password2" name="password2" class="login_input">
                    {# <p id="password-msg" class="error">{{ tips }}</p> #}
                    </li>
                {% endif %}
                <div>{{ tips }}</div>
                <li class="login-sub">
                    <input type="submit" name="Submit" value="确认">
                </li>
            </form>
        </div>
    </div>
</div>
{# 画布粒子 #}
<script type="text/javascript">
    window.onload = function () {
        var config = {
            vx: 4,
            vy: 4,
            height: 2,
            width: 2,
            count: 100,
            color: "121, 162, 185",
            stroke: '100, 200, 180',
            dist: 6000,
            e_dist: 20000,
            max_conn: 100
        };
        CanvasParticle(config);
    }
</script>
{# 画布粒子文件 #}
<script src="{% static 'js/canvas-particle.js' %}"></script>
</body>
</html>

image-20240507174147483

完成项目的功能配置, 路由设置和模板代码编写后, 最后在user的views.py里定义视图函数.
首先定义视图函数register_view, 实现用户注册功能, 代码如下:
# user 的 views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth import login, logout, authenticate


# 用户注册
def register_view(request):
    # 设置模板上下文
    title = '注册页面'
    page_title = '用户注册'

    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password', '')
        if User.objects.filter(username=u):  # 判断用户是否存在
            tips = '用户已经存在!'

        else:
            # 创建用户
            d = dict(username=u, password=p, is_staff=1, is_superuser=1)
            user = User.objects.create_user(**d)
            user.save()
            tips = '注册成功, 请登录!'

    return render(request, 'user.html', locals())

image-20240507163437891

视图函数register_view对用户请求进行判断分析, 如果当前请求为GET请求, 就调用模板文件user.html生成用户注册页面;
如果当前请求为POST请求, 就由内置的Auth认证系统执行用户注册过程, 具体说明如下:
(1) 当用户在注册页面输入账号和密码, 并单击'确定'按钮后, 程序将表单数据提交到函数register_view中进行处理.
(2) 函数register_view首先获取表单的数据内容, 根据获取的数据来判断Django内置模型User是否存在相关的用户信息.
(3) 如果用户存在, 就直接返回注册页面并提示用户已存在.
(4) 如果用户不存在, 程序就使用内置函数create_user对模型User进行用户创建,
    函数create_user是模型User特有的函数, 该函数创建并保存一个is_active= True的User对象.
    其中, 函数参数username不能为空, 否则抛出ValueError异常;
    而模型User的其他字段可作为函数create_user的可选参数, 模型User的字段可在数据表auth_user中查看,
    如email, first_name和password等.
    如果没有设置参数password, 模型User就调用set_unusable_password()为当前用户创建一个随机密码.
Django的内置模型User一共定义了11个字段, 各个字段的含义说明如表10-1所示.
10-1 User模型各个字段的说明
字段类型说明
idint数据表主键
passwordvarchar代表用户密码,在默认情况下使用pbkdf2_sha256方式来存储和管理用户的密码
last_logindatetime最近一次登录的时间
is_superuserbool表示该用户是否拥有所有的权限,即是否为超级管理员
usernamevarchar代表用户账号
first_namevarchar代表用户的名字
last_namevarchar代表用户的姓氏
emailvarchar代表用户的邮件
is_staffbool用来判断用户是否可以登录进入Admin后台系统(注:此处在原问题中未明确给出类型)
is_activebool用来判断该用户的状态是否被激活
date_joineddatetime账号的创建时间
运行MyDjango项目, 在浏览器上访问: 127.0.0.1:8000/register.html ,
在用户注册表单中填写账号密码, 单击'确定'按钮即可完成用户注册.
打开数据表auth_user就能看到新建的用户信息, 如图10-3所示.

GIF 2024-5-7 16-23-11

image-20240507162504218

10-3 数据表auth_user
下一步实现用户登录过程, 同时也能验证视图函数register_view实现的用户注册功能是否正常.
我们在user的views.py中定义视图函数login_view, 函数代码如下:
# user 的 views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth import login, logout, authenticate


# 用户注册
def register_view(request):
	...


# 用户登录
def login_view(request):
    # 设置模板上下文
    title = '登录页面'
    page_title = '用户登录'
    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password', '')
        if User.objects.filter(username=u):  # 判断用户是否存在
            user = authenticate(username=u, password=p)  # 校验密码
            if user:
                if user.is_active:  # 如果用户是活跃的, 那么让他们登录
                    login(request, user)
                return HttpResponse('登录成功!')
            else:
                tips = '账号密码错误, 请重新输入!'
        else:
            tips = '用户不存在, 请注册!'

    return render(request, 'user.html', locals())

image-20240507165806468

视图函数login_view与register_view的实现过程有相似之处, 它也是对用户请求进行判断分析, 
如果当前请求为GET请求, 就调用模板文件user.html生成用户登录页面;
如果当前请求为POST请求, 就由内置的Auth认证系统执行用户登录过程, 具体说明如下:
(1) 当函数loginView收到POST请求并获取表单的数据后, 根据表单数据判断用户是否存在.
    如果用户存在, 就对用户账号和密码进行验证处理, 由内置函数authenticate完成验证过程,
    若验证成功, 则返回模型Uesr的用户对象user, 否则返回None.
(2) 从用户对象user的is_active字段来判断当前用户状态是否被激活, 如果字段is_active的值为1,
    就说明当前用户处于已激活状态, 可执行用户登录.
(3) 执行用户登录, 由内置函数login完成登录过程. 此外, 它还会更新request.user, 使其指向新登录的用户.
    函数login接收两个参数, 第一个是request对象, 来自视图函数的参数request; 
    第二个是user对象, 来自函数authenticate返回的对象user.
解开对应的路由注释.
# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    path('login.html', login_view, name='login'),  # 登录
    path('register.html', register_view, name='register'),  # 注册
    # path('set_password.html', set_password_view, name='set_password'),  # 修改密码
    # path('logout.html', logout_view, name='logout'),  # 注销
]

image-20240507171108264

运行MyDjango项目, 在浏览器上访问: 127.0.0.1:8000/login.html , 
在用户登录表单中填写新建的admin用户信息, 单击'确定'按钮即可完成用户登录.

2024-05-07_165613

我们可以在数据表auth_user中查看admin用户的登录时间(UTC时间, 北京时间比UTC时间快八个小时)来验证登录是否成功, 如图10-4所示.

image-20240507165912274

10-4 数据表auth_user
密码修改页面是根据已有的用户信息进行密码修改. 我们在user的views.py中定义视图函数set_password_view, 函数代码如下:
# user 的 views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth import login, logout, authenticate


# 用户注册
def register_view(request):
    ...

# 用户登录
def login_view(request):
    ...

# 修改密码
def set_password_view(request):
    # 设置模板上下文
    title = '修改密码'
    page_title = '修改密码'
    password2 = True
    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password', '')
        p2 = request.POST.get('password2', '')
        if User.objects.filter(username=u):  # 判断用户是否存在
            user = authenticate(username=u, password=p)
            # 判断用户的账号和密码是否正确
            if user:
                user.set_password(p2)
                user.save()
                tips = '密码修改成功!'
            else:
                tips = '原始密码不正确!'
        else:
            tips = '用户不存在!'
    
    return render(request, 'user.html', locals())

image-20240507174534000

解开对应的路由注释.
# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    path('login.html', login_view, name='login'),  # 登录
    path('register.html', register_view, name='register'),  # 注册
    path('set_password.html', set_password_view, name='set_password'),  # 修改密码
    # path('logout.html', logout_view, name='logout'),  # 注销
]

image-20240507174708803

密码修改页面相比注册和登录页面多出了一个文本输入框, 该文本输入框由模板上下文password2控制显示.
当password2为True时, 文本输入框将显示到页面上, 访问: http://127.0.0.1:8000/set_password.html , 如图10-5所示.

image-20240507174310745

10-5 修改密码页面
视图函数setpsView的处理逻辑与用户注册, 登录大致相同, 函数处理逻辑说明如下: 
(1) 当函数set_password_view收到POST请求后, 程序获取表单的数据内容, 然后在表单数据中查找模型User的用户信息.
(2) 如果用户存在, 就由内置函数authenticate验证用户的账号和密码是否正确.
    若验证成功, 则返回对象user, 再由对象user使用内置函数set_password修改当前用户的密码,
    最后保存修改后的对象user, 从而实现密码修改.
(3) 如果用户不存在, 就直接返回密码修改页面并提示用户不存在.
密码修改主要由内置函数set_password实现, 而函数set_password是在内置函数make_password的基础上进行封装而来的.
Django默认使用pbkdf2_sha256方式存储和管理用户密码, 而内置函数make_password用于实现用户密码的加密处理,
并且该函数可以脱离Auth认证系统单独使用, 比如对某些特殊数据进行加密处理等.
在user的views.py中定义视图函数set_password_view2, 它使用函数make_password实现密码修改, 代码如下:
# user 的 views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.hashers import make_password


# 用户注册
def register_view(request):
   ...
   

# 用户登录
def login_view(request):
   ...


# 修改密码
def set_password_view(request):
   ...


# 修改密码2
def set_password_view2(request):
    # 设置模板上下文
    title = '修改密码'
    page_title = '修改密码'
    password2 = True
    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password', '')
        p2 = request.POST.get('password2', '')
        if User.objects.filter(username=u):  # 判断用户是否存在
            user = authenticate(username=u, password=p)
            # 判断用户的账号和密码是否正确
            if user:
                #  密码加密处理并保存到数据库
                dj_ps = make_password(p2, None, 'pbkdf2_sha256')
                user.password = dj_ps
                user.save()
                tips = '密码修改成功!'
            else:
                tips = '原始密码不正确!'
        else:
            tips = '用户不存在!'

    return render(request, 'user.html', locals())

image-20240507175620112

修改路由使用的视图函数, 将set_password_view视图函数改为set_password_view2.
# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    path('login.html', login_view, name='login'),  # 登录
    path('register.html', register_view, name='register'),  # 注册
    path('set_password.html', set_password_view2, name='set_password'),  # 修改密码
    # path('logout.html', logout_view, name='logout'),  # 注销
]

image-20240507175821524

访问: http://127.0.0.1:8000/set_password.html , 修改密码.

image-20240507204551436

视图函数set_password_view2与set_password_view的处理逻辑相同, 只不过两者修改密码的方法有所不同.
除了内置函数make_password之外, 还有内置函数check_password, 
该函数是对加密前的密码与加密后的密码进行验证匹配, 判断两者是否为同一个密码.
在PyCharm的Terminal中开启Django的Shell模式, 函数make_password和check_password的使用方法如下:
D:\MyDjango> python manage.py shell
>>> from django.contrib.auth.hashers import make_password
>>> from django.contrib.auth.hashers import check_password
>>> ps = "123456"  # 加密前的密码
>>> dj_ps = make_password(ps, None, 'pbkdf2_sha256')  # 加密前的密码
>>> ps_bool = check_password(ps, dj_ps)  # 判断两者是否为同一个密码
>>> ps_bool
True

image-20240507205301341

用户注销是Auth认证系统较为简单的功能, 只需调用内置函数logout即可实现.
函数logout的参数request代表当前用户的请求对象, 来自于视图函数的参数request.
因此, 视图函数logoutView的代码如下:
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.hashers import make_password  # 使用make_password实现密码修改


# 用户注册
def register_view(request):
    ....


# 用户登录
def login_view(request):
    ....
    
    
# 修改密码
def set_password_view(request):
    ....
    

# 修改密码2
def set_password_view2(request):
    ....


# 用户注销, 退出登录
def logout_view(request):
    logout(request)
    return HttpResponse('注销成功!')

image-20240507205522933

解开对应的路由注释.
from django.urls import path
from .views import *

urlpatterns = [
    path('login.html', login_view, name='login'),  # 登录
    path('register.html', register_view, name='register'),  # 注册
    path('set_password.html', set_password_view2, name='set_password'),  # 修改密码
    path('logout.html', logout_view, name='logout'),  # 注销
]

image-20240507205717730

访问: http://127.0.0.1:8000/logout.html , 完成注销用户.

image-20240507205823235

综上所述, 整个MyDjango项目定义了4条路由信息, 分别实现用户注册, 登录, 修改密码和注销功能.
由Auth认证系统的内置模型User实现用户信息管理, 开发者只需调用内置函数即可实现功能开发.

10.2 发送邮件实现密码找回

10.1节中, 密码修改是在用户知道密码的情况下实现的,
而在日常应用中, 还有一种是在用户忘记密码的情况下实现密码修改, 也称为密码找回.
密码找回首先需要对用户账号进行验证, 确认该账号是当前用户所拥有的, 验证成功才能为用户重置密码.
用户验证方式主要有手机验证码和邮件验证码, 因此本节使用Django内置的邮件功能发送验证码, 从而实现密码找回功能.

在实现邮件发送功能之前, 我们需要对邮箱进行相关配置, 以QQ邮箱为例, 在QQ邮箱的设置中找到账户设置, 
在账户设置中找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务, 然后开启POP3/SMTP服务, 如图10-6所示.
* 1. 点击设置.

image-20240507211557031

* 2. 选择账号.

2024-05-07_211624

* 3. 下拉页面, 在POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务选择处, 点击开启服务.

2024-05-07_212057

* 4. 短信验证.

image-20240507212159332

* 5. 保存授权码.

2024-05-07_212258

10-6 开启POP3/SMTP服务 (ltaeeijtpwuzj)
开启服务成功后, QQ邮箱会返回一个客户端授权密码, 
该密码是用于登录第三方邮件客户端的专用密码, 切记保存授权密码, 该密码在开发过程中需要使用.
如果QQ开启了登录保护, 就必须取消登录保护, 否则Django无法使用POP3/SMTP服务发送邮件验证码.

下一步在Django里使用POP3/SMTP服务发送邮件验证码, 10.1节的MyDjango为例, 
在配置文件settings.py中设置邮件配置信息, 配置信息如下:
# MyDajango 的 settings.py

# 邮件配置信息
EMAIL_USE_SSL = True
# 邮件服务器, 如果是163, 就改成smtp.163.com
EMAIL_HOST = 'smtp.qq.com'
# 邮件服务器端口
EMAIL_PORT = 465
# 发送邮件的账号
EMAIL_HOST_USER = '[email protected]'
# SMTP服务密码
EMAIL_HOST_PASSWORD = 'ltaeeijtpwuzj'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

image-20240508002725973

邮件配置一共设置6个配置属性, 这些属性是配置邮件发送方的服务器信息, 配置属性的值是由邮件服务器决定的.
每个配置属性的作用说明如下:
 EMAIL_USE_SSL: Django与邮件服务器的连接方式是否设为SSL模式.
 EMAIL_HOST: 设置服务器类型, QQ邮箱分为SMTP服务器和POP3服务器.
 EMAIL_PORT: 设置服务器端口号, 若使用SMTP服务器, 则端口应为465587.
 EMAIL_HOST_USER: 发送邮件的账号, 该账号必须开启POP3/SMTP服务.
 EMAIL_HOST_PASSWORD: 客户端授权密码, 即图10-6开启服务后所获得的授权码.
 DEFAULT_FROM_EMAIL: 设置默认发送邮件的账号.
完成邮件相关配置后, 在MyDjango的urls.py和user的urls.py中定义路由信息,
分别设置Admin后台系统的路由和定义密码找回的路由, 代码如下:
# MyDjango 的 urls.py
from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(('user.urls', 'user'), namespace='user')),
]

image-20240508002833636

# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    path('', find_password_view, name='find_password_view'),
]

image-20240508002926595

设置Admin后台系统的路由可以验证用户密码修改后是否能正常登录Admin后台系统.
接着修改模板文件user.html的用户表单, 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>找回密码</title>
    <link rel="stylesheet" href="{% static "css/reset.css" %}">
    <link rel="stylesheet" href="{% static "css/user.css" %}">
    <script src="{% static "js/jquery.min.js" %}"></script>
    <script src="{% static "js/user.js" %}"></script>
</head>
<body>
<div class="page">
    <div class="loginwarrp">
        <div class="logo">找回密码</div>
        <div class="login_form">
            <form id="Login" name="Login" method="post" action="">
                {% csrf_token %}
                <li class="login-item">
                    <label for="id_username">用户名称:</label>
                    <input type="text" id="id_username" name="username" class="login_input">
                    {# <p id="count-msg" class="error">{{ tips }}</p> #}
                </li>
                {% if password %}
                    <li class="login-item">
                        <label for="id_password">用户密码:</label>
                        <input type="password" id="id_password" name="password" class="login_input">
                        {# <p id="password-msg" class="error">{{ tips }}</p> #}
                    </li>
                {% endif %}
                {% if VCodeInfo %}
                    <li class="login-item">
                        <label for="id_VCode">验证码:</label>
                        <input type="password" id="id_VCode" name="VCode" class="login_input">
                        {# <p id="password-msg" class="error">{{ tips }}</p> #}
                    </li>
                {% endif %}
                <div>{{ tips }}</div>
                <li class="login-sub">
                    <input type="submit" name="Submit" value="{{ button }}">
                </li>
            </form>
        </div>
    </div>
</div>
{# 画布粒子 #}
<script type="text/javascript">
    window.onload = function () {
        var config = {
            vx: 4,
            vy: 4,
            height: 2,
            width: 2,
            count: 100,
            color: "121, 162, 185",
            stroke: '100, 200, 180',
            dist: 6000,
            e_dist: 20000,
            max_conn: 100
        };
        CanvasParticle(config);
    }
</script>
{# 画布粒子文件 #}
<script src="{% static 'js/canvas-particle.js' %}"></script>
</body>
</html>

2024-05-08_003134

模板文件user.html设置了4个模板上下文, 分别为password, VCodeInfo, tips和button, 每个上下文的说明如下:
 password控制密码文本框是否显示在网页上, 数据类型为布尔型.
 VCodeInfo控制验证码文本框是否显示在网页上, 数据类型为布尔型.
 tips是根据用户操作和账号验证设置的信息提示, 信息内容分别为: 
  '验证码已发送', '用户XXX不存在', '密码已重置''验证码错误', '请重新获取'.
 button可以改变表单提交按钮的文本内容, 内容分别为: '获取验证码''重置密码'.
最后在user的views.py中定义视图函数find_password_view, 该函数实现3个功能:
发送邮件验证码, 验证邮件验证码和修改密码, 具体代码如下:
import random
from django.shortcuts import render
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password  # 使用make_password实现密码修改


# 找回密码
def find_password_view(request):
    # GET请求, 展示一个用户名称输入框和获取验证码按钮
    button = '获取验证码'
    VCodeInfo = False
    password = False

    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password', '')
        VCode = request.POST.get('Vcode', '')
        # 获取用户
        user = User.objects.filter(username=u)
        # 用户不存在, 则提示
        if not user:
            tips = '用户' + u + '不存在!'

        # 用户存在
        else:
            # 判断验证码是否已经发送, 没有发送则生成验证码保存到sessions并通过邮箱发送到用户邮箱
            if not request.session.get('VCode', ''):
                # 发送验证码并将验证码写入session
                button = '重置密码'
                tips = '验证码已发送!'
                password = True
                VCodeInfo = True
                VCode = str(random.randint(1000, 9999))  # 随机四位数字
                request.session['VCode'] = VCode
                user.first().email_user('找回密码', VCode)

            # 验证码存在, 则匹配输入的验证码是否正确
            elif VCode == request.session.get('VCode'):
                # 密码加密处理并保存到数据库
                dj_ps = make_password(p, None, 'pbkdf2_sha256')
                user.first().password = dj_ps
                user.first().save()
                del request.session['VCode']
                tips = '密码已重置'

            # 输入验证码错误
            else:
                tips = '验证码错误, 请重新获取!'
                VCodeInfo = False
                password = False
                del request.session['VCode']

    return render(request, 'user.html', locals())

由于视图函数find_password_view实现3个不同的功能, 因此从功能使用的角度分析视图函数find_password_view.
运行MyDjango并在浏览器上访问: 127.0.0.1:8000 , 视图函数find_password_view触发GET请求,
调用模板文件user.html生成网页表单, 在网页表单上输入用户账号admin并单击'获取验证码'按钮, 如图10-7所示.

image-20240507234102379

10-7 获取验证码
当输入用户名并单击'获取验证码'按钮时, 浏览器向Django发送POST请求,
视图函数find_password_view首先根据用户输入的用户名在模型User里进行数据查找,
然后判断用户名是否存在, 若不存在, 则会生成提示信息, 如图10-8所示.

image-20240507234256347

10-8 用户不存在
如果用户存在, 就继续判断会话Session的VCode是否存在.
若不存在, 则视图函数findpsView通过发送邮件的方式将验证码发送到用户邮箱, 实现过程如下:
 设置模板上下文button, tips, password和VCodeInfo的值, 在网页上生成提示信息, 密码输入框和验证码输入框.
 验证码是使用random模块随机生成长度为4位的整数, 然后将验证码写入会话Session的VCode, 其作用是与用户输入的验证码进行匹配.
 邮件发送是由内置函数email_user实现的, 该方法是模型User特有的方法之一, 只适用于模型User.
 用户邮箱信息来自于模型User的字段email, 如果当前用户的邮箱信息为空, Django就无法发送邮件.
邮件发送如图10-9所示.

image-20240507234645670

image-20240507235158742

image-20240507235343071

10-9 邮件发送
用户接收到验证码之后, 可以在网页上输入用户名, 重置密码和验证码, 然后单击'重置密码'按钮,
这时将会触发POST请求, 函数findpsView获取用户输入的验证码并与会话Session的VCode进行对比, 
如果两者不符合, 就说明用户输入的验证码与邮件中的验证码无法匹配, 系统提示验证码错误, 如图10-10所示.

image-20240507235306027

10-10 验证码错误
若用户输入的验证码与会话Session的VCode匹配符合, 则程序将执行密码修改.
首先获取用户输入的密码, 然后使用函数make_password对密码进行加密处理并保存在模型User中,
最后删除会话Session的VCode, 否则会话Session的VCode一直存在, 在下次获取验证码时, 程序就不会执行邮件发送功能.
运行结果如图10-11所示.

GIF 2024-5-8 0-12-16

10-11 运行结果
上述例子是使用内置函数email_user发送邮件验证码, 除此之外, Django还提供了多种邮件发送方法.
我们在Django的Shell模式下进行讲解, 代码如下:
D:\MyDjango> python manage.py shell
# 使用send_mail实现邮件发送
>>> from django.core.mail import send_mail
>>> from django.conf import settings

# 获取settings.py的配置信息
>>> from_email = settings.DEFAULT_FROM_EMAIL
>>> sending = ['[email protected]']

# 接收邮件可以以列表表示, 可设置多个接收对象
>>> send_mail('标题0', '内容0', from_email, sending)
1

image-20240508110152271

image-20240508110116454

# 使用send_mass_mail实现多封邮件同时发送
>>> from django.core.mail import send_mass_mail
>>> message1 = ('标题1', '内容1', from_email, sending)
>>> message2 = ('标题2', '内容2', from_email, sending)
# fail_silently参数用于控制当邮件发送失败时是否引发异常
>>> send_mass_mail((message1, message2), fail_silently=False)  
2

image-20240508112321594

image-20240508112401215

# 使用EmailMultiAlternatives实现邮件发送
>>> from django.core.mail import EmailMultiAlternatives
>>> content = '<p>这是一封<h3>重要的</h3>邮件. </p>'
>>> msg = EmailMultiAlternatives('标题3', content, from_email, sending)
# 将正文设置为HTML格式
>>> msg.content_subtype = 'html'
# attach_alternative对正文内容进行补充和添加(在尾部添加信息)
>>> msg.attach_alternative('<h3>补充内容!</h3>','text/html')
# 添加附件(可选), 需要在D盘创建这个文件!!!
>>> msg.attach_file('D://a.txt')
# 发送
>>> msg.send()
1

image-20240508112842559

image-20240508112905118

上述代码分别讲述了send_mail, send_mass_mail和EmailMultiAlternatives的使用方法,
三者之间的对比说明如下:
 使用send_mail每次发送邮件都会建立一个新的连接, 如果发送多封邮件, 就需要建立多个连接.
 send_mass_mail是建立单个连接发送多封邮件, 所以一次性发送多封邮件时, send_mass_mail要优于send_mail.
 EmailMultiAlternatives比前两者更为个性化, 可以将邮件正文内容设为HTML格式的, 也可以在邮件上添加附件, 满足多方面的开发需求.

10.3 模型User的扩展与使用

在开发过程中, 模型User的字段可能满足不了复杂的开发需求.
现在大多数网站的用户信息都有用户的手机号码, QQ号码和微信账号等一系列个人信息.
为了满足各种需求, Django提供了以下4种模型扩展的方法.

代理模型: 这是一种模型继承, 这种模型在数据库中无须创建新数据表.
一般用于改变现有模型的行为方式, 如增加新方法函数等, 并且不影响数据表的结构.
如果不需要在数据表存储额外的信息, 只是增加模型User的操作方法或更改模型的查询方式, 那么可以使用代理模型扩展模型User.

Profile扩展模型User: 当存储的信息与模型User相关, 而且不改变模型User的内置方法时, 
可定义新的模型MyUser, 并设置某个字段为OneToOneField, 这样能与模型User形成一对一关系, 该方法称为用户配置(User Profile).

AbstractBaseUser扩展模型User: 当模型User的内置方法不符合开发需求时, 
可使用该方法对模型User重新自定义设计, 该方法对模型User和数据表结构造成较大影响.

AbstractUser扩展模型User: 如果模型User的内置方法符合开发需求, 
在不改变这些函数方法的情况下, 添加模型User的额外字段, 可通过AbstractUser方式替换原有的模型User.

上述4种方法各有优缺点, 一般情况下, 建议使用AbstractUser扩展模型User, 因为该方式对原有模型User影响较小而且无须额外创建数据表.
在讲述如何扩展模型User之前, 首先深入了解模型User的定义过程.
在PyCharm里打开模型User的源码文件, 如图10-12所示.

image-20240508113533134

10-12 模型User的源码文件
模型User继承父类AbstractUser, 而AbstractUser继承父类AbstractBaseUser和PermissionsMixin,
因此模型User的继承关系如图10-13所示.

image-20240508114016054

10-13 模型User的继承关系
从图10-13得知, 模型User的字段和方法是由父类AbstractUser, AbstractBaseUser和PermissionsMixin定义的,
模型字段已在10.1节的表10-1列举说明, 本节只列举说明模型User的内置方法, 分别说明如下:
 get_full_name(): 由AbstractUser定义, 获取用户的全名, 即字段first_name与last_name的组合值.
 get_short_name(): 由AbstractUser定义, 获取模型字段first_name的值.
 email_user(): 由AbstractUser定义, 发送邮件.
  参数subject设置邮件标题; 参数message设置邮件内容; 
  参数from_email设置发送邮件的账号, 即配置文件settings.py的DEFAULT_FROM_EMAIL.
 save(): 由AbstractBaseUser定义, 定义模型的数据保存方式.
 get_username(): 由AbstractBaseUser定义, 获取当前用户的账号信息, 即模型字段username.
 set_password(): 由AbstractBaseUser定义, 更改当前用户的密码, 即更改模型字段password.
 check_password(): 由AbstractBaseUser定义, 验证加密前的密码与加密后的密码是否相同.
 get_session_auth_hash(): 由AbstractBaseUser定义, 获取模型字段password的HMAC, 在更改密码时可使当前用户的登录状态失效.
 set_unusable_password(): 由AbstractBaseUser定义, 标记用户尚未设置密码.
 has_usable_password(): 由AbstractBaseUser定义, 检测用户是否尚未设置密码.
 get_group_permissions(): 由PermissionsMixin定义, 获取当前用户所在用户组的权限.
 get_all_permissions(): 由PermissionsMixin定义, 获取当前用户所拥有的权限.
 has_perm(): 由PermissionsMixin定义, 判断当前用户是否具有某个权限, 参数perm代表权限的名称, 以字符串表示.
 has_perms(): 由PermissionsMixin定义, 判断当前用户是否具有多个权限, 参数perm_list代表多个权限的集合, 以列表表示,
 has_module_perms(): 由PermissionsMixin定义, 判断当前用户是否具有某个项目应用的所有权限, 参数app_label代表项目应用的名称.
下一步讲述如何使用AbstractUser扩展模型User.
以MyDjango为例, 打开项目的数据库db.sqlite3文件, 清除数据库所有的数据表, 在user的models.py中定义模型MyUser, 代码如下:
# user 的 models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class MyUser(AbstractUser):
    # 设置额外字段, 第一个参数为verbose_name详细名称
    qq = models.CharField('QQ号码', max_length=16)
    weChat = models.CharField('微信账号', max_length=100)
    mobile = models.CharField('手机号码', max_length=11)

    # 打印对象时展示用户名
    def __str__(self):
        return self.username

image-20240508124858952

模型MyUser继承AbstractUser类, AbstractUser类是模型User的父类, 因此模型MyUser也具有模型User的全部字段.
在执行数据迁移之前, 必须在项目的settings.py中配置相关信息, 配置信息如下:
# settings.py
AUTH_USER_MODEL = 'user.MyUser'

image-20240508133952258

(user是应用的名称(也就是的应用目录的名字), MyUser是自定义的模型名称.
Django会根据这个设置来查找和加载你的MyUser模型, 而不需要指定models子目录. 详细说明在另一篇文章中, 配置资源中查看.)
配置信息是将内置模型User替换成自定义的模型MyUser, 
若没有设置配置信息, 则在创建数据表的时候, Django分别创建数据表auth_user和user_myuser, 这样模型MyUser无法取代内置模型User.
在PyCharm的Terminal下执行数据迁移, 代码如下:
D:\MyDjango> python manage.py makemigrations
Migrations for 'user':
  user\migrations\0001_initial.py
    - Create model MyUser
D:\MyDjango> python manage.py migrate

image-20240508125205799

完成数据迁移后, 打开数据库查看数据表信息, 可以发现内置模型User的数据表auth_user改为数据表user_myuser,
并且数据表user_myuser的字段除了具有内置模型User的字段之外, 还额外增加了自定义的字段, 如图10-14所示.

image-20240508125354306

10-14 数据表user_myuser
使用AbstractUser扩展模型User的实质是重新自定义模型User, 将内置模型User替换成自定义的模型MyUser, 替换过程可分为两个步骤.
 定义新的模型MyUser, 该模型必须继承AbstractUser类, 在模型MyUser里定义的字段为扩展字段.
 在项目的配置文件settings.py中配置AUTH_USER_MODEL信息, 在数据迁移时, 将内置模型User替换成自定义的模型MyUser.
完成模型MyUser的自定义及数据迁移后, 接着探讨模型MyUser与内置模型User在开发过程中是否存在使用上的差异.
首先使用Django内置命令: python manage.py createsuperuser 创建超级管理员并登录Admin后台系统, 如图10-15所示.
PS D:\MyDjango> python manage.py createsuperuser
Username: admin
Email address: (回车)
Password: 123
Password (again): 123
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

image-20240508133252747

image-20240508133333326

image-20240508133406370

Admin后台系统默认以英文的形式显示, 想要以中文显示, 可以在项目的settings.py中设置中间件MIDDLEWARE.
# MyDjango 的 settings.py
# 添加中间件LocaleMiddleware
'django.middleware.locale.LocaleMiddleware',  # 设置本地化

image-20240508133805939

image-20240508133837640

10-15 Admin后台系统
从图10-15中发现, 认证与授权没有显示用户信息表, 因为模型MyUser是在user的models.py中定义的.
若将模型MyUser展示在后台系统, 则可以在user的admin.py和初始化文件__init__.py中定义相关的数据对象, 代码如下:
# user 的 admin.py
from django.contrib import admin
from .models import MyUser
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _


@admin.register(MyUser)
class MyUserAdmin(UserAdmin):
    # 显示的字段
    list_display = ['username', 'email', 'mobile', 'qq', 'weChat']
    # 修改用户时, 添加mobile, qq, weChat的录入
    # 将源码的 UserAdmin.fieldsets转换成列表格式
    fieldsets = list(UserAdmin.fieldsets)
    # 重写UserAdmin的fieldsets, 添加mobile, qq, weChat的录入
    fieldsets[1] = (_('Personal info'),  # 渲染Personal info时, 使用gettext_lazy函数对其进行惰性翻译
                    {'fields': ('first_name', 'last_name',
                                'email', 'mobile', 'qq', 'weChat')})

# fieldsets 的值是元组, 元组是不是可不变的, 使用list转换为列表.
[
(None, {'fields': ('username', 'password')}),  # 字段集没有标题, 通常用于用户登录凭证
('Personal info', {'fields': ('first_name', 'last_name', 'email')}),  # 个人信息字段集
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),  # 权限字段集
('Important dates', {'fields': ('last_login', 'date_joined')})  # 日期字段集
]

gettext_lazy  Django 框架中用于国际化(i18n)和本地化(l10n)的一个函数.
设置gettext_lazy后, 在渲染页面或管理后台等需要显示这些字符串的地方时, Django会使用当前配置的语言来翻译这些字符串.

这个函数返回的是一个惰性翻译对象, 而不是立即执行翻译.
这在处理如数据库字段的verbose_name, 表单字段的label等需要在运行时之前进行翻译的情况时特别有用.

使用gettext_lazy的主要优势是, 它可以避免在模型或表单定义时立即进行翻译, 而是在需要时(如渲染模板时)进行翻译.
这样做的好处是, 在数据库迁移, 表单验证等过程中, 你可以使用翻译后的字符串, 但实际的翻译工作会延迟到需要时执行.

下面是一个使用gettext_lazy的例子:
from django.utils.translation import gettext_lazy as _  
  
class MyModel(models.Model):  
    name = models.CharField(verbose_name=_('Name'), max_length=100)  
    # 其他字段...  
  
class MyForm(forms.Form):  
    name = forms.CharField(label=_('Name'))  
    # 其他字段...
    
在这个例子中, verbose_name和label属性被设置为gettext_lazy函数的返回值.
这意味着这些字符串在模型或表单定义时不会被翻译, 而是会在需要时(如渲染模板时)进行翻译.

这样做的好处之一是, 即使你在设置文件中更改了默认语言(通过LANGUAGE_CODE设置), 
也不需要重新运行数据库迁移来更新字段的verbose_name.
因为verbose_name是在需要时动态翻译的, 所以它会反映当前的语言设置.
# user 的 __init__.py
# 设置App(user)的中文名称
from django.apps import AppConfig
import os

# 修改app在admin后台显示的名称
# default_app_config 的值来自apps.py的类名
default_app_config = 'user.UserIndex'


# 获取当前app的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]


# 重写类UserIndex
class UserIndex(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '用户管理'

# 注意配置文件的注册应用时, 不要设置配置文件, 否则__init__.py中的配置会不起作用.
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'user.apps.UserConfig',
    'user',
]
重启MyDjango项目并再次进入Admin后台系统, 可以在页面上看到模型MyUser所生成的'用户', 如图10-16所示.

image-20240508152528974

10-16 模型MyUser的用户链接
从用户数据列表页进入用户数据修改页或用户数据新增页的时候, 
发现用户数据修改页或用户数据新增页出现了用户的手机号码, QQ号码和微信账号的文本输入框,
这是由MyUserAdmin重写属性fieldsets实现的, 如图10-17所示.

image-20240508152910022

10-17 修改用户数据
admin.py定义的MyUserAdmin继承自UserAdmin, UserAdmin是内置模型User的Admin数据对象,
换句话说, MyUserAdmin通过继承UserAdmin并重写父类的某些属性和方法能使自定义模型MyUser展示在Admin后台系统.

UserAdmin的定义过程可以在Django源码文件: django\contrib\auth\admin.py中查看.

image-20240508163133607

除了UserAdmin之外, 还可以继承内置模型User定义的表单类.
内置表单类可以在源码文件django\contrib\auth\forms.py中查看.

image-20240508163416625

 从源码中发现, forms.py定义了多个内置表单类, 其说明如下:
 UserCreationForm: 表单字段为username, password1和password2, 创建新的用户信息.
 UserChangeForm: 表单字段为password和模型User所有字段, 修改已有的用户信息.
 AuthenticationForm: 表单字段为username和password, 用户登录时所触发的认证功能.
 PasswordResetForm: 表单字段为email, 将重置密码通过发送邮件的方式实现密码找回.
 SetPasswordForm: 表单字段为password1和password2, 修改或新增用户密码.
 PasswordChangeForm: 表单字段为old_password, new_password1和new_password2,
  继承SetPasswordForm, 如果是修改密码, 就需要对旧密码进行验证.
 AdminPasswordChangeForm: 表单字段为password1和password2, 用于Admin后台修改用户密码.
从上述的内置表单类发现, 这些表单都是在内置模型User的基础上实现的, 但这些表单类不一定适用于自定义模型MyUser.
但我们可以自定义模型MyUser的表单类, 并继承上述的内置表单类, 从而使定义的表单类适用于模型MyUser.
以内置表单类UserCreationForm为例, 使用表单类UserCreationForm实现用户注册功能.
在user中创建form.py文件, 并在文件下编写以下代码:
# user 的 form.py
# 用户创建表单
from django.contrib.auth.forms import UserCreationForm
# 导入用户模型
from .models import MyUser


class MyUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = MyUser
        # 在注册页面添加邮箱, 手机号码, qq, 微信
        fields = UserCreationForm.Meta.fields
        fields += ('email', 'mobile', 'qq', 'weChat')

image-20240508170952092

自定义表单类MyUserCreationForm继承表单类UserCreationForm, 并且重写Meta的属性model和fields, 分别设置表单类绑定的模型和字段.
最后在MyDjango的urls.py, user的urls.py, views.py和模板文件user.html中实现用户注册功能, 代码如下:
# MyDjango 的 urls.py
from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(('user.urls', 'user'), namespace='user')),
]

image-20240508171019474

# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    path('', register_view, name='register'),
]

image-20240508171054734

# user 的 views.py
from django.shortcuts import render
from .form import MyUserCreationForm


# 使用表单实现用户注册
def register_view(request):
    if request.method == 'POST':
        user = MyUserCreationForm(request.POST)
        if user.is_valid():
            user.save()
            tips = '注册成功!'
        else:
            tips = '注册失败!'
    
    user = MyUserCreationForm()
    return render(request, 'user.html', locals())

image-20240508171128168

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
    <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
</head>
<body>
<div class="flex-center">
    <div class="container">
        <div class="flex-center">
            <div class="unit-1-2 unit-1-on-mobile">
                <h1>用户注册</h1>
                {% if tips %}
                    <div>{{ tips }}</div>
                {% endif %}

                <form class="form" action="" method="post">
                    {% csrf_token %}
                    <div>用户名称: {{ user.username }}</div>
                    <div>邮箱地址: {{ user.email }}</div>
                    <div>手机号码: {{ user.mobile }}</div>
                    <div>QQ 账号: {{ user.qq }}</div>
                    <div>微信账号: {{ user.weChat }} </div>
                    <div>用户密码: {{ user.password1 }}</div>
                    <div>密码确认: {{ user.password2 }}</div>
                    <button type="submit" class="btn btn-primary btn-block">注册</button>
                </form>

            </div>
        </div>
    </div>
</div>

</body>
</html>

image-20240508183959387

视图函数register_view使用自定义表单类MyUserCreationForm实现用户注册功能, 实现过程如下:
(1) 在浏览器上访问: 127.0.0.1:8000 , 视图函数将表单类MyUserCreationForm实例化并传递给模板文件user.html,
    在浏览器上生成用户注册页面.
(2) 输入用户信息并单击'注册'按钮, 视图函数register_view收到POST请求,
    将表单数据交给表单类MyUserCreationForm处理并生成user对象.
(3) 验证user对象的数据信息, 如果验证成功, 就将数据保存到数据表user_myuser中, 并在网页上提示'注册成功';
    若验证失败, 则在网页上提示'注册失败'并清空表单数据.
(4) 注册用户的时候, 表单类MyUserCreationForm对密码的安全强度有严格的要求, 
    9.4节的图9-20所示, 建议读者设置安全强度较高的密码, 密码过于简单会无法完成注册.

image-20240508170123229

mobi.css是一个轻量级的CSS框架, 专为移动设备设计.	使用它可以快速地为移动网页添加样式和布局.
浏览器关闭了缓存, 每次运行需要从unpkg.com(一个开源的JavaScript包分发平台)获取mobi.css文件的压缩版, 会影响网页的渲染速度.
将脚本下载到本地, 避免上述问题,  文件下载地址: https://github.com/mobi-css/mobi.css/releases .
mobi.min.css文件在mobi.css-3.1.1/packages/mobi.css/dist/下, 将文件复制到static的css目录下.
修改加载静态文件为: <link rel="stylesheet" href="{% static "css/mobi.min.css" %}"> .

image-20240508184744695

10.4 权限的设置与使用

用户权限是对不同用户设置不同的功能使用权限, 而每个功能主要以模型来划分.
10.3节的MyDjango项目为例, 在Admin后台系统的用户数据修改页或用户数据新增页可以查看并设置用户权限, 如图10-18所示.

image-20240508185606447

10-18 用户权限
在图10-18的左侧列表框中列出了整个项目的用户权限, 每个权限以'项目应用|模型|模型使用权限'的格式表示,
以user|用户|Can add user为例, 说明如下:
 user是项目应用user的名称.
 用户是项目应用user定义的模型MyUser.
 Can add user是模型MyUser新增数据的权限.
当执行数据迁移时, 每个模型默认拥有增(Add), (Change), (Delete)和查(View)权限.
数据迁移成功后, 可以在数据库中查看数据表auth_permission的数据信息, 每行数据代表项目中某个模型的某个权限, 如图10-19所示.

image-20240508185741603

10-19 数据表auth_permission
设置用户权限实质上是对数据表user_myuser和auth_permission之间的数据设置多对多关系.
首先需要了解用户, 用户权限和用户组三者之间的关系, 以MyDjango的数据表为例, 如图10-20所示.

image-20240508190306588

10-20 MyDjango的数据表信息
从整个项目的数据表看到, 用户, 用户权限和用户组分别对应数据表user_myuser, auth_permission和auth_group.
无论是设置用户权限, 设置用户所属用户组或者设置用户组的权限, 它们的本质都是对两个数据表之间的数据建立多对多的数据关系, 说明如下:
 数据表user_myuser_user_permissions: 管理数据表user_myuser和auth_permission之间的多对多关系, 设置用户所拥有的权限.
 数据表user_myuser_groups: 管理数据表user_myuser和auth_group之间的多对多关系, 设置用户所在的用户组.
 数据表auth_group_permissions: 管理数据表auth_group和auth_permission之间的多对多关系, 设置用户组所拥有的权限.
在设置用户权限时, 如果用户的角色是超级管理员, 该用户就无须设置权限, 
因为超级管理员已默认具备整个系统的所有权限, 而设置用户权限只适用于非超级管理员的用户.
我们在Admin后台系统创建普通用户root, 并设置用户信息, 最后保存设置.

image-20240508191423854

image-20240508191953803

然后, 在PyCharm的Terminal下开启Django的Shell模式实现用户权限设置, 代码如下:
D:\MyDjango> python manage.py shell
# 导入模型MyUser
>>> from user.models import MyUser
# 查询用户信息
>>> user = MyUser.objects.filter(username='root').first()
# 判断当前用户是否具有用户新增的权限
# user.add_myuser为固定写法
# user为项目应用的名称
# add_myuser来自数据表auth_permission的字段codename
>>> user.has_perm('user.add_myuser')
False
在Django中, 对于每个模型, Django都会自动生成三个基本的权限:
add_<modelname>: 允许用户添加模型的实例.
change_<modelname>: 允许用户更改模型的实例.
delete_<modelname>: 允许用户删除模型的实例.

image-20240508192251917

# 导入模型Permission
>>> from django.contrib.auth.models import Permission
# 在权限管理表获取权限add_myuser的数据对象permission
>>> p = Permission.objects.filter(codename='add_myuser').first()
# 对当前用户对象user设置权限add_myuser
# user_permissions是多对多的模型字段
# 该字段由内置模型User的父类PermissionsMixin定义
>>> user.user_permissions.add(p)
# 再次判断当前用户是否具有用户新增的权限
>>> user = MyUser.objects.filter(username='root').first()
>>> user.has_perm('user.add_myuser')
True
其任何继承自AbstractUser或AbstractBaseUser并混入PermissionsMixin的自定义模型都有一个多对多字段user_permissions,
这个字段用于表示该用户拥有的权限.
这个字段在数据库层面上是通过一个中间表来实现的, 这个中间表(user_myuser_user_permissions)连接了User表和Permission表.

当在Django的ORM中访问user.user_permissions时, 其实是在访问一个反向关联的管理器,
这个管理器允许你查询和操作与该用户相关联的权限.
这个管理器提供了多种方法, 比如: add(), remove(), clear(), 用于向集合中添加, 移除或清空权限.

image-20240508192413262

上述代码首先查询用户名为root的用户是否具备用户新增的权限, 然后对该用户设置用户新增的权限,
设置方式是由多对多的模型字段user_permissions调用add方法实现的.
打开数据表user_myuser_user_permissions可以看到新增了一行数据, 如图10-21所示.

image-20240508192611353

10-21 数据表user_myuser_user_permissions
从图10-21看到, 表字段myuser_id和permission_id分别是数据表user_myuser和auth_permission的主键,
每一行数据代表某个用户具有某个模型的某个操作权限.
除了添加权限之外, 还可以对用户的权限进行删除和查询, 代码如下:
# 导入模型MyUser
>>> from user.models import MyUser
# 导入模型Permission
>>> from django.contrib.auth.models import Permission
# 查询用户信息
>>> user = MyUser.objects.filter(username='root').first()
# 查询用户新增的权限
>>> p = Permission.objects.filter(codename='add_myuser').first()
# 删除某条权限
>>> user.user_permissions.remove(p)
# 判断是否已删除权限, 若为False, 则说明删除成功
# 函数has_perm用于判断用户是否拥有权限
>>> user.has_perm('user.add_myuser')
False

# 清空当前用户全部权限
>>> user.user_permissions.clear()
# 获取当前用户所拥有的权限信息
# 将上述删除的权限添加到数据表再查询
>>> user.user_permissions.add(p)
# 查询数据表user_myuser_user_permissions的数据
# 查询方式是使用ORM框架的API方法
# .values()方法: 从数据库中检索了用户的权限,
# 并且得到了一个包含字典的QuerySet, 其中每个字典代表一个权限记录(auth_permission表中的记录).
>>> user.user_permissions.values()  # 查看用户的权限
<QuerySet [{'id': 21, 'name': 'Can add user', 'content_type_id': 6, 'codename': 'add_myuser'}]>

image-20240508220559877

10.5 自定义用户权限

一般情况下, 每个模型默认拥有增(Add), (Change), (Delete)和查(View)权限.
但实际开发中可能要对某个模型设置特殊权限, 比如QQ音乐只允许会员播放高质音乐.

为了解决这种开发需求, 在定义模型时, 可以在模型的属性Meta中设置自定义权限.
10.3节的MyDjango项目为例, 对user的模型MyUser重新定义, 代码如下:
from django.db import models
from django.contrib.auth.models import AbstractUser


class MyUser(AbstractUser):
    # 设置额外字段, 第一个参数为verbose_name详细名称
    qq = models.CharField('QQ号码', max_length=16)
    weChat = models.CharField('微信账号', max_length=100)
    mobile = models.CharField('手机号码', max_length=11)

    # 打印对象时展示用户名
    def __str__(self):
        return self.username
    
    class Meta(AbstractUser.Meta):
        # 自定义权限
        permissions = (
            ('vip_myuser', 'Can vip user'),
        )
模型MyUser的Meta继承父类AbstractUser的Meta, 并且新增permissions属性, 这样就能创建用户权限, 
模型MyUser的Meta必须继承父类AbstractUser的Meta, 
因为AbstractUser的Meta定义属性verbose_name, verbose_name_plural和abstract.

新增属性permissions以元组或列表的数据格式表示, 元组或列表的每个元素代表一个权限, 每个权限以元组或列表表示.
一个权限中含有两个元素, 如上述的('vip_myuser', 'Can vip user'), 
vip_myuser和Can vip user分别是数据表auth_permission的codename和name字段.
下一步在数据库中清除MyDjango原有的数据表和迁移文件, 并在PyCharm的Terminal中重新执行数据迁移, 代码如下:
D:\MyDjango> python manage.py makemigrations
Migrations for 'user':
  user\migrations\0001_initial.py
    - Create model MyUser
D:\MyDjango> python manage.py makemigrations
Migrations for 'user':
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying user.0001_initial... OK
记得重新创建超级用户后续需要使用!
数据迁移执行完成后, 在数据库中打开数据表auth_permission, 可以找到自定义权限vip_myuser, 如图10-22所示.

image-20240508225726340

10.6 设置网页的访问权限

通过前面的学习, 相信大家对Django的内置权限功能有了一定的了解.
本节将结合示例讲述如何在网页中设置用户的访问权限, 10.5节的MyDjango项目为例, 
确保项目应用user已定义模型MyUser, 并且数据表auth_permission已记录权限vip_myuser的数据信息.

我们在MyDjango的项目应用user里实现用户注册, 登录和注销功能, 并且增加用户中心, 用于检验用户是否具有vip_myuser权限.
在编写代码之前, 需要对MyDjango的目录结构进行调整, 在templates文件夹放置模板文件info.html,
在static文件夹放置模板文件的静态资源, 并且user的form.py文件已定义MyUserCreationForm表单类(定义过程可回顾10.3),
用于实现用户注册和登录功能.

下一步实现用户注册, 登录, 注销和用户中心页, 分别在MyDjango的urls.py和user的urls.py中定义路由信息, 代码如下:
# MyDjango 的 urls.py
from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(('user.urls', 'user'), namespace='user')),
]

image-20240508230338185

# user 的 urls.py
from django.urls import path
from .views import *

urlpatterns = [
    # 用户注册
    path('', register_view, name='register'),
    # 用户登录
    path('login.html', login_view, name='login'),
    # 用户注销
    path('logout.html', logout_view, name='logout'),
    # 用户中心
    path('info.html', info_view, name='info')
]

image-20240509164119217

项目应用user的urls.py中定义了4条路由信息, 它们实现了一个简单的操作流程, 
流程顺序为: 用户注册  用户登录  用户中心  用户注销.
然后在user的views.py中定义相关的视图函数, 代码如下:
# user 的 views.py
from django.shortcuts import render, redirect
from django.shortcuts import reverse
from .models import MyUser
from .form import MyUserCreationForm
from django.contrib.auth.models import Permission
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import permission_required


# 使用表单实现用户注册
def register_view(request):
    user_login = False
    if request.method == 'POST':
        user = MyUserCreationForm(request.POST)
        if user.is_valid():
            user.save()
            tips = '注册成功!'
            # 添加权限
            # 由表单对象user的instance获取对应的模型对象
            u = user.instance  # 新用户
            p = Permission.objects.filter(codename='vip_myuser').first()  # 获取权限记录
            u.user_permissions.add(p)  # 为新用户添加权限
            return redirect(reverse('user:login'))
        else:
            tips = '注册失败!'

    # GET请求生成空表单
    user = MyUserCreationForm()
    return render(request, 'user.html', locals())


# 用户登录
def login_view(request):
    tips = '请登录!'
    user_login = True
    if request.method == 'POST':
        u = request.POST.get('username', '')
        p = request.POST.get('password1', '')
        if MyUser.objects.filter(username=u):
            user = authenticate(username=u, password=p)
            if user:
                if user.is_active:
                    # 登录当前用户
                    login(request, user)
                return redirect(reverse('user:info'))
            else:
                tips = '账号密码错误, 请重新输入!'
        else:
            tips = '用户不存在, 请注册!'

    # GET请求生成空表单
    user = MyUserCreationForm()
    return render(request, 'user.html', locals())


# 退出登录
def logout_view(request):
    logout(request)
    return redirect(reverse('user:login'))


# 用户中心
# login_request判断当前用户是否已经登录
# permission_request判断当前用户是否具有某个权限
@login_required(login_url='/login.html')
@permission_required(perm='user.vip_myuser', login_url='/login.html')
def info_view(request):
    return render(request, 'info.html', locals())

image-20240509164819146

上述代码分别定义视图函数register_view, login_view, logout_view和info_view, 
函数之间通过网页跳转方式构建关联, 从而实现一个简单的操作流程, 流程说明如下:
(1) 当用户访问 127.0.0.1:8000 , 浏览器将呈现用户注册页面, 
    输入新的用户信息并单击'确定'按钮, 视图函数register_view将收到POST请求,
    将表单数据交由表单类MyUserCreationForm实现用户注册.
(2) 如果用户注册失败, 就提示错误信息并返回用户注册页面,
    如果注册成功, 就由表单类MyUserCreationForm创建用户, 保存在模型MyUser的数据表中.
    默认情况下, 新用户不具备任何权限, 因此视图函数register_view为新用户赋予自定义权限vip_myuser并跳转到用户登录页面.
(3) 在用户登录页面输入新用户的账号和密码并单击'确定'按钮, 视图函数login_view将收到POST请求,
    从请求参数获取用户信息并进行验证.
    如果用户验证失败, 就提示错误信息并返回用户登录页面;
    如果用户验证成功,就执行用户登录并跳转到用户中心页面.
(4) 用户中心页面设有装饰器permission_required, 用于检测用户是否具有vip_myuser权限.
    如果用户权限检测失败, 就跳转到用户登录页面;
    如果检测成功, 就说明当前用户具有vip_myuser权限, 能正常访问用户中心页面.
视图函数info_view使用了装饰器login_required和permission_required, 
分别对当前用户的登录状态和用户权限进行校验, 两者的说明如下:
(1) login_required: 用于设置用户登录访问权限.
    如果当前用户在尚未登录的状态下访问用户中心页面, 程序就会自动跳转到指定的路由地址, 只有用户完成登录后才能正常访问用户中心页.
    login_required的参数有function, redirect_field_name和login_url, 参数说明如下:
     参数function: 默认值为None, 这是定义装饰器的执行函数.
     参数redirect_field_name: 默认值是next. 当登录成功之后, 程序会自动跳回之前浏览的网页.
     参数login_url: 设置用户登录的路由地址.
      默认值是settings.py的配置属性LOGIN_URL, 而配置属性LOGIN_URL需要开发者自行在settings.py中配置.
(2) permission_required: 用于验证当前用户是否拥有相应的权限.
    若用户不具备权限, 则程序将跳转到指定的路由地址或者抛出异常.
    permission_required的参数有perm, login_url和raise_exception参数说明如下:
     参数perm: 必选参数, 判断当前用户是否具备某个权限.
      参数值为固定格式, : user.vip_myuser, user为项目应用名称, vip_myuser是数据表auth_permission的字段codename.
     参数login_url: 设置验证失败所跳转的路由地址, 默认值为None. 若不设置参数, 则验证失败后会抛出404异常.
     参数raise_exception: 设置抛出异常, 默认值为False.
装饰器permission_required的作用与内置函数has_perm相同, 
视图函数infoView也可以使用函数has_perm实现装饰器permission_required的功能, 代码如下:
# 使用函数has_perm实现装饰器permission_required的功能
from django.shortcuts import render, redirect


@login_required(login_url='/login.html')  # 登入验证
def info_view(request):
    user = request.user
    if user.has_perm('user.vip_myuser'):  # 权限验证
        return render(request, 'info.html', locals())
    else:
        return redirect(reverse('user:login'))
最后在模板文件user.html和info.html中编写网页代码, 模板文件user.html主要生成用户注册和登录页面; 
模板文件info.html生成用户中心页面, 两者的代码如下:
<!-- templates 的 user.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>用户注册</title>
    <link rel="stylesheet" href="{% static "css/mobi.min.css" %}">
</head>
<body>
<div class="flex-center">
    <div class="container">
        <div class="flex-center">
            <div class="unit-1-2 unit-1-on-mobile">
                {% if user_login %}
                    <h1>用户登入</h1>
                {% else %}
                    <h1>用户注册</h1>
                {% endif %}
                {% if tips %}
                    <div>{{ tips }}</div>
                {% endif %}
                <form class="form" action="" method="post">
                    {% csrf_token %}
                    <div>用户名称: {{ user.username }}</div>
                    <div>用户密码: {{ user.password1 }}</div>
                    {% if not user_login %}
                        <div>密码确认: {{ user.password2 }}</div>
                        <div>邮箱地址: {{ user.email }}</div>
                        <div>手机号码: {{ user.mobile }}</div>
                        <div>QQ 账号: {{ user.qq }}</div>
                        <div>微信账号: {{ user.weChat }} </div>
                    {% endif %}
                    <button type="submit" class="btn btn-primary btn-block">确认</button>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

image-20240509165347594

<!-- templates 的 info.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>用户信息</title>
    <link rel="stylesheet" href="{% static "css/common.css" %}">
    <link rel="stylesheet" href="{% static "css/home.css" %}">
</head>
<body class="member">
<div class="mod_profile js_user_data">
    <div class="section_inner">
        {# 模板上下文user是User或AnoymousUser对象 #}
        {# user由模型MyUser实例化 #}
        {% if user.is_authenticated %}
            <div class="profile__cover_link">
                <img src="{% static "image/user.jpg" %}" alt="" class="profile__cover">
            </div>
            <h1 class="profile__tit">
                <span class="profile__name">{{ user.username }}</span>
            </h1>
            {# 模板上下文perms是模型Permission实例化对象 #}
            {% if perms.user.vip_myuser %}
                <div class="profile__name">VIP 会员</div>
            {% endif %}
            <a href="{% url 'user:logout' %}" style="color:white;">退出登录</a>
        {% endif %}
    </div>
</div>
</body>
</html>

image-20240509165448191

模板文件user.html设置模板上下user_login, tips和user, 每个上下文的作用说明如下:
 user_login判断当前页面的功能类型, 从而显示相应的网页内容,
  若user_login的值为False, 则当前页面为用户注册页面, 否则为用户登录页面.
 tips是显示的提示信息, 比如用户注册失败或用户不存在等异常信息.
 user是表单类MyUserCreationForm的实例化对象, 由user生成网页表单, 从而实现用户注册和登录功能.
模板文件info.html使用模板上下文user和perms, 但视图函数info_view没有定义变量user和perms.
模板上下文user和perms是在Django解析模板文件的过程中生成的, 它们与配置文件settings.py的TEMPLATES设置有关.
我们查看settings.py的TEMPLATES配置信息, 代码如下:
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',  # 看这
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

因为TEMPLATES定义了处理器集合context_processors, 所以在解析模板文件之前, Django依次运行处理器集合的程序.
当运行到处理器auth时, 程序会生成变量user和perms, 并且将变量传入模板上下文TemplateContext中,
因此模板中可以直接使用模板上下文user(前登录用户的User对象)和perms(当前用户权限的对象).

运行MyDjango项目, 在浏览器上访问: 127.0.0.1:8000 进入用户注册页面, 创建新用户root, 密码为 mydjango123,
然后在用户登录页面使用新用户root完成登录过程, 最后在用户中心页面查看相关的用户信息, 如图10-23所示.

image-20240509165626050

image-20240509165756245

image-20240509161721300

10-23 用户中心页面
如果在Admin后台系统创建普通用户user1, 并且该用户不赋予任何权限, 在用户登录页面使用user1完成登录过程,
Django就会重新跳转到用户登录页面, 并且在登录页面的路由地址设置请求参数next, 
这说明当前用户不具备自定义权限vip_myuser, 导致无法访问用户中心页面, 如图10-24所示.
# user的views.py中的register_view视图
# 注释掉为新用户添加权限的语句
# u.user_permissions.add(p)  # 为新用户添加权限

image-20240509170609494

2024-05-09_170629

10-24 用户登录页面
?next=/info.html: 这是URL的查询字符串部分, 它通常用于向服务器传递参数.
在这个例子中, 有一个名为'next'的参数, 其值为'/info.html'.
这可能意味着一旦用户成功登录, 他们将被重定向到'/info.html'页面.

10.7 用户组的设置与使用

用户组是对用户进行分组管理, 其作用是在权限控制中可以批量地对用户权限进行分配, 
无需将权限一个一个地分配到每个用户, 节省维护的工作量.

将用户加入某个用户组, 该用户就拥有该用户组所具备的权限.
例如用户组teachers拥有权限can_add_lesson, 那么所有属于teachers用户组的用户都具备can_add_lesson权限.

我们知道用户, 权限和用户组三者之间是多对多的数据关系, 而用户组可以理解为用户和权限之间的中转站.
设置用户组分为两个步骤: 设置用户组的权限和设置用户组的用户.

设置用户组的权限主要对数据表auth_group和auth_permission构建多对多的数据关系, 数据关系保存在数据表auth_group_permissions中.
10.6节的MyDjango项目为例, 其数据库结构如图10-25所示.

image-20240509171646909

10-25 MyDjango的数据表
在Admin后台系统创建用户组, 但新建的用户组无须添加任何权限, 如图10-26所示.
创建组, 设置组的权限

2024-05-09_173642

image-20240509173809551

创建的组

image-20240509181048277

组的权限

image-20240509181754360

设用户的归属组

2024-05-09_173946

组中的用户

image-20240509181836025

10-26 在Admin后台系统创建用户组
在数据表auth_group中新建一行数据, 表字段name为'用户管理', 当前数据在项目中代表一个用户组;

image-20240509185715730

下一步对用户组'用户管理'进行权限分配, 在PyCharm的Terminal中使用Django的Shell模式实现用户组的权限配置, 代码如下:
>>> python manage.py shell
# 用户组的权限配置
# 导入内置模型Group和Permission
>>> from django.contrib.auth.models import Group
>>> from django.contrib.auth.models import Permission
# 获取某个权限对象p
>>> p = Permission.objects.get(codename='vip_myuser')
# 获取某个用户组对象group
>>> group = Group.objects.get(id=2)
# 将权限对象p添加到用户组group中
>>> group.permissions.add(p)

image-20240509182449240

上述代码将自定义权限vip_myuser添加到用户组'用户管理', 功能实现过程如下:
(1) 从数据表auth_permission中查询自定义权限vip_myuser的对象p, 对象p是数据表字段codename等于vip_myuser的数据信息.
(2) 从数据表auth_group中查询用户组'用户管理'的对象group, 对象group是数据表主键等于1的数据信息.
(3) 由对象group使用permissions.add方法将权限对象p和用户组对象group绑定,
    在数据表auth_group_permission中构建多对多的数据关系, 数据表的数据信息如图10-27所示.

image-20240509185817974

10-27 数据表auth_group_permissions
除了添加用户组的权限之外, 还可以删除用户组已有的权限, 代码如下:
# 删除当前用户组group的vip_myuser权限
>>> group.permissions.remove(p)
# 删除当前用户组group的全部权限
>>> group.permissions.clear()

image-20240509185018784

最后实现用户组的用户分配, 这个过程是对数据表auth_group和user_myuser构建多对多的数据关系,
数据关系保存在数据表user_myuser_groups中.
在Django的Shell模式下实现用户组的用户分配, 代码如下:
# 将用户分配到用户组
# 导入模型Group和MyUser
>>> from user.models import MyUser
>>> from django.contrib.auth.models import Group
# 获取用户对象user, 代表用户名为user1的数据信息
>>> user = MyUser.objects.get(username='user1')
# 获取用户组对象group, 代表用户组'用户管理'的数据信息
>>> group = Group.objects.get(id=2)
# 将用户添加到用户组
>>> user.groups.add(group)

2024-05-09_185159

上述代码将用户user1添加到用户组'用户管理', 实现过程与用户组的权限设置有相似之处, 只是两者使用的模型和方法有所不同.
查看数据表user_myuser_groups, 数据信息如图10-28所示.

image-20240509185943601

10-28 数据表user_myuser_groups
除了添加用户组的用户之外, 还可以删除用户组已有的用户, 代码如下:
# 删除用户组某一用户
>>> user.groups.remove(group)
# 清空用户组全部用户
>>> user.groups.clear()

image-20240509185502964

image-20240509185605096

10.8 本章小结

Django除了内置的Admin后台系统之外, 还内置了Auth认证系统.
整个Auth认证系统可分为三大部分: 用户信息, 用户权限和用户组, 在数据库中分别对应数据表auth_user, auth_permission和auth_group.
使用内置模型User和内置的函数可以快速实现用户管理功能, 如用户注册, 登录, 密码修改, 密码找回和用户注销.
模型User的字段说明以及常用的内置函数如下:
 id: int类型, 数据表主键.
 password: varchar类型, 代表用户密码, 在默认情况下使用pbkdf2_sha256方式来存储和管理用户的密码.
 last_login: datetime类型, 最近一次登录的时间.
 is_superuser: bool类型, 表示该用户是否拥有所有的权限, 即是否为超级管理员.
 username: varchar类型, 代表用户账号.
 first_name: varchar类型, 代表用户的名字.
 last_name: varchar类型, 代表用户的姓氏.
 email: varchar类型, 代表用户的邮件.
 is_staff: bool类型, 用来判断用户是否可以登录进入Admin后台系统.
 is_active: bool类型, 用来判断该用户的状态是否被激活.
 date_joined:datetime类型, 账号的创建时间.
 get_full_name(): 由AbstractUser定义, 获取用户的全名, 即字段first_name与last_name的组合值.
 get_short_name(): 由AbstractUser定义, 获取模型字段first_name的值.
 email_user(): 由AbstractUser定义, 发送邮件.
  参数subject设置邮件标题; 参数message设置邮件内容; 
  参数from_email设置发送邮件的账号, 即配置文件settings.py的DEFAULT_FROM_EMAIL.
 save(): 由AbstractBaseUser定义, 定义模型的数据保存方式.
 get_username(): 由AbstractBaseUser定义, 获取当前用户的账号信息, 即模型字段username.
 set_password(): 由AbstractBaseUser定义, 更改当前用户的密码, 即更改模型字段password.
 check_password(): 由AbstractBaseUser定义, 验证加密前的密码与加密后的密码是否相同.
 get_session_auth_hash(): 由AbstractBaseUser定义, 获取模型字段password的HMAC, 在密码更改时可使当前用户的登录状态失效.
 set_unusable_password(): 由AbstractBaseUser定义, 标记用户尚未设置密码.
 has_usable_password(): 由AbstractBaseUser定义, 检测用户是否尚未设置密码.
 get_group_permissions(): 由PermissionsMixin定义, 获取当前用户所在用户组的权限.
 get_all_permissions(): 由PermissionsMixin定义, 获取当前用户所拥有的权限.
 has_perm(): 由PermissionsMixin定义, 判断当前用户是否具有某个权限, 参数perm代表权限的名称, 以字符串表示.
 has_perms(): 由PermissionsMixin定义, 判断当前用户是否具有多个权限, 参数perm_list代表多个权限的集合, 以列表表示.
 has_module_perms(): 由PermissionsMixin定义, 判断当前用户是否具有某个项目应用的所有权限, 参数app_label代表项目应用的名称.
Django提供了4种模型扩展的方法, 说明如下:
* 1. 代理模型: 这是一种模型继承, 这种模型在数据库中无须创建新数据表.
     一般用于改变现有模型的行为方式, 如增加新方法函数等, 并且不影响数据表的结构.
     如果不需要在数据表存储额外的信息, 只是增加模型User的操作方法或更改模型的查询方式, 那么可以使用代理模型扩展模型User.
* 2. Profile扩展模型User: 当存储的信息与模型User相关, 而且不改变模型User的内置方法时, 可定义新的模型MyUser,
     并设置某个字段为OneToOneField, 这样能与模型User形成一对一关系, 该方法称为用户配置(User Profile).
* 3. AbstractBaseUser扩展模型User: 当模型User的内置方法不符合开发需求时, 
     可使用该方法对模型User重新自定义设计, 该方法对模型User和数据表结构造成较大影响.
* 4. AbstractUser扩展模型User: 如果模型User的内置方法符合开发需求,
     在不改变这些函数方法的情况下, 添加模型User的额外字段, 那么可通过AbstractUser方式替换原有模型User.
用户, 用户权限和用户组分别对应数据表user_myuser, auth_permission和auth_group.
无论是设置用户权限, 设置用户所属用户组或者设置用户组的权限, 它们的本质都是对两个数据表之间的数据建立多对多的数据关系, 说明如下:
 数据表user_myuser_user_permissions: 管理数据表user_myuser和auth_permission之间的多对多关系, 设置用户所拥有的权限.
 数据表user_myuser_groups: 管理数据表user_myuser和auth_group之间的多对多关系, 设置用户所在的用户组.
 数据表auth_group_permissions: 管理数据表auth_group和auth_permission之间的多对多关系, 设置用户组所拥有的权限.
;