Django学习[26] 分页器,验证码,验证码绘制,验证
上一次我们学习到了分页器Paginator的部分属性,还有的属性可以去看源码。
分页器上次复习
django提供了分页的工具,存在于django.core中
Paginator :
数据分页工具
Page : 具体的某一页面
Paginator:
这里其实就是帮我们做好了封装,我们可以通过调用对象的属性来获取:
- 一共有多少页
- 如何获取某一页
对象创建: Paginator(数据集,每一页数据数)
属性:
count对象总数
num_pages:页面总数
page_range: 页码列表,从1开始
方法: page(整数): 获得一个page对象
分页器常见错误
方法: page(整数): 获得一个page对象
常见错误:
- InvalidPage:page()传递无效页码
- PageNotAnInteger:page()传递的不是整数,(比如1.1页,页数都为整数)
- Empty:page() :传递的值有效,但是没有数据
Page对象获得
对象获得,通过Paginator的page()方法获得
属性(详细参考上一次笔记)
- object_list: 当前页面上所有的数据对象
- number: 当前页的页码值
- paginator: 当前page关联的Paginator对象(这个用在复杂判断需要分页器判断的情况下,或者是存在多个分页器确认当前是哪个分页器)
方法(详细参考上一次笔记)
- has_next() :判断是否有下一页
- has_previous():判断是否有上一页
- has_other_pages():判断是否有上一页或下一页
- next_page_number():返回下一页的页码
- previous_page_number():返回上一页的页码
- len():返回当前页的数据的个数(count也可以)
验证码
也可以自己找库自动生成,不用自己写。
使用场景
- 用户注册登录
- 发现用户请求频率过高的时候,弹出验证码,让你证明你是一个人
在用户登录,注册以及一些敏感操作的时候,我们为了防止服务器被暴力请求,或爬虫爬取,我们可以使用验证码进行过滤,减轻服务器的压力。
只有当你正确输入验证码后,才能继续访问,否则ip就会被禁止访问网站。
验证码原生绘制
Django有专门生成验证码的库,但是这里我们先学习自己绘制验证码,以后想要调用第三方库也是比较简单的。
验证码绘制,说到底还是2D绘图,而2D绘图主要用到这些:坐标,画布,画图工具,一些参数,等等。
安装绘制验证码需要的库
验证码需要使用绘图 Pillow
安装指令
pip install Pillow
用到的库中API核心
- Image(画布)
- ImageDraw(画笔)
- ImageFont(画笔的扩充,原画笔比较单一)
实践:画验证码
静态验证码
新建url:
url('^getcode/', views.get_code, name='get_code'),
views:
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
color_bg = (255, 0, 0) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagedraw.text(xy=(0, 0), text='PeiQi') # 画文字
# 画好了之后,将我们的画的内容以二进制数据传递给前端
# 思路如下,将我们画好的内容以图片的形式保存在内存中,等验证码用完,自动消失
# 内存的IO流,这里之所以不用本地文件来保存,因为验证码保留在本地磁盘没有价值,相当于辣鸡
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue()) # 从内存流里将数据的值拿出来传递给response,返回给浏览器
测试发现,阿西吧
发现一串乱码,原因是浏览器将你的数据当作字符串来看,我们要指定浏览器打开的方式
解决方法是在HttpResponse中追加一个参数content_type='image.png'来控制浏览器以图片形式中的png形式打开
return HttpResponse(fp.getvalue(), content_type='image.png')
测试,ok.
接下来我们要调整验证码的字体和文字大小
我们可以从系统中或者其他地方把字体嫖出来,放到我们项目中的static文件夹中
首先新建静态资源文件夹
1. 建立static文件夹
1. settings.py注册
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
3. 将字体包复制粘贴到静态资源中:
4. 将字体路径在settings中注册
(这样我们在调用到字体文件的时候,使用的是相对路径,对比于绝对路径,我们在项目转移到其他服务器换了路径也不会出错,否则静态路径会报错)
FONT_PATH = os.path.join(BASE_DIR, 'static/fonts/ADOBEARABIC-BOLD.OTF')
5. 我们要在views函数中,构造字体
(11-12构造字体,13行添加字体的参数)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
color_bg = (255, 0, 0) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
# 其实ImageFont.load_path(filename)也可以构造字体,但是没有truetype更强,truetype可以设置size等等参数
imagedraw.text(xy=(0, 0), text='PeiQi', font=imagefont) # 画文字,并且用自己的字体
# 画好了之后,将我们的画的内容以二进制数据传递给前端
# 思路如下,将我们画好的内容以图片的形式保存在内存中,等验证码用完,自动消失
# 内存的IO流,这里之所以不用本地文件来保存,因为验证码保留在本地磁盘没有价值,相当于辣鸡
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
测试:
动态验证码
刚刚我们手动绘图了验证码,现在我们需要动态验证码,需要随机生成验证码
还是PeiQi但是需求是每个字母都是不同的颜色
这里我们用到一for循环,将字符串一个一个读取出来(12-14行)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
color_bg = (255, 0, 0) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = 'PeiQi'
for letter in verify_code: # 我们需要for循环,将我们的字符串一个一个画出来
imagedraw.text(xy=(0, 0), text=letter, font=imagefont) # 画文字,并且用自己的字体
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
测试,阿西吧
可以看到文字重叠了,原因是每次绘画字符的时候,坐标都相同,所以造成了重叠
接下来我们要改变坐标,每次画字的时候坐标都不一样,从而解决重叠问题
views:
(12-14行)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
color_bg = (255, 0, 0) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = 'PeiQi'
for i in range(5): # 我们需要for循环,将我们的字符串一个一个画出来
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont) # 画文字,并且用自己的字体
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
测试
随机背景颜色
现在我们要把背景颜色也随机一下
views:
(6-10行随机背景颜色,)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
red = get_color()
green = get_color()
blue = get_color()
color_bg = (red, green, blue) 利用元组,来存储RBG颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = 'PeiQi'
for i in range(5): # 我们需要for循环,将我们的字符串一个一个画出来
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont) # 画文字,并且用自己的字体
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
def get_color():
return random.randrange(256)
测试:多次刷新
随机文字颜色
下面我们要让文字也变成随机的颜色
我们在imagedraw.text中添加fill填充参数,可以控制文字的填充颜色
for i in range(5): # 我们需要for循环,将我们的字符串一个一个画出来
fill = (get_color(), get_color(), get_color())
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont, fill=fill)
干扰点
接着我们还要再加一点干扰,比如加很多点
我们可以利用imagedraw.point来进行画点
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
red = get_color()
green = get_color()
blue = get_color()
color_bg = (red, green, blue) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = 'PeiQi'
for i in range(5): # 我们需要for循环,将我们的字符串一个一个画出来
fill = (get_color(), get_color(), get_color())
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont, fill=fill) # 画文字,并且用自己的字体
for i in range(10000): # 加入干扰点,循环多少次代表画多少个点
fill = (get_color(), get_color(), get_color()) # 随机干扰点的颜色
xy = (random.randrange(201), random.randrange(100)) # 随机干扰点的坐标
imagedraw.point(xy=xy, fill=fill)
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
测试
类推你还可以画线,这里就不多介绍了
随机字符串
下面我们要把PeiQi这个字符串变成随机字符串,所以我们要写一个可以返回随机字符串的函数
views函数修改成:
(16行用写好的函数替换原来的peiqi,17行注意循环次数改为4次,否则会报错,因为我们生成随机字符串函数generate_code()固定生成4位)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
red = get_color()
green = get_color()
blue = get_color()
color_bg = (red, green, blue) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = generate_code()
for i in range(4): # 我们需要for循环,将我们的字符串一个一个画出来
fill = (get_color(), get_color(), get_color())
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont, fill=fill) # 画文字,并且用自己的字体
for i in range(10000): # 加入干扰点,循环多少次代表画多少个点
fill = (get_color(), get_color(), get_color()) # 随机干扰点的颜色
xy = (random.randrange(201), random.randrange(100)) # 随机干扰点的坐标
imagedraw.point(xy=xy, fill=fill)
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
def get_color():
return random.randrange(256)
def generate_code(): # 生成随机四位字符串
source = 'qwertyuiopasdfghjklzxcvbnm1234567890ZXCVBNMASDFGHJKLQWERTYUIOP' # 源字符串
code = ''
for i in range(4):
code += random.choice(source)
return code
但是这样我们将用到的函数直接写在视图函数内了,视图函数一般来说只是做视图处理的,我们应该专门创建一个工具类文件,然后将工具函数单独存放在工具类的文件里,做法如下:
我们在App项目下,新建utills.py文件,并且将两个工具函数移动进utills.py中,注意导入相关包,同样,在views中也要重新导入移动后的工具函数的包
测试
可以看到有那味儿了。
验证码还可以加入干扰线,随机每个字符的大小,还要一定程度的扭曲,旋转等等,这里就暂告一段落了。
在登录界面加入验证码
在我们之前写的login.html文件中,添加验证码
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="{% url 'App:login' %}" method="post">
<span>用户名</span><input type="text" name="username" placeholder="嘤嘤嘤">
<br>
<span>验证码</span><input type="text" name="verify_code" placeholder="请输入下方的验证码">
<br>
<img src="{% url 'App:get_code' %}" alt="">
<button>嘤嘤嘤嘤</button>
</form>
</body>
</html>
测试
但是我们还没有实现验证功能,如何判断输入的验证码是否正确?
在服务器中每次生成验证码的时候我们把验证码保存下来,等待客户端提交验证码的时候我们再去验证,所以我们需要将验证码绑定浏览器身份,我们可以用cookie或者session来做,但是一般不会使用cookie,因为cookie可以被客户端查找到,这样就能被爬虫直接获取,所以我们将验证码信息存在服务器,如果爬虫想要通过验证必须使用人工智能中的图片识别,或者是人工输入验证码,总之不能让爬虫舒服。
views:
login函数
@csrf_exempt
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
receive_code = request.POST.get('verify_code') # 获取客户端提交的验证码
store_code = request.session.get('verify_code') # 获取服务器生成的验证码
if receive_code != store_code: # 如果验证码不匹配
return redirect(reverse('App:login')) # 不匹配就重定向到login界面
return HttpResponse('登录成功')
get_code函数:(17行)
def get_code(request):
# 首先初始化画布
mode = 'RGB' # mode参数是指图像模式(由哪些颜色构成,常见使用RGB或者RGBA等等)
size = (200, 100) # size 指的是画布的大小,有长和宽,数据类型通常是元组或者列表
red = get_color()
green = get_color()
blue = get_color()
color_bg = (red, green, blue) # 默认的背景颜色
image = Image.new(mode=mode, size=size, color=color_bg) # new(mode, size, color=0)
# 初始化画笔
# 注意这里导包导的是from PIL.ImageDraw import ImageDraw
imagedraw = ImageDraw(image, mode=mode) # def __init__(self, im, mode=None)im指的将画笔绑定画布,
imagefont = ImageFont.truetype(settings.FONT_PATH, 100) # 构造字体,参数有font=None, size=10, index=0, encoding="",layout_engine=None)
verify_code = generate_code()
request.session['verify_code'] = verify_code # 将生成的验证码绑定浏览器,存储在session中
for i in range(4): # 我们需要for循环,将我们的字符串一个一个画出来
fill = (get_color(), get_color(), get_color())
imagedraw.text(xy=(45 * i, 0), text=verify_code[i], font=imagefont, fill=fill) # 画文字,并且用自己的字体
for i in range(10000): # 加入干扰点,循环多少次代表画多少个点
fill = (get_color(), get_color(), get_color()) # 随机干扰点的颜色
xy = (random.randrange(201), random.randrange(100)) # 随机干扰点的坐标
imagedraw.point(xy=xy, fill=fill)
fp = BytesIO() # 属于内存流
image.save(fp, 'png') # def save(self, fp, format=None, **params) fp是指文件流输出的位置, format指的是文件的格式,比如png,jpg
return HttpResponse(fp.getvalue(), content_type='image.png') # 从内存流里将数据的值拿出来传递给response
测试
输入验证码,点击嘤嘤嘤嘤
返回登录成功,假如验证码不匹配,则重定向到login
我们也可以改成大小写不区分:
if receive_code.lower() != store_code.lower(): # 如果验证码不匹配,统一转换成小写字母
但是,假如我们随机到识别不出的验证码,我们想点击验证码刷新,怎么做到?
用jQuery加点击事件
首先在login.html的head中导入jQuery
<script type='text/javascript' src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
然后要写js,首先要在static中新建js文件夹,接着新建login.js
然后在login.html最上方加入static,并且在head中导入刚刚的login.js路径
(1行和9行)
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<script type='text/javascript' src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript" src="{% static 'js/login.js' %}"></script>
由于浏览器的缓存功能,图片不会刷新,浏览器检测到路径发生改变的时候,才会检测到数据发生改变,而我们的路径没有发生改变,所以认为我们的图没有发生改变,我们只需要每次点击的时候,返回不同的路径,这样就能起到刷新的作用
js:
$(function () {
$('img').click(function () {
console.log('点到我了');
$(this).attr ('src', '/App/getcode/?t='+ Math.random());
})
})
测试:每次点击验证码都能刷新新的验证码
快捷键
- ctrl+p
- 快速查看函数或者方法需要哪些参数