一、登錄頁面
from django.contrib import admin from django.urls import path from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), ]
創建login視圖函數
from django.shortcuts import render # Create your views here. def login(request): return render(request, 'login.html')
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
<link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css">
</head>
<body>
<h3>登錄頁面</h3>
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-offset-3">
<form action="">
<div class="form-group">
{# label標簽的"for"屬性可把label綁定到另外一個元素,因此要把for屬性值與input的id屬性相同。#}
{# 當用戶選擇該標簽時,瀏覽器就會自動將焦點轉到和標簽相關的表單控件 #}
<label for="user">用戶名</label>
{# 這里沒必要加name屬性了,之前加是點擊submit按鈕自己組裝鍵值發出去,現在用ajax發只要找到標簽拿到里面的值即可 #}
<input type="text" id="user" class="form-control">
</div>
<div class="form-group">
<label for="pwd">密碼</label>
<input type="password" id="pwd" class="form-control">
</div>
{# 這里提交按鈕不能使用<input type="submit">這就變成form表單提交事件了。 button類型時,這個按鈕沒有任何事件,可以給這個按鈕綁定一個事件 #}
<input type="button" class="btn btn-default login-btn pull-right" value="提交">
</form>
</div>
</div>
</div>
</body>
</html>
注意:
1、label標簽的"for"屬性可把label綁定到另外一個元素,因此要把for屬性值與input的id屬性相同。當用戶選擇該標簽時,瀏覽器就會自動將焦點轉到和標簽相關的表單控件;
2、這里沒必要加name屬性了,之前加是點擊submit按鈕自己組裝鍵值發出去,現在用ajax發只要找到標簽拿到里面的值即可。
3、這里提交按鈕不能使用<input type="submit">這樣就變成form表單提交事件了。 設為button類型時,這個按鈕沒有任何事件,可以給這個按鈕綁定一個事件
二、在頁面中添加驗證碼圖片請求路徑
1、登錄頁面添加驗證碼
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-offset-3">
<form action="">
<div class="form-group">
<label for="user">用戶名</label>
<input type="text" id="user" class="form-control">
</div>
<div class="form-group">
<label for="pwd">密碼</label>
<input type="password" id="pwd" class="form-control">
</div>
<div class="form-group">
<label for="pwd">驗證碼</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="valid_code form-control">
</div>
<div class="col-md-6">
{# src還可以設置請求路徑 #}
<img width="270" height="40" src="/get_validCode_img/" alt="">
</div>
</div>
</div>
<input type="button" class="btn btn-default login-btn pull-right" value="提交">
</form>
</div>
</div>
</div>
注意:<img src="">,src除了可以指定圖片路徑還是設置為請求路徑。
2、驗證碼路由
path('get_validCode_img/', views.get_validCode_img),
三、驗證碼視圖函數
1、方式一:讀取靜態圖片文件
def get_validCode_img(request):
with open("lufei.jpg", "rb") as f:
data = f.read()
顯示效果如下:

不推薦使用這種方法,驗證圖片不能僅僅指定一張圖片,這種方法把程序寫死了。
2、方式二:pillow生成動態圖片
生成動態隨機圖片,使用Python圖像處理庫:Pillow
安裝pillow庫:pip3 install pillow
引入Pillow中最重要的類Image,該類存在於同名的模塊中。可以通過以下幾種方式實例化:從文件中讀取圖片,處理其他圖片得到,或者直接創建一個圖片。
import random
def get_validCode_img(request):
def get_random_color():
return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
# 方式二:pip3 install pillow
from PIL import Image
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
with open("validCode.png", "wb") as f:
img.save(f, "png") # 保存動態生成的圖片
with open("validCode.png", "rb") as f:
data = f.read()
return HttpResponse(data)
運行效果如下:

每次刷新,隨機圖片的顏色會發生隨機變換。但是這種方式是請求進來時,先把數據加載到磁盤上,再在磁盤把數據讀出來返還給瀏覽器,而且磁盤的處理數據時非常慢的,因此應該交到內存中管理。
3、方式三:使用pillow生成動態頁面的基礎上,引入BytesIO將圖片保存在內存中
import random
def get_validCode_img(request):
def get_random_color():
return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
# 方式三:要引入BytesIO
from PIL import Image
from io import BytesIO
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
# f為內存句柄
f= BytesIO() # 會自己處理內存回收
# 保存圖片
img.save(f, "png")
data = f.getvalue()
return HttpResponse(data)
4、方式四:添加驗證碼文字信息
ImageDraw模塊提供了圖像對象的簡單2D繪制。用戶可以使用這個模塊創建新的圖像,注釋或潤飾已存在圖像,為web應用實時產生各種圖形。
draw.text() 寫文字
參數: xy:坐標 text:文本內容 fill:文本顏色 font:文本樣式
draw.line() 畫線
draw.point() 畫點
ImageFont 模塊中,可以使用 load() 函數加載一個 bitmap 字體,使用 truetype(fontfile, fontsize) 函數加載一個 OpenType/TrueType 字體(注意,這個函數需要額外安裝_imageingft模塊)。
在static目錄下創建font子目錄,給用戶存放字體文件。下載字體到該目錄./cnblog/static/font/下。
import random
def get_validCode_img(request):
# 隨機顏色
def get_random_color():
return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
# 方式四:
from PIL import Image, ImageDraw,ImageFont
from io import BytesIO
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
# 創建Draw對象
draw = ImageDraw.Draw(img)
# 創建Font對象
kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=20)
draw.text((0,5), "python", get_random_color(), font=kumo_font)
f = BytesIO() # f為內存句柄
img.save(f, "png")
data = f.getvalue()
return HttpResponse(data)
顯示效果如下所示:

5、生成隨機字符串驗證碼
隨機生成大寫字母、小寫字母、數字。
針對隨機字母需要用到chr()方法,用一個范圍在 range(256)內的(就是0~255)整數作參數,返回一個對應的字符(當前整數對應的ascii字符)。
# 隨機字母: """ >>> chr(65) 'A' >>> chr(90) 'Z' >>> chr(97) 'a' >>> chr(122) 'z' """
另外還需要用到choice() 方法,該方法可返回一個列表,元組或字符串的隨機項。choice()是不能直接訪問的,需要導入 random 模塊,然后通過 random 靜態對象調用該方法。
import random
def get_validCode_img(request):
# 隨機顏色
def get_random_color():
return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
# 方式五:修改為隨機字符串
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
# 創建Draw對象
draw = ImageDraw.Draw(img)
# 創建Font對象
kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
for i in range(5):
random_num = str(random.randint(0, 9)) # 隨機數字
random_low_alpha = chr(random.randint(95, 122)) # 隨機小寫字母
random_upper_alpha = chr(random.randint(65, 90)) # 隨機大寫字母
# 三選一:choice() 方法返回一個列表,元組或字符串的隨機項。
random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
draw.text((i*50+20, 5), random_char, get_random_color(), font=kumo_font) # 坐標錯開間距
f = BytesIO() # f為內存句柄
img.save(f, "png")
data = f.getvalue()
return HttpResponse(data)
顯示效果如下所示:

四、驗證碼圖片的噪點和噪線
添加圖片噪點和噪線的代碼:
# 給驗證碼圖片添加噪點噪線
width = 270
height = 40
for i in range(10):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill = get_random_color()) # 畫出一條線
for i in range(50):
draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) # 畫點
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
可以任意調配噪線和噪點的數量,盡量保證機器無法識別,但人可以識別。
Draw 類提供了 arc(xy, start, end, options) 函數來繪制弧線,參數解析如下所示:
xy 是個長度為4的列表,用來表示一個 bounding box(邊界區域)。如[x0, y0, x1, y1],分別表示 弧線最左側距離左邊、弧線最頂點距離上邊、弧線最右側距離左邊、弧線最低點距離上邊的距離。
start 和 end 則是弧的起止角度,單位是 °。其中水平向右的方向為 0°,豎直向下的方向為 90°,水平向左的方向為 180°,豎直向上的方向為 270°。
options 中可用選項:
fill = (R, G, B) :指定線條顏色
驗證碼視圖修改如下:
import random
def get_validCode_img(request):
# 隨機顏色
def get_random_color():
return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
# 創建Draw對象
draw = ImageDraw.Draw(img)
# 創建Font對象
kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
for i in range(5):
random_num = str(random.randint(0, 9)) # 隨機數字
random_low_alpha = chr(random.randint(95, 122)) # 隨機小寫字母
random_upper_alpha = chr(random.randint(65, 90)) # 隨機大寫字母
# 三選一:choice() 方法返回一個列表,元組或字符串的隨機項。注意:choice()是不能直接訪問的,需要導入 random 模塊,然后通過 random 靜態對象調用該方法。
random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
draw.text((i*50+20, 5), random_char, get_random_color(), font=kumo_font) # 坐標錯開間距
# 給驗證碼圖片添加噪點噪線
width = 270
height = 40
for i in range(10):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill = get_random_color()) # 畫出一條線
for i in range(50):
draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) # 畫點
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
f = BytesIO() # f為內存句柄
img.save(f, "png")
data = f.getvalue()
return HttpResponse(data)
顯示效果如下所示:

五、驗證碼刷新功能
1、驗證碼圖片刷新原理
(1)給驗證碼圖片添加id屬性:id="valid_code_img"
<div class="form-group">
<label for="pwd">驗證碼</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="valid_code form-control">
</div>
<div class="col-md-6">
{# src還可以設置請求路徑 #}
<img width="270" height="40" id="valid_code_img" src="/get_validCode_img/" alt="">
</div>
</div>
</div>
(2)創建/static/js/目錄,添加jquery-3.3.1.js,在login.html中引入jquery:
<script src="/static/js/jquery-3.3.1.js"></script>
(3)在頁面控制台操作驗證碼圖片:

每次在$("#valid_code_img")[0].src后面添加一個“?”都會刷新驗證碼圖片。
2、實現點擊驗證碼,驗證碼刷新
<script src="/static/js/jquery-3.3.1.js"></script>
<script>
// 刷新驗證碼
$("#valid_code_img").click(function () {
$(this)[0].src+="?"
})
</script>
六、驗證驗證碼字符串
1、給btn綁定ajax事件:登錄驗證
// 登錄驗證
$(".login-btn").click(function () {
$.ajax({
url: "",
type: "post",
data: {
user: $("#user").val(),
pwd: $("#pwd").val(),
valid_code: $("#valid_code").val(),
// 自己組csrf鍵值
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), {# csrf_token的值 #}
},
success:function (data) {
console.log(data)
}
})
})
給后端提交用戶名密碼、驗證碼做校驗。
注意:發post請求一定要通過csrf校驗,因此要在form中找一個地方加入:
{% csrf_token %}
但是光加這個是不能通過校驗的,這里與發form請求不同,需要自己組csrf_token鍵值:

可以在這里看到鍵名:csrfmiddlewaretoken,利用鍵名組csrf鍵值對。
// 自己組csrf鍵值
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), {# csrf_token的值 #}
2、login視圖函數處理POST請求
from django.http import JsonResponse
def login(request):
if request.method == "POST":
response = {"user": None, "msg": None}
user = request.POST.get("user")
pwd = request.POST.get("pwd")
valid_code = request.POST.get("valid_code")
# 從session中取到值,一個瀏覽器存一份,不會發生相互干擾
valid_code_str = request.session.get("valid_code_str")
if valid_code.upper() == valid_code_str.upper(): # 添加upper()不區分大小寫
pass
else:
response['msg'] = "valid code error!"
return JsonResponse(response) # 字典放進去直接序列化,ajax拿到的就是 格式,不用反序列化了
return render(request, 'login.html')
(1)在校驗用戶名密碼前,要先校驗驗證碼。
(2)注意這里做驗證碼校驗,但是驗證碼在另一個視圖函數中,要取到另一個函數的驗證碼,不能設置為全局變量,這樣不同人登錄時會互相干擾,校驗無法保證正常完成。
因為session本身就是一個會話跟蹤,能夠保存上一次做的行為、操作、數據,因此利用它能完成驗證碼驗證。
(3)在get_validcode_img視圖函數中需要添加如下代碼:
request.session["valid_code_str"] = valid_code_str
"""驗證碼生成過程
1 生成一個隨機字符串
2 設置一個COOKIE,{"sessionid":"剛剛生成的隨機字符串"}
3 django-session表中存儲 session-key session-data
隨機字符串 {"valid_code_str": "隨機驗證碼字符"}
"""
(4)另外由於驗證碼校驗是不區分大小寫的,在login中校驗驗證碼時,添加upper()方法:
if valid_code.upper() == valid_code_str.upper(): # 添加upper()不區分大小寫
(5)由於ajax一般都需要return 一個響應字符串,在這里引入JsonResponse:
from django.http import JsonResponse
字典放進去直接序列化,ajax拿到的就是對象,兩邊都不需要進行json的序列化與反序列化。

驗證碼驗證成功,django_session表保存瀏覽器對應ssession記錄:

七、登錄驗證
1、引入用戶認證組件auth模塊
from django.contrib import auth
2、在驗證碼驗證通過后,運用authenticate()方法完成用戶認證,即驗證用戶名以及密碼是否正確
user = auth.authenticate(username=user, password=pwd)
3、添加一個用戶,在控制台執行如下命令
$ python3 manage.py createsuperuser
4、驗證用戶信息無誤后,使用login函數給使用django的session框架給某個已認證的用戶附加上session id等信息
auth.login(request, user) # request.user:當前登錄對象
request.user是全局變量,在任何視圖和模板中可以直接使用。
5、運用ajax,在頁面顯示報錯信息
(1)修改提交按鈕的input標簽樣式,並在后面加span標簽
<input type="button" class="btn btn-default login-btn" value="提交"><span class="error"></span>
(2)編寫ajax請求回調函數:
// 登錄驗證
$(".login-btn").click(function () {
$.ajax({
url: "",
type: "post",
data: {
user: $("#user").val(),
pwd: $("#pwd").val(),
valid_code: $("#valid_code").val(),
// 自己組csrf鍵值
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), {# csrf_token的值 #}
},
success:function (data) {
console.log(data);
if (data.user){
// 如果有值:前端跳轉
location.href = "/index/"
} else {
// 如果沒值
$(".error").text(data.msg).css({"color": "red", "margin-left": "10px"})
}
}
})
})
注意:前端跳轉寫法和錯誤信息樣式修改方式。
(3)添加index路由和視圖
path('index/', views.index),
視圖:
def index(request):
return render(request, "index.html")
index模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>首頁{{ request.user.username }}</h3>
</body>
</html>
(4)效果圖如下所示:

八、登錄驗證優化
1、在登錄驗證錯誤時,顯示的錯誤提示一秒后自動消失
// 登錄驗證
$(".login-btn").click(function () {
$.ajax({
url: "",
type: "post",
data: {
user: $("#user").val(),
pwd: $("#pwd").val(),
valid_code: $("#valid_code").val(),
// 自己組csrf鍵值
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), {# csrf_token的值 #}
},
success:function (data) {
console.log(data);
if (data.user){
// 如果有值:前端跳轉
location.href = "/index/"
} else {
// 如果沒值
$(".error").text(data.msg).css({"color": "red", "margin-left": "10px"})
setTimeout(function () {
$(".error").text(""); // 一秒后清空錯誤提示
}, 1000)
}
}
})
})
這里主要是用到了javascript中的setTimeout()方法,用來設定一個時間, 時間到了, 就會執行一個指定的 method。
2、從視圖中分離出驗證碼功能代碼
驗證碼功能代碼非常多且邏輯復雜,將這一部分邏輯構建為一個模塊,視圖中調用這個模塊,實現程序解耦:
(1)創建./blog/utils/目錄,創建文件validCode.py,將驗證碼功能相關代碼拷入該文件中:
import random
def get_random_color():
# 隨機顏色
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
def get_valid_code_imge(request):
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
img = Image.new("RGB", (270, 40), color=get_random_color()) # 得到img對象,顏色三要素:紅綠藍
# 創建Draw對象
draw = ImageDraw.Draw(img)
# 創建Font對象
kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
valid_code_str = ""
for i in range(5):
random_num = str(random.randint(0, 9)) # 隨機數字
random_low_alpha = chr(random.randint(95, 122)) # 隨機小寫字母
random_upper_alpha = chr(random.randint(65, 90)) # 隨機大寫字母
# 三選一:choice() 方法返回一個列表,元組或字符串的隨機項。
random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
draw.text((i * 50 + 20, 5), random_char, get_random_color(), font=kumo_font) # 坐標錯開間距
# 保存驗證碼字符串
valid_code_str += random_char
# 給驗證碼圖片添加噪點噪線
# width = 270
# height = 40
# for i in range(10):
# x1 = random.randint(0, width)
# x2 = random.randint(0, width)
# y1 = random.randint(0, height)
# y2 = random.randint(0, height)
# draw.line((x1, y1, x2, y2), fill = get_random_color()) # 畫出一條線
#
# for i in range(50):
# draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) # 畫點
# x = random.randint(0, width)
# y = random.randint(0, height)
# draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
print("valid_code_str", valid_code_str) # valid_code_str Ms4v0
# 為什么用request.session:
# 因為session本身就是一個會話跟蹤,能夠保存上一次做的行為、操作、數據,因此利用它能完成驗證碼驗證
request.session["valid_code_str"] = valid_code_str
f = BytesIO() # f為內存句柄
img.save(f, "png")
data = f.getvalue()
return data
(2)在視圖函數中引入該模塊,實現驗證碼功能
def get_validCode_img(request):
"""
基於PIL模塊動態生成響應狀態碼圖片
:param request:
:return:
"""
from blog.utils.validCode import get_valid_code_imge
data = get_valid_code_imge(request)
return HttpResponse(data)
九、總結登錄驗證重點
1、一次請求伴隨了多次請求(伴隨了多個靜態文件的請求)
2、PIL模塊掌握,驗證碼
3、session存儲
4、驗證碼刷新
