Django使用PIL做验证码登入

做出来的效果如下图:
在这里插入图片描述
点击图片会自动刷新重新加载图片,但不会刷新页面.

urls.py配置
必须给验证码图片一个URL地址:

urlpatterns = [
    # 登入验证码图片
    path('get_valid_img', get_valid_img, name='get_valid_img'),
]

由于代码比较多,因此新建utils的py文件,用于存放验证码代码:

# 获取验证码
class GetValidImg(object):
    def __init__(self, width=160, height=40, code_count=4, font_size=40,
                 point_count=20, line_count=3, img_format='png'):
        """
        可以生成一个经过降噪后的随机验证码的图片
        :param width: 图片宽度 单位px
        :param height: 图片高度 单位px
        :param code_count: 验证码个数
        :param font_size: 字体大小
        :param point_count: 噪点个数
        :param line_count: 划线个数
        :param img_format: 图片格式
        :return 生成的图片的bytes类型的data

        FreeMono.ttf为字体文件,需要指定字体文件路径
        """
        self.width = width
        self.height = height
        self.code_count = code_count
        self.font_size = font_size
        self.point_count = point_count
        self.line_count = line_count
        self.img_format = img_format

    @staticmethod
    def get_random_color():
        """获取一个随机颜色(r,g,b)格式的"""
        c1 = random.randint(0, 255)
        c2 = random.randint(0, 255)
        c3 = random.randint(0, 255)
        return c1, c2, c3

    @staticmethod
    def get_random_str():
        """获取一个随机字符串,每个字符的颜色也是随机的"""
        random_num = str(random.randint(0, 9))
        random_low_alpha = chr(random.randint(97, 122))
        random_upper_alpha = chr(random.randint(65, 90))
        random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
        return random_char

    def get_valid_code_img(self):
        # 获取一个Image对象,参数分别是RGB模式。宽150,高30,随机颜色
        image = Image.new('RGB', (self.width, self.height), self.get_random_color())
        # 获取一个画笔对象,将图片对象传过去
        draw = ImageDraw.Draw(image)
        # 获取一个font字体对象参数是ttf的字体文件的目录,以及字体的大小
        # 这里不能使用相对路径,否则部署代码时会出错,所以要借助os库实现绝对路径
        import os
        base_dir = os.path.dirname(os.path.abspath(__file__))
        font_path = os.path.join(base_dir, 'FreeMono.ttf')
        font = ImageFont.truetype(font_path, size=self.font_size)
        # 保存随机字符,以供验证用户输入的验证码是否正确时使用
        get_valid_num = ""
        for i in range(self.code_count):
            # 循环5次,获取5个随机字符串
            random_char = self.get_random_str()
            # 在图片上一次写入得到的随机字符串,参数是:定位(宽度 高度定位),字符串,颜色,字体
            draw.text((3 + i * self.height, 3), random_char, self.get_random_color(), font=font)
            # 保存随机字符,以供验证用户输入的验证码是否正确时使用
            get_valid_num += random_char

        # 噪点噪线
        # 划线
        for i in range(self.line_count):
            x1 = random.randint(0, self.width)
            x2 = random.randint(0, self.width)
            y1 = random.randint(0, self.height)
            y2 = random.randint(0, self.height)
            draw.line((x1, y1, x2, y2), fill=self.get_random_color())

        # 画点
        for i in range(self.point_count):
            draw.point([random.randint(0, self.width), random.randint(0, self.height)], fill=self.get_random_color())
            x = random.randint(0, self.width)
            y = random.randint(0, self.height)
            draw.arc((x, y, x + 4, y + 4), 0, 90, fill=self.get_random_color())

        # 保存本地或内存加载二选一
        # 保存到硬盘,名为test.png格式为png的图片
        # image.save(open('test.png', 'wb'), 'png')

        # 不需要在硬盘上保存文件,直接在内存中加载就可以
        io_obj = BytesIO()
        # 将生成的图片数据保存在io对象中
        image.save(io_obj, "png")
        # 从io对象里面取上一步保存的数据
        img_data = io_obj.getvalue()
        return img_data, get_valid_num

views.py部分代码:

# 登入页面,以及登入账号密码检查,都在这个页面完成。
def login_user(request):
    # 如果是POST请求,做登入认证(POST请求为:用户直接在表单里提交账号密码)
    if request.method == 'POST':
        # 验证验证码是否正确
        # 使用session做验证码的验证方法
        # if request.session.get('get_valid_num') != request.POST['valid_num']:
        #     return render(request, 'login.html', {'error': '验证码输入有误'})
        # 使用Cookie做验证码的验证方法
        if request.get_signed_cookie('get_valid_num', salt='Bruce Lin') != request.POST['valid_num']:
            return render(request, 'login.html', {'error': '验证码输入有误'})

        username = request.POST['username']
        password = request.POST['password']
        # authenticate用来校验用户名和密码是否正确。如果校验成功,会返回用户对象,认证失败不会有任何错误提示,返回None。
        user = authenticate(request, username=username, password=password)
        if user is not None:
            # login(),让校验成功的用户,登入系统。同时产生一条session,过期时间默认为14天。
            login(request, user)

            # 超时后,cookie会被游览器删除,但django不会把超时的session在数据库里删除。
            # 这条代码,执行后,会删除数据库里超时的session,不建议在这里使用,建议作为任务去使用。
            request.session.clear_expired()
            # 设置session(同时对应的cookie)超时时间,按秒计算。0秒意味着游览器一关闭,session就失效。
            # 设置15秒,意味着15秒到期后,session马上失效,哪怕15秒期间一直在游览网页
            request.session.set_expiry(0)

            # 认证成功后,重定向页面
            # 使用reverse增加反向解析,否则这里要写成:return redirect('students/level'))
            # 通过测试,这里不用加reverse就可以直接做反向解析
            # return redirect('level')
            # 重定向字典next键对应的值url(在做@login_required时,这个字典已生成),如果值为空,则赋予students的值
            # students值为urls.py里的反向解析名称。
            # 后台打印request.GET.get('next'),输出为:<QueryDict: {'next': ['/students/score_rank']}>
            return redirect(request.GET.get('next', 'students'))

        # 如果authenticate校验失败,返回None,则匹配这里的else,提示账号或密码不对。
        else:
            return render(request, 'login.html', {'flag': '您输入的账号或密码不对,请检查。'})
    # 如果不是POST,是GET请求(即直接打开登入URL),返回login.html登入页面
    else:
        # 如果用户认证成功(返回True),直接重定向某个URL(通过GET方式打开URL,在认证成功的情况下,直接做重定向)
        if request.user.is_authenticated:
            return redirect('students')
        else:
            # 如果没有认证过,返回login.html页面
            return render(request, 'login.html')


# 验证码图片
def get_valid_img(request):
    img = GetValidImg()
    img_show, get_valid_num = img.get_valid_code_img()

    # 这里使用session或Cookie,目的是为了在login的时候,做校验验证码使用
    # 这里使用Cookie比session好,因为session会把数据写入数据库中
    # request.session['get_valid_num'] = get_valid_num
    # request.session.set_expiry(0)
    # return HttpResponse(img_show)

    # 使用Cookie,在安全性要求不高情况下,可以用这种办法.
    valid_code = HttpResponse(img_show)
    # 下面这种做法Cookie是以明文在客户端显示,建议使用set_signed_cookie
    # valid_code.set_cookie('get_valid_num', get_valid_num)

    # 使用set_signed_cookie,稍微安全一点.
    valid_code.set_signed_cookie('get_valid_num', get_valid_num, salt='Bruce Lin')
    return valid_code

login.html相关代码:

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .login{
            width: 300px;
            height: 40px;
        }
        #validNum{
            width: 140px;
            height: 40px;
        }
        .error{
            color: #ff0000;
        }
        /*验证码 输入框和验证码 对齐,input 和 img 实现对齐。*/
        #validNum, #img {
		    vertical-align: middle;
	    }
        #submit{
            width: 140px;
            height: 40px;
        }
    </style>
    <script type="text/javascript" src="{% static 'js/base.js' %}"></script>
</head>
<body>
<p>采用的是HTML的Form做登入</p>
<form method="post" action="#">
     {% csrf_token %}
    <!--账号或密码不对错误提示-->
    {% if flag %}
        <span class="error">{{ flag }}</span>
        <br>
    {% endif %}
    <input class="login" type="text" name="username" required="required" placeholder="用户名">
    <br>
    <input class="login" type="password" name="password" required="required" placeholder="密码">
    <br>

    <span>
        <input id="validNum" type="text" name="valid_num" required="required" placeholder="验证码,区分大小写">
    </span>

    <span>
        <a id="validCode" href="javascript:">
            <img id="img" src="{% url 'get_valid_img' %}" alt="暂无图片">
        </a>
     </span>

    <span class="error">{{ error }}</span>  <!--验证码不对,错误提示-->
    <br>
    <input id="submit" type="submit" value="提交">
</form>
</body>
</html>

js/base.js的相关代码:

window.onload = function () {
    // img标签比较特殊,可以做到点击图片,不刷新页面重新加载图片
    var validCode = document.getElementById('validCode');
    validCode.onclick = function () {
        var img = document.getElementById('img');
        img.src += "?";  // URL不变:浏览器不发请求;加上?号,get参数请求,向后台发请求
        return false;
    };
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值