07-day04-用户登录附加深入理解Django的中间件middleware


目录
  • 一、用户登录
    • 1.1、pillow生成验证码
      • 1.1.1、基本使用
      • 1.1.2、图片验证码
    • 1.2、session & cookie 关系
      • 1.2.1、客户端与服务端请求响应的关系
      • 1.2.2、cookie是什么
      • 1.2.3、session是什么
      • 1.2.5、session的实现机制
    • 1.3、生成图片验证码
      • 1.3.1、用户名和密码登录页面展示
      • 1.3.2、生成验证码图片的接口
    • 1.4、验证码超时和点击更换
      • 1.4.1、验证码存入session并设置session超时
      • 1.4.2、前端图片验证码点击更换
    • 1.5、用户名和密码图片验证码完成登录(标准)
    • 1.6、用户名和密码图片验证码完成登录(优化 :邮箱或手机号登录)
    • 1.7、访问首页Index
    • 1.8、优化base基页面,增加登录和注册
    • 1.9、优化短信登录页面及用户名密码登录,可以来回切换点击
    • 1.10、增加判断用户登录状态,从而显示用于信息及管理中心按钮(用户认证完善->middleware中间件)
    • 1.11、用户退出登录(清空当前用户session)
  • 二、知识点补充
    • 2.1、深入理解Django的中间件middleware
      • 2.1.1、Middleware摘要
      • 2.1.2、中间件结构
      • 2.1.3、中间件执行过程
      • 2.1.4、中间件执行前提
        • 2.1.4.1、中间件方法
        • 2.1.4.2、中间件详细执行流程
      • 2.1.5、自建中间件与执行过程测试
      • 2.1.6、自定义中间件应用场景

一、用户登录

1.1、pillow生成验证码

  • Python生成随机验证码,需要使用PIL模块.
安装:pip3 install pillow

1.1.1、基本使用

1、创建图片

from PIL import Image
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
 
# 在图片查看器中打开
# img.show() 
 
# 保存在本地
with open('code.png','wb') as f:
    img.save(f,format='png')

2、创建画笔,用于在图片上画任意内容

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')

3、画点

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示坐标
# 第二个参数:表示颜色
draw.point([100, 100], fill="red")
draw.point([300, 300], fill=(255, 255, 255))

4、画线

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示起始坐标和结束坐标
# 第二个参数:表示颜色
draw.line((100,100,100,300), fill='red')
draw.line((100,100,300,100), fill=(255, 255, 255))

5、画圆

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示起始坐标和结束坐标(圆要画在其中间)
# 第二个参数:表示开始角度
# 第三个参数:表示结束角度
# 第四个参数:表示颜色
draw.arc((100,100,300,300),0,90,fill="red")

6、写文本

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示起始坐标
# 第二个参数:表示写入内容
# 第三个参数:表示颜色
draw.text([0,0],'python',"red")

7、特殊字体文字

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示字体文件路径
# 第二个参数:表示字体大小
font = ImageFont.truetype("kumo.ttf", 28)
# 第一个参数:表示起始坐标
# 第二个参数:表示写入内容
# 第三个参数:表示颜色
# 第四个参数:表示颜色
draw.text([0, 0], 'python', "red", font=font)

1.1.2、图片验证码


import random
 
def check_code(width=120, height=30, char_length=5, font_file='kumo.ttf', font_size=28):
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')
 
    def rndChar():
        """
        生成随机字母   
        :return:
        """
        return chr(random.randint(65, 90))
 
    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
 
    # 写文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
 
    # 写干扰点
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
 
    # 写干扰圆圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
 
    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)
 
        draw.line((x1, y1, x2, y2), fill=rndColor())
 
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img,''.join(code)
 
 
if __name__ == '__main__':
    # 1. 直接打开
    # img,code = check_code()
    # img.show()
 
    # 2. 写入文件
    # img,code = check_code()
    # with open('code.png','wb') as f:
    #     img.save(f,format='png')
 
    # 3. 写入内存(Python3)
    # from io import BytesIO
    # stream = BytesIO()
    # img.save(stream, 'png')
    # stream.getvalue()
 
    # 4. 写入内存(Python2)
    # import StringIO
    # stream = StringIO.StringIO()
    # img.save(stream, 'png')
    # stream.getvalue()
 
    pass

PS 原文链接 :

1.2.1、客户端与服务端请求响应的关系

  • USER(客户端) 请求 Server(服务器), 属于HTTP请求。http请求是无状态的,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;所以当用户从客户端请求一次登录后,登录成功,再次进行请求时,因为 Server 不能识别这两次会话都是来自同一个浏览器,即服务端不知道客户端的历史请求记录;就会再次弹出登录对话框。

  • 为了解决客户端与服务端会话同步问题。这便引出了下面几个概念:cookie、session。
  • 于是,我们便把服务器中产生的会话sessionID存储到客户端浏览器cookie中去。在客户端存在周期为浏览器关闭时,消失。这样便解决了客户端请求服务端会话不同步问题。

1.2.2、cookie是什么

  • 一个HTTP cookie的(网络Cookie,浏览器cookie)是一小片数据的一个服务器发送到用户的网络浏览器。浏览器可以存储它并将其与下一个请求一起发送回同一服务器。通常,它用于判断两个请求是否来自同一个浏览器 - 例如,保持用户登录。它记住无状态 HTTP协议的有状态信息。

1.2.3、session是什么

  • 客户端请求服务端,服务端(Server)会为这次请求开辟一块内存空间,这个对象便是Session对象, 存储结构为ConcurrentHashMap。
  • session的目的:弥补HTTP无状态特性,服务器可以利用session存储客户端在同一个会话期间的一些操作记录。

1.2.4、HTTP是无状态的

  • 在同一连接上连续执行的两个请求之间没有链接。对于试图与某些页面连贯地相互作用的用户而言,这立即存在问题,例如,使用电子商务购物篮。但是,虽然HTTP本身的核心是无状态,但HTTP cookie允许使用有状态会话。使用标头可扩展性,HTTP Cookie被添加到工作流中,允许在每个HTTP请求上创建会话以共享相同的上下文或相同的状态。

1.2.5、session的实现机制

  • 1、服务器如何判断客户端发送过来的请求属于同一个会话?
    • 用session id区分;session id 相同即认为是同一个会话;
    • 在Server中session id中用JSESSIONID来表示;
  • 2、服务器、客户端如何获取sessionID?SessionID在期间是如何传输的?
    • 服务器第一次接收到请求时,开辟了一块Session空间(创建了Session对象),同时生成一个Session id,并通过响应头的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客户端发送要求设置cookie的响应; 客户端收到响应后,在本机客户端设置了一个JSESSIONID=XXXXXXX的cookie信息,该cookie的过期时间为浏览器会话结束;
    • 接下来客户端每次向同一个网站发送请求时,请求头都会带上该cookie信息(包含Session id); 然后,服务器通过读取请求头中的Cookie信息,获取名称为JSESSIONID的值,得到此次请求的Session id;
    • 注意:服务器只会在客户端第一次请求响应的时候,在响应头上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下来在同一个会话的第二第三次响应头里,是不会添加Set- Cookie:“JSESSIONID=XXXXXXX”信息的; 而客户端是会在每次请求头的cookie中带上JSESSIONID信息;



1.3、生成图片验证码

1.3.1、用户名和密码登录页面展示

  • 第一步 :设计用户直接使用用户名邮箱登录的路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py

from django.conf.urls import url, include
from web_app.views import account

urlpatterns = [
    url(r'^send/sms/$', account.send_sms, name='send_sms'),
    # 短信登录
    url(r'^login/sms/$', account.login_sms, name='login_sms'),
    # 用户注册
    url(r'^register/$', account.register, name='register'),
    # 用户名邮箱验证码登录
    url(r'^login/$', account.login, name='login'),
]
  • 第二步 :编写用户名密码登录的前端模板
    • 不使用ajax,这里直接使用Form进行表单POST提交验证 ->
    • 展示Form中的验证错误 -> {{ field.errors.0 }}
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html 

{% extends 'layout/basic.html' %}
{% load static %}

{% block title %} 用户登录 {% endblock %}


{% block css %}
    
    
{% endblock %}


{% block content %}
    
{% endblock %}


{% block js %}
    
{% endblock %}
  • 第三步 :准备用户名密码登录的Form进行表单验证
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 

from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
from web_app.forms.bootstrap import BootStrapForm
...
...
class LoginForm(BootStrapForm, forms.Form):
    username = forms.CharField(label='用户名')
    password = forms.CharField(label='密码', widget=forms.PasswordInput())
    code = forms.CharField(label='图片验证码')
  • 第四步 :Views视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
...
...
def login(request):
    """ 用户名和密码登录 """
    form = LoginForm()
    return render(request, 'login.html', {'form': form})
  • 第五步 :先查看用户名和密码登录页面效果

1.3.2、生成验证码图片的接口

  • 第一步 :设计用于生成图片验证码的路由(用于用户名和密码登录前端模板以命名空间的形式访问)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py 

from django.conf.urls import url, include
from web_app.views import account

urlpatterns = [
    url(r'^send/sms/$', account.send_sms, name='send_sms'),          # 发送SMS
    url(r'^login/sms/$', account.login_sms, name='login_sms'),       # 短信登录
    url(r'^register/$', account.register, name='register'),          # 用户注册
    url(r'^login/$', account.login, name='login'),                   # 用户名邮箱验证码登录
    url(r'^image/code/$', account.image_code, name='image_code'),    # 图片验证码
]
  • 第二步 :将生成图片验证码功能封装到utils
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat utils/image_code.py      
# -*- coding:utf-8 -*-
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random


def check_code(width=120, height=30, char_length=5, font_file='utils/Monaco.ttf', font_size=28):
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')

    def rndChar():
        """
        生成随机字母
        :return:
        """
        return chr(random.randint(65, 90))

    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))

    # 写文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())

    # 写干扰点
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())

    # 写干扰圆圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())

    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)

        draw.line((x1, y1, x2, y2), fill=rndColor())

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img, ''.join(code)


if __name__ == '__main__':
    image_object, code = check_code()

    # 把图片写入文件
    """
    with open('code.png', 'wb') as f:
        image_object.save(f, format='png')
    """

    # 把图片的内容写到内存 stream
    """
    from io import BytesIO
    stream = BytesIO()
    image_object.save(stream, 'png')
    stream.getvalue()
   ''''''
  • 第三步 :生成图片验证码并返回给前端的 Views视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
...
...
def image_code(request):
    """ 生成图片验证码并返回前端 """
    from io import BytesIO
    from utils.image_code import check_code
    image_object, code = check_code()
    # 将生成的图片对象返回给前端(将图片写入内存,再使用HttpResponse从内存获取并返回)
    stream = BytesIO()
    image_object.save(stream, 'png')
    return HttpResponse(stream.getvalue())
    # with open('code.png', 'rb') as f:
    #     image_object.save(f, format='png')
    # with open('code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

1.4、验证码超时和点击更换

1.4.1、验证码存入session并设置session超时

  • 第一步 :需要将验证码加入到session中(存入到当前客户单的session)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
...
...
def image_code(request):
    """ 生成图片验证码并返回前端 """
    from io import BytesIO
    from utils.image_code import check_code
    image_object, code = check_code()
    request.session['image_code'] = code
    stream = BytesIO()
    image_object.save(stream, 'png')
    return HttpResponse(stream.getvalue())
    # with open('code.png', 'rb') as f:
    #     image_object.save(f, format='png')
    # with open('code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
  • 默认的session过期时长为14天


  • 第二步 :对图片验证码默认的session过期时长进行调整,需要主动给session修改过期时间为60s
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
...
...
def image_code(request):
    """ 生成图片验证码并返回前端 """
    from io import BytesIO
    from utils.image_code import check_code
    image_object, code = check_code()
    request.session['image_code'] = code
    request.session.set_expiry(60)  # 60s
    stream = BytesIO()
    image_object.save(stream, 'png')
    return HttpResponse(stream.getvalue())
    # with open('code.png', 'rb') as f:
    #     image_object.save(f, format='png')
    # with open('code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

1.4.2、前端图片验证码点击更换

  • 将图片验证码设置ID,并将ID绑定一个事件
// 设置ID
...
...

{% if field.name == 'code' %}
                    
{{ field }} {{ field.errors.0 }}
{% else %} ... ... // 绑定事件 ... ... {% block js %} {% endblock %}

1.5、用户名和密码图片验证码完成登录(标准)

  • 第一步 :用户名和密码登录的模板是 不使用ajax,这里直接使用Form进行表单POST提交验证
// 回顾
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html 
...
...
{% block content %}
    
  • 第二步 :增加用户名密码Form的表单验证
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py

from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
from web_app.forms.bootstrap import BootStrapForm
from utils import encrypt
...
...
class LoginForm(BootStrapForm, forms.Form):
    username = forms.CharField(label='用户名')
    password = forms.CharField(label='密码', min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    code = forms.CharField(label='图片验证码')

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        self.request = request

    def clean_password(self):
        """ 用户密码钩子函数,进行MD5加密 """
        pwd = self.cleaned_data['password']
        # 加密 & 返回
        return encrypt.md5(pwd)

    def clean_code(self):
        """
        图片验证码钩子函数
        读取用户输入的code值与session中的code进行校验
        """
        # 读取用户输入的code值
        code = self.cleaned_data['code']

        # 获取session中的code进行校验
        session_code = self.request.session.get('image_code')   # 使用get方法获取避免session中的验证码不存在
        if not session_code:
            raise ValidationError('验证码已过期,请重新获取')

        if code.strip().upper() != session_code.upper():
            raise ValidationError('验证码输入有误')
        return code
  • 第三步 :视图进行对method的判断,GET就是展示登录页面,POST就是点击登录,并在视图中进行用户名密码校验
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
...
...
"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from web_app.forms.account import RegisterModelForm,SendSmsForm, LoginSMSForm, LoginForm
from web_app import models

def login(request):
    """ 用户名和密码登录 """
    if request.method == 'GET':
        form = LoginForm(request)
        return render(request, 'login.html', {'form': form})

    # POST,前端表单提交
    form = LoginForm(request, data=request.POST)
    if form.is_valid():
        username = form.cleaned_data['username']
        password = form.cleaned_data['password']

        user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        if user_object:
            # 用户名密码正确,进行跳转至index路由
            return render('index')
        # 如果用户名不存在,则在视图中对Form表单验证增加异常
        form.add_error('username', '用户名或密码错误')
    return render(request, 'login.html', {'form': form})

def image_code(request):
    """ 生成图片验证码并返回前端 """
    from io import BytesIO
    from utils.image_code import check_code
    image_object, code = check_code()
    request.session['image_code'] = code
    request.session.set_expiry(60)  # 60s
    stream = BytesIO()
    image_object.save(stream, 'png')
    return HttpResponse(stream.getvalue())
    # with open('code.png', 'rb') as f:
    #     image_object.save(f, format='png')
    # with open('code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

1.6、用户名和密码图片验证码完成登录(优化 :邮箱或手机号登录)

  • 第一步 :优化Form
...
...
class LoginForm(BootStrapForm, forms.Form):
    username = forms.CharField(label='邮箱或手机号')
    password = forms.CharField(label='密码', min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    code = forms.CharField(label='图片验证码')
...
...
  • 第二步 :优化视图
...
...
def login(request):
    """ 用户名和密码登录 """
    if request.method == 'GET':
        form = LoginForm(request)
        return render(request, 'login.html', {'form': form})

    # POST,前端表单提交
    form = LoginForm(request, data=request.POST)
    if form.is_valid():
        username = form.cleaned_data['username']
        password = form.cleaned_data['password']

        # user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        # 用户&密码 或者 邮箱&密码 使用Q 实现复杂的搜索条件
        from django.db.models import Q
        user_object = models.UserInfo.objects.filter(Q(mobile_phone=username)|Q(username=username)
                                                     ).filter(password=password).first()

        if user_object:
            # 用户名密码正确,进行跳转至index路由
            return render('index')
        # 如果用户名不存在,则在视图中对Form表单验证增加异常
        form.add_error('username', '用户名或密码错误')
    return render(request, 'login.html', {'form': form})
...
...

  • 第三步 :From中密码字段中增加render_value=True 属性会在前端如果某字段输入错误,但是密码还会显示在页面
...
...

class LoginForm(BootStrapForm, forms.Form):
    username = forms.CharField(label='邮箱或手机号')
    password = forms.CharField(label='密码', min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput(), required=True)
    code = forms.CharField(label='图片验证码')
...
...

1.7、访问首页Index

  • 第一步 :设计用户访问的URL路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat Bug_manager/urls.py

from django.conf.urls import url, include
from web_app.views import account
from web_app.views import home

urlpatterns = [
    url(r'^send/sms/$', account.send_sms, name='send_sms'),          # 发送SMS
    url(r'^login/sms/$', account.login_sms, name='login_sms'),       # 短信登录
    url(r'^register/$', account.register, name='register'),          # 用户注册
    url(r'^login/$', account.login, name='login'),                   # 用户名邮箱验证码登录
    url(r'^image/code/$', account.image_code, name='image_code'),    # 图片验证码

    url(r'^index/$', home.index, name='index'),

]
  • 第二步 :Views视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/home.py 

# -*- coding:utf-8 -*-
from django.shortcuts import render, redirect, HttpResponse

def index(request):
    return render(request, 'index.html')
  • 第三步 :Template模板,用户登录首页(显示图片)

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/index.html 

{% extends 'layout/basic.html' %}
{% load static %}

{% block title %} 首页 {% endblock %}

{% block css %}
    
{% endblock %}

{% block content %}
    
{% endblock %}

  • 第四步 :将base基页面的 BugManager,使用反向解析的方式,解析到index首页
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html 

...
...

1.8、优化base基页面,增加登录和注册

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html 

...
...
    
    

1.9、优化短信登录页面及用户名密码登录,可以来回切换点击

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html    
...
...
            

            
{% endblock %} ... ...

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login_sms.html 
...
...
            
            
{% endblock %} ... ...

1.10、增加判断用户登录状态,从而显示用于信息及管理中心按钮(用户认证完善->middleware中间件)

  • 第一步 :Views : 在用户名和密码登录视图及短信验证码登录视图中,当验证通过后自定义session字段,将用户ID写入session
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py  

...
...
def login_sms(request):
    """ 短信登录 """
    if request.method == 'GET':
        form = LoginSMSForm()
        return render(request, 'login_sms.html', {'form': form})

    form = LoginSMSForm(request.POST)
    if form.is_valid():
        # 用户输入正确,登录成功
        mobile_phone = form.cleaned_data['mobile_phone']

        # 把用户名写入到session中
        user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first()
        request.session['user_id'] = user_object.id
        request.session.set_expiry(60 * 60 * 24 * 14)

        return JsonResponse({"status": True, 'data': "/index/"})
    return JsonResponse({"status": False, 'error': form.errors})


def login(request):
    """ 用户名和密码登录 """
    if request.method == 'GET':
        form = LoginForm(request)
        return render(request, 'login.html', {'form': form})

    # POST,前端表单提交
    form = LoginForm(request, data=request.POST)
    if form.is_valid():
        username = form.cleaned_data['username']
        password = form.cleaned_data['password']

        # user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        # 用户&密码 或者 邮箱&密码 使用Q 实现复杂的搜索条件
        from django.db.models import Q
        user_object = models.UserInfo.objects.filter(Q(mobile_phone=username)|Q(email=username)
                                                     ).filter(password=password).first()

        if user_object:
            # 用户名密码正确,将用户ID写入session,进行跳转至index路由(命名空间使用即反向解析)
            request.session['user_id'] = user_object.id
            request.session.set_expiry(60 * 60 * 24 * 14)   # 定义用户信息的session过期时长为2周
            return render('index')
        # 如果用户名不存在,则在视图中对Form表单验证增加异常
        form.add_error('username', '用户名或密码错误')
    return render(request, 'login.html', {'form': form})

...
...
  • 第二步 :使用diango中间见,处理用户登录后的白名单判定
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/middleware/auth.py 

from django.utils.deprecation import MiddlewareMixin
from web_app import models

class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        """ 如果用户已经登录,则request中赋值 """
        user_id = request.session.get('user_id', 0)    # 用户登录则session中有用户ID,如果没有则为0

        # 如果登录的用户存在则返回用户对象,不存在返回None
        user_object = models.UserInfo.objects.filter(id=user_id).first()
        request.bug_manager = user_object
  • 第三步 :settings中配置自定义中间件,顺序往下放
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'web_app.middleware.auth.AuthMiddleware',
]
  • 第四步 :Template 视图,进行对request.session中自定义字段进行判断
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html 

...
...

    
    
  
... ...

1.11、用户退出登录(清空当前用户session)

  • 第一步 :设计退出路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py   

from django.conf.urls import url, include
from web_app.views import account
from web_app.views import home

urlpatterns = [
    url(r'^send/sms/$', account.send_sms, name='send_sms'),          # 发送SMS
    url(r'^login/sms/$', account.login_sms, name='login_sms'),       # 短信登录
    url(r'^register/$', account.register, name='register'),          # 用户注册
    url(r'^login/$', account.login, name='login'),                   # 用户名邮箱验证码登录
    url(r'^image/code/$', account.image_code, name='image_code'),    # 图片验证码
    url(r'^index/$', home.index, name='index'),                      # 登录首页
    url(r'^logout/$', account.logout, name='logout'),                # 退出登录

]
  • 第二步 :Views视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 

...
...
def logout(request):
    request.session.flush()
    return redirect('index')	# 反向代理
  • 第三步 :Template前端模板
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html 

...
...
      
    
... ...

二、知识点补充

2.1、深入理解Django的中间件middleware

PS :基于 Django 1.11 :https://docs.djangoproject.com/en/1.11/topics/http/middleware/#writing-your-own-middleware

2.1.1、Middleware摘要

# settings.py

MIDDLEWARE = [
	'django.middleware.security.SecurityMiddleware',
	'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.common.CommonMiddleware',
	'django.middleware.csrf.CsrfViewMiddleware',
	'django.contrib.auth.middleware.AuthenticationMiddleware',
	'django.contrib.messages.middleware.MessageMiddleware',
	'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

PS :具体的顺序问题可以参考 :https://docs.djangoproject.com/en/1.11/ref/middleware/#middleware-ordering

2.1.2、中间件结构

1. process_request(self, request)
2. process_view(self, request, callback, callback_args, callback_kwargs)
3. process_template_response(self, request, response)
4. process_exception(self, request, exception)
5. process_response(self, request, response)

2.1.3、中间件执行过程

2.1.4、中间件执行前提


2.1.4.1、中间件方法

2.1.4.2、中间件详细执行流程

2.1.5、自建中间件与执行过程测试

#  我们也可以自己定义一个中间件,我们可以自己写一个类,但是必须继承MiddlewareMixin

from django.utils.deprecation import MiddlewareMixin

class MyMiddleware_1(MiddlewareMixin):
	 def process_request(self, request):
	  print("自定义 process_request 1")
	  return None

	 def process_response(self, request, response):
	  print("自定义 process_response 1")
	  return response

	 def process_view(self, request, callback, callback_args, callback_kwargs):
	  print("自定义 process_view 1")
	  return None

	 def process_exception(self, request, exception):
	  print("自定义 process_exception 1")
 
 
class MyMiddleware_2(MiddlewareMixin):
	 def process_request(self, request):
	  print("自定义 process_request 2")
	  return None

	 def process_response(self, request, response):
	  print("自定义 process_response 2")
	  return response

	 def process_view(self, request, callback, callback_args, callback_kwargs):
	  print("自定义 process_view 2")
	  return None

	 def process_exception(self, request, exception):
	  print("自定义 process_exception 2")
MIDDLEWARE = [
	 'django.middleware.security.SecurityMiddleware',
	 'django.contrib.sessions.middleware.SessionMiddleware',
	 'django.middleware.common.CommonMiddleware',
	 'django.middleware.csrf.CsrfViewMiddleware',
	 'django.contrib.auth.middleware.AuthenticationMiddleware',
	 'django.contrib.messages.middleware.MessageMiddleware',
	 'django.middleware.clickjacking.XFrameOptionsMiddleware',
	 'app_aa.middle_by_me.MyMiddleware_1', # 第一个自定义 middleware
	 'app_aa.middle_by_me.MyMiddleware_2' # 第二个自定义 middleware
]

2.1.6、自定义中间件应用场景

相关