第九章  Auth认证系统

  Django除了有强大的Admin管理系统之外,还提供了完善的用户管理系统。整个用户管理系统可分为三大部分:用户信息、用户权限和用户组,在数据库中分别对应数据表auth_user、auth_permission和auth_group。

9.1  内置User实现用户管理

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

  本节使用内置的用户管理系统实现用户的注册、登录、修改密码和注销功能。以MyDjango为例,在项目创建新的App,命名为user,并且在项目的settings.py和urls.py中配置App的信息,代码如下:

  1. #settings.py配置信息
  2. INSTALLED_APPS = [
  3. 'django.contrib.admin',
  4. 'django.contrib.auth',
  5. 'django.contrib.contenttypes',
  6. 'django.contrib.sessions',
  7. 'django.contrib.messages',
  8. 'django.contrib.staticfiles',
  9. 'index.apps.IndexConfig',
  10. 'user.apps.UserConfig',
  11. 'user_defined',
  12. ]
  13.  
  14. #文件夹MyDjango的urls.py的URL地址配置
  1. from django.contrib import admin
    from django.urls import path,include
  2.  
  3. urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/',include('index.urls')),
    path('user/',include('user.urls')),
    ]

  完成user的基本配置后,在App中分别添加urls.py和user.py文件,如下图:

  项目目录架构

  App中的urls.py主要用于接收和处理根目录的urls.py的请求信息。在App的urls.py中设定了4个不同的URL地址,分别代表用户登录、注册、修改密码和用户注销,代码如下:

  1. #urls.py
  2. #设置URL地址信息
  3. from django.urls import path
  4. from . import views
  5.  
  6. urlpatterns = [
  7. path('login.html', views.loginView, name='login'),
  8. path('register.html', views.registerView, name='register'),
  9. path('setpassword.html', views.setpasswordView, name='setpassword'),
  10. path('logout.html', views.logoutView, name='logout'),
  11. ]

  上述URL地址分别对应视图函数loginView、registerView、setpasswordView、logoutView;参数name用于设置URL的命名,可直接在HTML模板上使用并生成相应的URL地址。在讲解视图函数之前,首先了解HTML模板的代码结构,代码如下:

  1. #user.py文件
  2. #用户登录、注册和修改密码界面
  3. <!DOCTYPE html>
  4. <html lang="zh-cn">
  5. <head>
  6. <meta charset="utf-8">
  7. <title>{{ title }}</title>
  8. <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
  9. </head>
  10. <body>
  11. <div class="flex-center">
  12. <div class="container">
  13. <div class="flex-center">
  14. <div class="unit-1-2 unit-1-on-mobile">
  15. <h1>MyDjango Auth</h1>
  16. {% if tips %}
  17. <div>{{ tips }}</div>
  18. {% endif %}
  19. <form class="form" action="" method="post">
  20. {% csrf_token %}
  21. <div>用户名:<input type="text" name='username'></div>
  22. <div> 码:<input type="password" name='password'></div>
  23. {% if new_password %}
  24. <div>新密码:<input type="password" name='new_password'></div>
  25. {% endif %}
  26. <button type="submit" class="btn btn-primary btn-block">确定</button>
  27. </form>
  28. <div class="flex-left top-gap text-small">
  29. <div class="unit-2-3">
  30. <a href="{{ unit_2 }}">{{ unit_2_name }}</a>
  31. </div>
  32. <div class="unit-1-3 flex-right">
  33. <a href="{{ unit_1 }}">{{ unit_1_name }}</a>
  34. </div>
  35. </div>
  36. </div>
  37. </div>
  38. </div>
  39. </div>
  40. </body>
  41. </html>

  一个模板分别用于实现用户登录、注册和修改密码,该模板是由两个文本输入框和一个按钮所组成的表单,在该表单下分别设置不同链接,分别指向另外两个URL地址,如下图:

模板界面

  当用户输入账号和密码之后,单击"确定"按钮,就会触发一个POST请求,该请求将表单数据发送到当前的URL地址,再由相应的视图函数进行处理,并将处理结果返回到浏览器上生成相应的网页。

  了解URL和模板文件的配置后,接下来在views.py中实现用户登录功能,视图函数loginView的代码如下:

  1. from django.shortcuts import render, redirect
  2. from django.contrib.auth.models import User
  3. from django.contrib.auth import login, logout, authenticate
  4.  
  5. # Create your views here.
  6.  
  7. def loginView(request):
  8. title = '登录'
  9. unit_2 = '/user/register.html'
  10. unit_2_name = '立即注册'
  11. unit_1 = '/user/setpassword.html'
  12. unit_1_name = '修改密码'
  13. if request.method == 'POST':
  14. username = request.POST.get('username', '')
  15. password = request.POST.get('password', '')
  16. if User.objects.filter(username=username):
  17. user = authenticate(username=username, password=password)
  18. if user:
  19. if user.is_active:
  20. login(request, user)
  21. return redirect('/')
  22. else:
  23. tips = '账号密码错误,请重新输入'
  24. else:
  25. tips = '用户不存在,请注册'
  26. return render(request, 'user.html', locals())

  上述代码结合模板文件user.html的变量进行分析,分析如下:

    1、首先设置模板变量title和unit_2等变量值,在模板中生成相关的URL地址,可以从登录界面跳到注册界面或修改密码界面。

    2、由于提交表单是由当前的URL执行处理的,因此函数loginView需要处理不同的请求方式。如果是POST请求,则获取表单中两个文本框的数据内容,分别为username和password,然后对模型User中的数据进行判断和验证,只有验证成功之后,网页才会跳转到首页,否则在登录界面上提示错误信息。

    3、如果是GET请求,当完成模板变量赋值之后就不再做任何处理,直接将模板user.html生成HTML网页返回到浏览器上。

  在整个登录过程中,我们并没有对模型User进行定义,而函数中使用的模型User来自于Django的内置模型,在数据库中对于的数据表为auth_user,如下图:

项目MyDjango数据表

  打开数据表auth_user,可以通过表字段的命名了解模型字段的含义,如下图:

数据表auth_user的字段信息

  Django默认的模型User共定义了11个字段,各个字段的含义说明如下表:

字段 说明
ID int类型,数据表主键
Password varchar类型,代表用户密码,在默认情况下使用pbkdf2_sha256方式来存储和管理用户的密码
last_login datetime类型,最近一次登录的时间
is_superuser tinyint类型,表示该用户是否拥有所有的权限,即是否为超级用户
Username varchar类型,代表用户账号
first_name varchar类型,代表用户的名字
last_name varchar类型,代表用户的姓氏
Email varchar类型,代表用户的邮件
is_staff 用来判断用户是否可以登录进入Admin系统
is_active tinyint类型,用来判断该账户的状态是否被激活
date_joined datetime类型,账号的创建时间

  我们结合函数loginView和模型User的字段含义,进一步分析函数loginView的代码功能:

    1、当函数loginView收到POST请求并获取表单的数据后,根据表单的数据判断用户是否存在。当用户存在时,对用户的账号和密码进行验证处理,由内置函数authenticate完成验证功能,如果验证成功,函数authenticate返回模型User的数据对象User,否则返回None。

    2、然后从对象user的is_active字段来判断当前用户的状态是否被激活,如果为1,说明当前用户处于激活状态,可执行用户登录。

    3、最后执行用户登录,由内置函数login完成登录过程。函数login接收两个参数,第一个是request对象,来自视图函数的参数request;第二个是user对象,来自函数authenticate返回的对象user。

  从整个登录过程中可以发现,Django为我们提供了完善的内置函数,可快速实现用户登录功能。为了更好地演运行结果,在index中分别对模板index.html的<header>标签和视图函数index进行修改,代码如下:

  1. #模板index.py的<header>标签
  2. <header id="top">
  3. <!-- 内容显示区域 width : 1211px -->
  4. <div id="top_box">
  5. <ul class="lf">
  6. <li><a href="#">华为官网</a></li>
  7. <li><a href="#">华为荣耀</a></li>
  8. </ul>
  9. <ul class="rt">
  10. {% if username %}
  11. <li>用户名: {{ username }}</li>
  12. <li><a href="{% url 'logout' %}">退出登录</a></li>
  13. {% else %}
  14. <li><a href="{% url 'login' %}">登录</a></li>
  15. <li><a href="{% url 'register' %}">注册</a></li>
  16. {% endif %}
  17. </ul>
  18. </div>
  19. </header>
  1. #views.py的视图函数index
  2. def index(request):
  3. #获取当前请求的用户名
  4. username = request.user.username
  5. return render(request, 'index.html', locals())

  在浏览器上访问http://127.0.0.1:8000/user/login.html,在用户登录界面输入用户的账号和密码,然后单击"确定"按钮,将输入的用户信息提交到视图函数loginView中完成登录过程,运行结果如下:

用户登录

  上述例子用户实现登录,接下来完成用户注册功能,用户注册的视图函数为registerView,在user的views.py中编写函数registerView,代码如下:

  1. def registerView(request):
  2. #设置标题和另外两个URL链接
  3. title = '注册'
  4. unit_2 = '/user/login.html'
  5. unit_2_name = '立即登录'
  6. unit_1 = '/user/setpassword.html'
  7. unit_1_name = '修改密码'
  8. if request.method == 'POST':
  9. username = request.POST.get('username', '')
  10. password = request.POST.get('password', '')
  11. if User.objects.filter(usernam=username):
  12. tips = '用户已存在'
  13. else:
  14. user = User.objects.create_user(username=username,password=password)
  15. user.save()
  16. tips = '注册成功,请登录'
  17. return render(request, 'user.html', locals())

  从上述代码得知,用户注册与用户登录的流程大致相同,具体说明如下:

    1、当用户输入账号和密码并单击"确定"按钮后,程序将表单数据提交到函数registerView中进行处理。

    2、函数registerView首先获取表单的数据内容,根据获取的数据来判断模型User是否存在相关的用户信息。

    3、如果用户存在,直接返回到注册界面并提示用户已存在。

    4、如果用户不存在,程序使用内置函数create_user对模型User进行用户创建,函数create_user是模型User特有的函数,该函数创建并保存一个is_active=True的User对象。其中,函数参数username不能为空,否则抛出ValueError异常;而模型User的其他字段可作为函数create_user的可选参数,如email、first_name和password等。如果使用过程中没有设置函数参数password,则User对象的set_unusable_password()函数将会被调用,为当前用户创建一个随机密码。

  最后在views.py中变成函数setpasswordView,实现修改用户密码的功能,代码如下:

  1. def setpasswordView(request):
  2. #设置标题和另外两个URL链接
  3. title = '修改密码'
  4. unit_2 = '/user/login.html'
  5. unit_2_name = '立即登录'
  6. unit_1 = '/user/register.html'
  7. unit_1_name = '立即注册'
  8. new_password = True
  9. if request.method == 'POST':
  10. username = request.POST.get('username', '')
  11. old_password = request.POST.get('new_password', '')
  12. new_password = request.POST.get('new_password', '')
  13. if User.objects.filter(username=username):
  14. user = authenticate(username=username, password=old_password)
  15. user.set_password(new_password)
  16. user.save()
  17. tips = '密码修改成功'
  18. else:
  19. tips = '用户不存在'
  20. return render(request, 'user.html', locals())

  密码修改界面相比注册和登录界面多出了一个文本输入框,该文本输入框由模板变量new_password控制显示。当变量new_password为True时,文本输入框将显示到页面上,如下图:

  函数setpasswordView的处理逻辑与上述例子也是相似的,函数处理逻辑说明如下:

    1、当函数setpasswordView收到表单提交的请求后,程序会获取表单的数据内容,然后根据表单数据查找模型User相应的数据。

    2、如果用户存在,由内置函数authenticate验证用户的账号和密码是否正确,若验证成功,则返回user对象,再使用内置函数set_password修改对象user的密码,最后保存修改后的user对象,从而实现密码修改。

    3、如果用户不存在,直接返回到界面并提示用户不存在。

  密码修改主要由内置函数set_password实现,而函数set_password是在内置函数make_password的基础上进行封装而来的。我们知道在默认情况下,Django使用pbkdf2_sha256方式来存储和管理用户密码,而内置函数make_password主要实现用户密码的加密功能,并且该函数可以脱离Auth认证系统单独使用,比如对某些特殊数据进行加密处理等,在上述例子中,使用函数make_password实现密码修改,代码如下:

  1. #使用make_password实现密码修改
  2. def setpasswordView_1(request):
  3. if request.method == 'POST':
  4. username = request.POST.get('username', '')
  5. old_password = request.POST.get('password', '')
  6. new_password = request.POST.get('new_password', '')
  7. #判断用户是否存在
  8. user = User.objects.filter(username=username)
  9. if User.objects.filter(username=username):
  10. user = authenticate(username=username,password=old_password)
  11. #密码加密处理并保存到数据库
  12. dj_ps = make_password(new_password, None, 'pbkdf2_sha256')
  13. user.password = dj_ps
  14. user.save()
  15. return render(request, 'user.html', locals())

  除了内置函数make_password处,还有内置函数check_password,该函数是对加密前的密码与加密后的密码进行验证匹配,判断两者是否为同一个密码。在PyCharm的Terminal中开启Django的shell模式,函数make_password和check_password的使用方法如下:

  1. (py3_3) E:\test5\MyDjango>python manage.py shell
  2. Python 3.7.2 (default, Feb 21 2019, 17:35:59) [MSC v.1915 64 bit (AMD64)]
  3. Type 'copyright', 'credits' or 'license' for more information
  4. IPython 7.8.0 -- An enhanced Interactive Python. Type '?' for help.
  5.  
  6. In [1]: from django.contrib.auth.hashers import make_password, check_password
  7.  
  8. In [2]: ps = ""
  9.  
  10. In [3]: dj_ps = make_password(ps, None, 'pbkdf2_sha256')
  11.  
  12. In [4]: dj_ps
  13. Out[4]: 'pbkdf2_sha256$150000$n091gCKzoRqh$TnV7e6w7nN63uIOYHZK97/0iEarv+at4EB+NuOofBDk='
  14.  
  15. In [5]: ps_bool = check_password(ps, dj_ps)
  16.  
  17. In [6]: ps_bool
  18. Out[6]: True

  上述例子总分别讲述了用户登录、注册和密码修改的实现过程,最后只剩下用户注销,用户注销是用户管理系统较为简单的功能,调用内置函数logout即可实现。函数logout接收参数request,代表当前用户的请求对象,来自于视图函数的参数request。因此,视图函数logoutView的代码如下:

  1. #用户注销,退出登录、
  2. def logoutView(request):
  3. logout(request)
  4. return render('/index/')

9.2  发送邮件实现密码找回

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

  在实现有机发生功能之前,我们需要对邮箱进行相关配置,以163邮箱为例,在163邮箱的设置中找到账户设置,在账户设置中找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,然后开启POP3/SMTP服务,如下图:

  值得注意的是,开启服务时,163邮箱会返回一个客户端授权密码,该密码是用于登录第三方邮件客户端的专用密码,切记保存授权密码,该密码在开发过程中需要使用。

  本例中,我们使用163邮箱给用户发送验证邮件,因此在Django的settings.py中添加163邮箱的相关配置,配置信息如下:

  1. #邮件配置信息
  2. EMAIL_USE_SSL = True
  3. #邮件服务器,如果是163就改成smtp.163.com
  4. EMAIL_HOST = 'smtp.qq.com'
  5. #发送邮件的账号
  6. EMAIL_HOST_USER = "457291583@qq.com"
  7. #邮件服务器端口
  8. EMAIL_PORT = 465
  9. EMAIL_HOST_PASSWORD = "xxxxxxxxxxxxxx"
  10. DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

  上述配置是邮件发送方的邮件服务器信息,各个配置信息说明如下:

    1、EMAIL_USE_SSL:设置Django与邮件服务器的连接方式为SSL。

    2、EMAIL_HOST:设置服务器的地址,该配置使用SMTP服务器。

    3、EMAIL_PORT:设置服务器端口信息,若使用SMTP服务器,则端口应为465或587.

    4、EMAIL_HOST_USER:发送邮件的账号,该账号必须开启POP3/SMTP服务。

    5、EMAIL_HOST_PASSWORD:客户端授权密码,即上图开启服务后所获得的授权码。

    6、DEFAULT_FROM_EMAIL:设置默认发送邮件的账号。

  完成邮箱相关配置后,我们在user的urls.py、模板user.html和views.py中编写功能实现代码,代码如下:

  1. #user/urls.py代码
  2. from django.urls import path
  3. from . import views
  4. urlpatterns = [
  5. path('findPassword.html', views.findPassword, name='findPassword'),
  6. ]
  1. #模板user.html
  2. <!DOCTYPE html>
  3. <html lang="zh-cn">
  4. <head>
  5. <meta charset="utf-8">
  6. <title>找回密码</title>
  7. <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
  8. </head>
  9. <body>
  10. <div class="flex-center">
  11. <div class="container">
  12. <div class="flex-center">
  13. <div class="unit-1-2 unit-1-on-mobile">
  14. <h1>MyDjango Auth</h1>
  15. {% if tips %}
  16. <div>{{ tips }}</div>
  17. {% endif %}
  18. <form class="form" action="{% url 'findPassword' %}" method="post">
  19. {% csrf_token %}
  20. <div>用户名:<input type="text" name='username' value="{{ username }}"></div>
  21. <div>验证码:<input type="text" name='VerificationCode'></div>
  22. {% if new_password %}
  23. <div>新密码:<input type="password" name='password'></div>
  24. {% endif %}
  25. <button type="submit" class="btn btn-primary btn-block">{{ button }}</button>
  26. </form>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. </body>
  32. </html>

user.html

  1. #views.py的视图函数findPassword
  2. import random
  3. from django.shortcuts import render
  4. from django.contrib.auth.models import User
  5. from django.contrib.auth.hashers import make_password
  6.  
  7. # 找回密码
  8. def findPassword(request):
  9. button = '获取验证码'
  10. new_password = False
  11. if request.method == 'POST':
  12. username = request.POST.get('username', 'root')
  13. VerificationCode = request.POST.get('VerificationCode', '')
  14. password = request.POST.get('password', '')
  15. user = User.objects.filter(username=username)
  16. # 用户不存在
  17. if not user:
  18. tips = '用户' + username + '不存在'
  19. else:
  20. # 判断验证码是否已发送
  21. if not request.session.get('VerificationCode', ''):
  22. # 发送验证码并将验证码写入session
  23. button = '重置密码'
  24. tips = '验证码已发送'
  25. new_password = True
  26. VerificationCode = str(random.randint(1000, 9999))
  27. request.session['VerificationCode'] = VerificationCode
  28. user[0].email_user('找回密码', VerificationCode)
  29. # 匹配输入的验证码是否正确
  30. elif VerificationCode == request.session.get('VerificationCode'):
  31. # 密码加密处理并保存到数据库
  32. dj_ps = make_password(password, None, 'pbkdf2_sha256')
  33. user[0].password = dj_ps
  34. user[0].save()
  35. del request.session['VerificationCode']
  36. tips = '密码已重置'
  37. # 输入验证码错误
  38. else:
  39. tips = '验证码错误,请重新获取'
  40. new_password = False
  41. del request.session['VerificationCode']
  42. return render(request, 'user.html', locals())

/user/views.py

  用户第一次访问http://127.0.0.1:8000/user/findPassword.html的时候,触发了GET请求,视图函数findPassword直接将模板user.html返回,如下图:

  当输入用户名并单击"获取验证码"按钮的时候,触发了POST请求,视图函数findPassword首先根据用户输入的用户名和木星User里的数据进行查找,判断用户名是否存在,若不存在,则会生成提示信息,如上右图:

  如果用户存在,接着判断会话session的VerificationCode是否存在。若不存在,则视图函数findPassword通过发送邮件的方式验证码发到该用户的邮箱,验证码是使用random模块随机生成的4位数,然后将验证码写入会话session的VerificationCode,其作用是与用户输入的验证码进行匹配。邮件发送是由内置函数email_user实现的,该方法是模型User特有的方法之一,只是用于模型User。需要注意的是,用户的邮箱来自于模型User的字段email,如果当前用户的邮箱信息为空,邮件是无法发送出去的。邮件发送如下图:

  用户接受到验证码之后,可在网页上输入验证码,然后单击"重置密码"按钮,这时会触发POST请求,函数findPassword获取用户输入的验证码并与会话session的VerificationCode进行对比,如果两者不符合,说明用户输入的验证码与邮件中的验证码不一样,系统提示验证码错误,如下图:

  若用户输入的验证码与会话session的VerificationCode相符合,那么程序会执行密码修改。首先会获取用户输入的密码,然后使用函数make_password对密码加密处理并保持在模型User中,最后删除会话session与VerificationCode,否则会话session一值存在,在下次获取验证码时,程序不会执行邮件发送功能,运行结果如下:

  除了使用内置函数email_user实现邮件发送之外,Django还另外提供多种邮件发送方法。我们在Django的shell模式下进行讲解,代码如下:

  1. (py3_3) E:\test5\MyDjango>python manage.py shell
  2. #使用send_mail实现邮件发送
  3. In [1]: from django.core.mail import send_mail
  4. In [2]: from django.conf import settings
  5. #获取settings.py的配置信息
  6. In [3]: from_email = settings.DEFAULT_FROM_EMAIL
  7. #发送邮件,接受邮件以列表表示,说明可设置多个接受对象
  8. In [4]: send_mail('MyDjango', 'This is Django', from_email, ['vgyhn159110@163.com'])
  9. Out[4]: 1
  10.  
  11. #使用send_mass_mail实现多封邮件同时发送
  12. In [26]: from django.core.mail import send_mass_mail
  13.  
  14. In [27]: message1 = ('MyDjango', 'This is Django', from_email, ['vgyhn159110@163.com'])
  15.  
  16. In [28]: message2 = ('MyDjango', 'HELLO is Django', from_email, ['vgyhn159110@163.com'])
  17.  
  18. In [29]: send_mass_mail((message1, message2), fail_silently=False)
  19. Out[29]: 2
  20.  
  21. #使用EmailMultiAlternatives实现邮件发送
  22. In [5]: from django.core.mail import EmailMultiAlternatives
  23.  
  24. In [6]: content = '<p>这是一封<strong>重要的</strong>邮件。</p>'
  25.  
  26. In [7]: msg = EmailMultiAlternatives('MyDjango', content, from_email, ['vgyhn159110@163.com'])
  27.  
  28. #将正文设置为HTML格式
  29. In [8]: msg.content_subtype = 'html'
  30. #attach_alternative对正文内容进行补充和添加
  31. In [9]: msg.attach_alternative('<strong>This is from Django</strong>', 'text/html')
  32.  
  33. #添加附件(可选)
  34. In [10]: msg.attach_file('E://attachfile.csv')
  35. #发送
  36. In [25]: msg.send()
  37. Out[25]: 1

email_user实现邮件发送

  上述例子中分别讲述了send_mail、send_mass_mail和EmailMultiAlternatives的使用方法,方法说明如下:

    1、使用send_mail每次发送邮件都会建立一个新的连接,如果发送多封邮件,就需要建立多个连接,

    2、send_mass_mail是建立单个连接发送多封邮件,所以一次性发送多封邮件时,send_mass_mail要优于send_mail。

    3、EmailMultiAlternatives比前面两者更为个性化,可以设置邮件正文内容为HTML格式,也可以在邮件上添加附件,满足多方面的开发需求。

9.3  扩展User模型

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

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

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

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

  上述4中方法各有优缺点,一般情况下,建议使用AbstractUser扩展模型User,因为该方式对原有模型User影响较少而且无须额外创建数据表。下面以MyDjango项目为例讲解如何使用AbstractUser扩展模型User。首先在MySQL中找到项目所使用的数据库,并清除数据库中全部的数据表,在user的models.py文件中定义模型MyUser,代码如下:

  1. #user/models.py
  2. class MyUser(AbstractUser):
  3. qq = models.CharField('QQ号码', max_length=16)
  4. weChat = models.CharField('微信账号', max_length=100)
  5. mobile = models.CharField('手机号码', max_length=11)
  6.  
  7. #设置返回值
  8. def __str__(self):
  9. return self.username

  模型MyUser继承自AbstractUser类,AbstractUser类已有内置模型User的字段属性,因此模型MyUser具有模型User的全部属性。在执行数据迁移(创建数据表)之前,必须要在项目的settings.py中配置相关信息,配置信息如下:

  1. #settings.py
  2. AUTH_USER_MODEL = 'user.MyUser'

  配置信息是将内置模型User替换成user定义的模型MyUser,若没有设置配置信息,在创建数据表的时候,会分别创建数据表auth_user和user_myuser。在PyCharm的Terminal下执行数据迁移,代码如下:

  1. (py3_3) E:\test5\MyDjango>python manage.py makemigrations
  2. Migrations for 'user':
  3. user\migrations\0001_initial.py
  4. - Create model MyUser

(py3_3) E:\test5\MyDjango>python manage.py migrate

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

  数据表user_myuser

  上述例子使用AbstractUser扩展模型User,实现过程可分为两个步骤:

    1、定义新的模型MyUser,该模型必须继承AbstractUser类,在模型MyUser下定义的字段为扩展字段。

    2、在项目的配置文件settings.py中配置AUTH_USER_MODEL信息,在数据迁移时,将内置模型User替换成user定义的模型MyUser。

  完成模型User的扩展后,接着探讨模型MyUser与内置模型User在实际开发过程中是否存在使用上的差异。首先是要python manage.py createsuperuser创建超级用户并登录Admin后台管理系统,如下图:

  从上图中发现,认证与授权没有用户信息表,因为模型MyUser是在user的models.py中定义的。若将模型MyUser展示在后台系统,则可以在user的admin.py中定义相关数据对象,代码如下:

  1. #user/admin.py
  2. from django.contrib import admin
  3. from .models import MyUser
  4. from django.contrib.auth.admin import UserAdmin
  5. from django.utils.translation import gettext_lazy as _
  6. # Register your models here.
  7.  
  8. @admin.register(MyUser)
  9. class MyUserAdmin(UserAdmin):
  10. list_display = ['username', 'email', 'mobile', 'qq', 'weChat']
  11.  
  12. #修改用户时,在个人信息里添加'mobile'、'qq'、'weChat'的信息录入
  13. #将源码的UserAdmin.fieldsets转换成列表格式
  14. fieldsets = list(UserAdmin.fieldsets)
  15.  
  16. #重写UserAdmin的fieldsets,添加'mobile'、'qq'、'weChat'的信息录入
  17. fieldsets[1] = (_('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'mobile', 'qq', 'weChat')})
  18.  
  19. #user/__init__.py
  20. #设置App(user)的中文名
  21. from django.apps import AppConfig
  22. import os
  23.  
  24. #修改app在admin后台显示名称
  25. #default_app_config的值来自apps.py的类名
  26. default_app_config = 'user.IndexConfig'
  27.  
  28. #获取当前app的命名
  29. def get_current_app_name(_file):
  30. return os.path.split(os.path.dirname(_file))[-1]
  31.  
  32. #重写类IndexConfig
  33. class IndexConfig(AppConfig):
  34. name = get_current_app_name(__file__)
  35. verbose_name = '用户管理'

  重写MyDjango项目并进入Admin后台管理系统,可以在界面上看到模型MyUser所生成的用户信息表,如下图:

  进入用户信息表并修改某个用户信息的时候,发现用户信息的修改界面出现用户的手机号码、QQ号码和微信号吗的文本输入框,这是由MyUserAdmin类中重写属性fieldsets实现的,如下图:

  上述例子中,admin.py定义的MyUserAdmin继承自UserAdmin,UserAdmin是内置模型User的Admin数据对象,源码可在Python安装目录Lib\site-packages\django\contrib\auth\admin.py中查看。因此,在定义MyUserAdmin时,直接继承UserAdmin,并通过重写某些属性,可以快速开发扩展模型MyUser的Admin后台数据对象。

  除了继承UserAdmin的Admin数据对象之外,还可以在表单中继承内置模型User所定义的表单类。内置表单类可以在Python安装目录Lib\site-packages\django\contrib\auth\forms.py下查看源码。从源码中发现,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的字段,这说明这些表单都是在内置模型User的基础上实现的。因此,我们为扩展模型MyUser定义相关的表单类可以继承上述的表单类。以UserCreationForm为例,使用表单类UserCreationForm实现用户注册功能。在user中创建form.py文件,并在文件下编写一下代码:

  1. #user/form.py
  2. from django.contrib.auth.forms import UserCreationForm
  3. from .models import MyUser
  4.  
  5. class MyUserCreationForm(UserCreationForm):
  6. class Meta(UserCreationForm.Meta):
  7. model = MyUser
  8. #在注册界面添加邮箱、手机号码、微信号码和QQ号码
  9. fields = UserCreationForm.Meta.fields + ('email', 'mobile', 'weChat', 'qq')

  自定义表单类MyUserCreationForm继承自表单类UserCreationForm,并且重写类Meta的属性model和属性fields,分别重新设置表单类所绑定的模型和字段。然后在模板user.html和视图函数regusterView中编写一下代码,代码如下:

  1. <!DOCTYPE html>
  2. <html lang="zh-cn">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>用户注册</title>
  6. <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
  7. </head>
  8. <body>
  9. <div class="flex-center">
  10. <div class="container">
  11. <div class="flex-center">
  12. <div class="unit-1-2 unit-1-on-mobile">
  13. <h1>MyDjango Auth</h1>
  14. {% if tips %}
  15. <div>{{ tips }}</div>
  16. {% endif %}
  17. <form class="form" action="" method="post">
  18. {% csrf_token %}
  19. <div>用户名:{{ user.username }}</div>
  20. <div>邮 箱:{{ user.email }}</div>
  21. <div>手机号:{{ user.mobile }}</div>
  22. <div>Q Q 号:{{ user.qq }}</div>
  23. <div>微信号:{{ user.weChat }}</div>
  24. <div>密 码:{{ user.password1 }}</div>
  25. <div>密码确认:{{ user.password2 }}</div>
  26. <button type="submit" class="btn btn-primary btn-block">注 册</button>
  27. </form>
  28. </div>
  29. </div>
  30. </div>
  31. </div>
  32. </body>
  33. </html>
  1. #views.py的视图函数registerView
  2. from .form import MyUserCreationForm
  3.  
  4. #使用表单实现用户注册
  5. def registerView(request):
  6. if request.method == 'POST':
  7. user = MyUserCreationForm(request.POST)
  8. if user.is_valid():
  9. user.save()
  10. tips = '注册成功'
  11. user = MyUserCreationForm()
  12. else:
  13. user = MyUserCreationForm()
  14. return render(request, 'user.html', locals())

  从上述代码可以看到,视图函数registerView使用表单类MyUserCreationForm实现用户注册功能,功能说明如下:

    1、当用户在浏览器上访问http://127.0.0.1:8000/user/register.html时,视图函数首先将表单类MyUserCreationForm实例化后传给模板,在网页上生成用户注册的表单界面。

    2、输入用户信息并单击"注册"按钮,程序将表单数据交给表单类MyUserCreationForm处理并生成user对象。

    3、最后验证user对象的数据格式,若验证成功通过,则将数据保存到数据表user_myuser中,如下图:

  数据表user_myuser

玩转Django2.0---Django笔记建站基础九(一)(Auth认证系统)的更多相关文章

  1. 玩转Django2.0---Django笔记建站基础九(二)(Auth认证系统)

    9.4 设置用户权限 用户权限主要是对不同的用户设置不同的功能使用权限,而每个功能主要以模型来划分.以9.3节的MyDjango项目为例,在Admin后台管理系统可以查看并设置用户权限,如下图: 用户 ...

  2. 玩转Django2.0---Django笔记建站基础十三(第三方功能应用)

    第13章 第三方功能应用 在前面的章节中,我们主要讲述Django框架的内置功能以及使用方法,而本章主要讲述Django的第三方功能应用以及使用方法.通过本章的学习,读者能够在网站开发过程中快速开发网 ...

  3. 玩转Django2.0---Django笔记建站基础十一(二)((音乐网站开发))

    11.5 歌曲排行榜 歌曲排行榜是通过首页的导航链接进入的,按照歌曲的播放次数进行降序显示.从排行榜页面的设计图可以看到,网页实现三个功能:网页顶部搜索.歌曲分类筛选和歌曲信息列表,其说明如下: 1. ...

  4. 玩转Django2.0---Django笔记建站基础八(admin后台系统)

    第八章 admin后台系统 admin后台系统也成为网站后台管理系统,主要用于对网站前台的信息进行管理,如文字.图片.影音和其他日常使用文件的发布.更新.删除等操作,也包括功能信息的统计和管理,如用户 ...

  5. django笔记 - 建站

    1,建站步骤:1)django-admin.exe startproject mysite 创建完后的目录结构: - mysite # 对整个程序进行配置 - init - settings # 配置 ...

  6. 玩转Django2.0---Django笔记建站基础十二(Django项目上线部署)

    第十二章 Django项目上线部署 目前部署Django项目有两种主流方案:Nginx+uWsGI+Django或者Apache+uWSGI+Django.Nginx作为服务器最前端,负责接收浏览器的 ...

  7. 玩转Django2.0---Django笔记建站基础十(二)(常用的Web应用程序)

    10.3 CSRF防护 CSRF(跨站请求伪造)也成为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用,窃取网站的用户信息来制作 ...

  8. 玩转Django2.0---Django笔记建站基础十(一)(常用的Web应用程序)

    第十章 常用的Web应用程序 Django为开发者提供了常见的Web应用程序,如会话控制.高速缓存.CSRF防护.消息提示和分页功能.内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而 ...

  9. 玩转Django2.0---Django笔记建站基础六(模型与数据库)

    第六章 模型与数据库 Django对各种数据库提供了很好的支持,包括:PostgreSQL.MySQL.SQLite和Oracle,而且为这些数据库提供了统一的调用API,这些API统称为ORM框架. ...

随机推荐

  1. MFC 封装类为静态链接库

    mfc自带的基本控件都不怎么美观,所以一般开发者都会自定义类对控件进行重绘.手里也积累了不少控件的重绘,对对话框.静态文本.列表框等. 但是每次都要把这些类重新导入到新的工程里,比较麻烦,而且我也不想 ...

  2. 【转】Elasticsearch学习笔记

    一.常用术语 索引(Index).类型(Type).文档(Document) 索引Index是含有相同属性的文档集合.索引在ES中是通过一个名字来识别的,且必须是英文字母小写,且不含中划线(-):可类 ...

  3. npm脚本和package.json

    1.什么是npm脚本 在创建node.js项目如一个vue项目,或一个react项目时,项目都会生成一个描述文件package.json . 比如npm允许在package.json文件里面,使用sc ...

  4. java基础 -- 关键字static的用法

    static关键字的基本作用就是方便在没有创建对象的情况下调用类的方法/变量, static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问. static ...

  5. 如何解决Selenium句柄、多窗口问题

    有时我们在打开浏览器浏览网页时,当点击网页上某些链接时,它不是直接在当前页面上跳转,而是重新打开一个新标签页面,对于这种情况,想在新页面上操作,就得先切换窗口了.获取窗口的唯一标识用句柄表示,所以只需 ...

  6. 「学习笔记」动态规划 I『初识DP』

    写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...

  7. python版飞机大战代码简易版

    # -*- coding:utf-8 -*- import pygame import sys from pygame.locals import * from pygame.font import ...

  8. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  9. java基础之----redi分布式锁

    最近项目中,用到了redis分布式锁,使用过程有些心得,所以希望分享给大家. 首先我们意识里要知道分布锁有哪些? 分布式锁一般分三种,基于数据库的乐观锁,基于redis的分布式锁,基于zookeper ...

  10. Bandicam(班迪录屏)高清视频录制工具

    Bandicam(班迪录屏)简单好用的录屏幕,录游戏,录视频的功能强大的屏幕录像软件,比起其他软件其性能更加卓越. 与其他软件相比,用Bandicam录制的视频大小更小, 不仅保证原文件的质量.