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

  - 快速查看函数或者方法需要哪些参数

Last modification:April 2nd, 2020 at 01:44 am
如果觉得我的文章对你有用,可以打赏一瓶汽水钱嗷~