07-day04-用户登录附加深入理解Django的中间件middleware
目录
一、用户登录
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
1.2、session & cookie 关系
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 method="POST" novalidate>
- 展示Form中的验证错误 ->
<span class="error-msg">{{ field.errors.0 }}</span>
- 不使用ajax,这里直接使用Form进行表单POST提交验证 ->
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户登录 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户登录</div>
// 表单 POST ,提交后台
<form method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg">{{ field.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img src="{% url 'image_code' %}" id="imageCode" title="点击更换图片">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<span class="error-msg">{{ field.errors.0 }}</span>
</div>
{% endif %}
{% endfor %}
<div>
<div style="float: right;">
// 使用后端接口(命名空间)生成图片验证码
<a href="{% url 'login_sms' %}">短信验证码登录?</a>
</div>
</div>
<div class="row">
<div class="col-xs-3">
<input type="submit" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
$(function () {
$('#imageCode').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc + "?");
})
})
</script>
{% 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' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg">{{ field.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img src="{% url 'image_code' %}" id="imageCode" title="点击更换图片">
</div>
</div>
</div>
{% else %}
...
...
// 绑定事件
...
...
{% block js %}
<script>
$(function () {
$('#imageCode').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc + "?");
})
})
</script>
{% endblock %}
1.5、用户名和密码图片验证码完成登录(标准)
- 第一步 :用户名和密码登录的模板是 不使用ajax,这里直接使用Form进行表单POST提交验证
// 回顾
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html
...
...
{% block content %}
<div class="account">
<div class="title">用户登录</div>
<form method="POST" novalidate>
- 第二步 :增加用户名密码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 %}
<style>
img {
width: 100%;
}
</style>
{% endblock %}
{% block content %}
<div>
<img src="{% static 'img/index/index-1.png' %}">
<img src="{% static 'img/index/index-2.png' %}">
<img src="{% static 'img/index/index-3.png' %}">
<img src="{% static 'img/index/index-4.png' %}">
</div>
{% endblock %}
- 第四步 :将base基页面的
BugManager
,使用反向解析的方式,解析到index首页
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html
...
...
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'index' %}">BugManager</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">产品功能</a></li>
<li><a href="#">企业方案</a></li>
<li><a href="#">帮助文档</a></li>
<li><a href="#">价格参考</a></li>
</ul>
...
...
1.8、优化base基页面,增加登录和注册
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.html
...
...
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">产品功能</a></li>
<li><a href="#">企业方案</a></li>
<li><a href="#">帮助文档</a></li>
<li><a href="#">价格参考</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="{% url 'login' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
<li><a href="#">Link</a></li>
...
...
1.9、优化短信登录页面及用户名密码登录,可以来回切换点击
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login.html
...
...
<div>
<div style="float: right;">
<a href="{% url 'login_sms' %}">短信验证码登录?</a>
</div>
</div>
<div class="row">
<div class="col-xs-3">
<input type="submit" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
...
...
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/login_sms.html
...
...
<div>
<div style="float: right;">
<a href="{% url 'login' %}">用户名密码登录?</a>
</div>
</div>
<div class="row">
<div class="col-xs-3">
<input id="btnSubmit" type="button" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% 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
...
...
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">产品功能</a></li>
<li><a href="#">企业方案</a></li>
<li><a href="#">帮助文档</a></li>
<li><a href="#">价格参考</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.bug_manager %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.bug_manager.username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">管理中心</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退 出</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'login' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
...
...
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
...
...
<ul class="nav navbar-nav navbar-right">
{% if request.bug_manager %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.bug_manager.username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
{# <li><a href="#">Action</a></li>#}
<li><a href="#">管理中心</a></li>
{# <li><a href="#">Something else here</a></li>#}
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退 出</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'login' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
...
...
二、知识点补充
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摘要
- Django 中的中间件(middleware),是一个镶嵌到Django的request/response处理机制中的一个hooks框架,是一个修改django全局输入输出的一个底层插件系统。让我们可以自定义想要的一些功能来处理用户的请求。
- 在Django中,中间件其实就是一个类,在类中包含一组特定的功能,在请求到来或者结束时,Django会根据我们定义的中间件规则执行中间件中对应的方法,一个 Django 项目默认激活的中间件在我们项目中的配置中可以看到是这个样子的:
# 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',
]
- MIDDLEWARE这里列表中的每一个元素,其实就是一个个单独的中间件,举例来说:django.middleware.csrf.CsrfViewMiddleware这个中间件,作用就是在我们的 form 表单提交请求的时候,提交的时候必须要带上csrf_token,否则就不会正确提交。
- 中间件使用顺序很重要,下一层依赖上一层的封装,例如,我们的AuthenticationMiddleware是一个认证中间件,在session 中保存认证用户的信息,但是他必须依赖于SessionMiddleware才可以被正确使用,所以他也必须在SessionMiddleware之后。
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、中间件执行前提
- 中间件要按照一定的顺序一层一层的执行下去,需要按照标准返回特定的内容:
如果为 None,则按照顺序继续向下执行
如果为 HttpResonse 对象,则直接将这个对象返回给用户
此处有一个版本前后的区别,请大家注意区分:
- 在 Django1.10之后, 当某个中间件,例如CsrfViewMiddleware请求process_request没有返回 None 后,这个请求会交给CsrfViewMiddleware的process_response来返回,即返回给相同一层的中间件来返回;
在 Django1.10之前的版本,会返回到最底层的中间件来返回:
2.1.4.1、中间件方法
- 1、
process_request(self, request)
- 其中request参数就是我们的HttpRequest对象,process_request 会在每个request在被决定使用哪个view之前调用,它会返回None或HttpResponse对象;
- 2、
process_view(self, request, callback, callback_args, callback_kwargs)
- 其中request参数就是的HttpRequest对象,callback 就是请求被决定使用的 view 函数,书具体的函数名,不是字符串类型。callback_args和callback_kwargs是 view 函数需要接受的参数,它会返回None或HttpResponse对象;
- 3、
process_template_response(self, request, response)
- 其中request 是 HttpRequest 对象, response 是一个由Django view或者中间件返回的TemplateResponse 对象,process_template_response()在 view 使用 render 渲染一个模版对象完成之后被调用,它必须返回一个render 方法执行后的response对象;
- 4、
process_exception(self, request, exception)
- 其中request参数就是的HttpRequest对象,exception是view函数中raise的Exception对象,当 view 函数 raise 一个 exception 的时候调用process_exception,它会返回None或HttpResponse对象;
- 5、
process_response(self, request, response)
- 其中request是 HttpRequest 对象,response 是一个django view或者中间件返回的 HttpResponse 或者StreamingHttpResponse对象,process_response会在所有响应到达浏览器之前被调用;
2.1.4.2、中间件详细执行流程
- 由于process_template_response在特定的 rander 渲染中才会被调用,所以过程中不添加该方法;
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、自定义中间件应用场景
- 应用场景这个问题其实不是很好去固定,因为大家在实际使用过程中的需求都不尽相同,所以我简单的举个我可以想到的例子吧。
- 在不修改业务逻辑源代码的情况下,我可以使用中间件来对用户的访问进行一定的筛选过滤,或者访问控制。
向往的地方很远,喜欢的东西很贵,这就是我努力的目标。