python学习-Django7、md5加密、验证码

实现“用户登录”的功能,并限制只有登录的用户才能投票

创建用户模型。之前我们讲解过如果通过Django的ORM实现从二维表到模型的转换(反向工
程),这次我们尝试把模型变成二维表(正向工程)。

class User(models.Model):
    """用户"""
    
    no = models.AutoField(primary_key=True,verbose_name='编号')
    username = models.CharField(max_length=20,unique=True,verbose_name='用户名')
    password = models.CharField(max_length=32,verbose_name='密码')
    tel = models.CharField(max_length=20,verbose_name='手机号').
    reg_date = models.DateTimeField(auto_now_add=True,verbose_name='注册时间')
    last_visit = models.DateTimeField(null=True,verbose_name='最后登陆时间')
    #用户密码要在数据库中需要保存为哈希摘要(签名,指纹)
    #Django框架自带哈希256函数 make_password()  check_password()

    class Meta:
    	managed = True
        db_table = 'tb_user'
        verbose_name = '用户'
        verbose_name_plural = '用户'

使用下面的命令生成迁移文件并执行迁移,将 User 模型直接变成关系型数据库中的二维
表 tb_user 。

python manage.py makemigrations polls  #生成迁移(执行完后,polls下会多出migrations文件夹)
python manage.py migrate polls         #执行迁移迁移managed = True的模型
#完成后在数据库查看下表(只可以迁移一次)
#如果迁移错误的话,在数据库 django_migrations表中找到刚才迁移的记录,删除即可再次迁移



#使用sql语句在数据库中添加两条用户数据测试
insert into `tb_user`
 (`username`, `password`, `tel`, `reg_date`)
values
 ('wangdachui', '1c63129ae9db9c60c3e8aa94d3e00495', '13122334455', now()),
 ('hellokitty', 'c6f8cf68e5f68b0aa4680e089ee4742c', '13890006789', now());
说明:上面创建的两个用户 wangdachui 和 hellokitty 密码分别是 1qaz2wsx 和 Abc123!! 。

在应用下增加一个名为 utils.py 的模块用来保存需要使用的工具函数。Python标准库中的
hashlib 模块封装了常用的哈希算法,包括:MD5、SHA1、SHA256等。下面是使用hashlib 中 的 md5 类将字符串处理成MD5摘要的函数如下所示。

import hashlib

def gen_md5_digest(content):
	return hashlib.md5(content.encode()).hexdigest()

进入登陆页面和注册页面

修改view.py可以可以在首页进入登录或者注册页面

def login(request: HttpResponse) -> HttpResponse:
    return render(request,'login.html')  


def register(request: HttpResponse) -> HttpResponse:
    return render(request,'register.html')





修改urls.py中数据,添加两个链接

path('login/',login),
path('register/',register)




修改subject.html(跳转注册和登陆的超链接)

 <div class="user">
     <a href="/login/">用户登录</a>          //改为url,点击后访问login视图函数
     <a href="/register/">快速注册</a>       //同上,可以进入注册页面 
 </div>
#同上方法修改login.html中最后返回首页和注册新用户的超链接
<div>
	<a href="/">返回首页</a>
    <a href="/register/">注册新用户</a>
</div>
#同上方法修改register.html中返回首页和返回登陆页的超链接
<div>
	<a href="/">返回首页</a>
   	<a href="/login/">返回登录</a>
</div>
#昨晚此步骤整个网站流程可以跑通

生成验证码的代码

在应用(polls)下创建fonts文件夹用于存放字体,在应用(polls)下创建captcha.py用于生成验证码
准备三个字体文件ttf格式(我准备了三个Action.ttf 、Arial.ttf 、 Gerorgia.ttf
"""
图片验证码
"""
import os
import random
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    """贝塞尔曲线"""

    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def make_bezier(self, n):
        """绘制贝塞尔曲线"""
        try:
            return self.beziers[n]
        except KeyError:
            combinations = pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha:
    """验证码"""

    def __init__(self, width, height, fonts=None, color=None):
        self._image = None
        self._fonts = fonts if fonts else \
            [os.path.join(os.path.dirname(__file__), 'fonts', font)
             for font in ['Arial.ttf', 'Georgia.ttf', 'Action.ttf']]
        self._color = color if color else random_color(0, 200, random.randint(220, 255))
        self._width, self._height = width, height

    @classmethod
    def instance(cls, width=200, height=75):
        prop_name = f'_instance_{width}_{height}'
        if not hasattr(cls, prop_name):
            setattr(cls, prop_name, cls(width, height))
        return getattr(cls, prop_name)

    def _background(self):
        """绘制背景"""
        Draw(self._image).rectangle([(0, 0), self._image.size],
                                    fill=random_color(230, 255))

    def _smooth(self):
        """平滑图像"""
        return self._image.filter(ImageFilter.SMOOTH)

    def _curve(self, width=4, number=6, color=None):
        """绘制曲线"""
        dx, height = self._image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = Bezier().make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(self._image).line(points, fill=color if color else self._color, width=width)

    def _noise(self, number=50, level=2, color=None):
        """绘制扰码"""
        width, height = self._image.size
        dx, dy = width / 10, height / 10
        width, height = width - dx, height - dy
        draw = Draw(self._image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)),
                      fill=color if color else self._color, width=level)

    def _text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        """绘制文本"""
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(self._image)
        char_images = []
        for c in captcha_text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = self._image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            self._image.paste(char_image,
                              (offset, int((height - c_height) / 2)),
                              mask)
            offset += int(c_width * squeeze_factor)

    @staticmethod
    def _warp(image, dx_factor=0.3, dy_factor=0.3):
        """图像扭曲"""
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        warp_image = Image.new(
            'RGB',
            (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
        warp_image.paste(image, (abs(x1), abs(y1)))
        width2, height2 = warp_image.size
        return warp_image.transform(
            (width, height),
            Image.QUAD,
            (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))

    @staticmethod
    def _offset(image, dx_factor=0.1, dy_factor=0.2):
        """图像偏移"""
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        offset_image = Image.new('RGB', (width + dx, height + dy))
        offset_image.paste(image, (dx, dy))
        return offset_image

    @staticmethod
    def _rotate(image, angle=25):
        """图像旋转"""
        return image.rotate(random.uniform(-angle, angle),
                            Image.BILINEAR, expand=1)

    def generate(self, captcha_text='', fmt='PNG'):
        """生成验证码(文字和图片)
        :param captcha_text: 验证码文字
        :param fmt: 生成的验证码图片格式
        :return: 验证码图片的二进制数据
        """
        self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
        self._background()
        self._text(captcha_text, self._fonts,
                   drawings=['_warp', '_rotate', '_offset'])
        self._curve()
        self._noise()
        self._smooth()
        image_bytes = BytesIO()
        self._image.save(image_bytes, format=fmt)
        return image_bytes.getvalue()


def pascal_row(n=0):
    """生成毕达哥拉斯三角形(杨辉三角)"""
    result = [1]
    x, numerator = 1, n
    for denominator in range(1, n // 2 + 1):
        x *= numerator
        x /= denominator
        result.append(x)
        numerator -= 1
    if n & 1 == 0:
        result.extend(reversed(result[:-1]))
    else:
        result.extend(reversed(result))
    return result


def random_color(start=0, end=255, opacity=255):
    """获得随机颜色"""
    red = random.randint(start, end)
    green = random.randint(start, end)
    blue = random.randint(start, end)
    if opacity is None:
        return red, green, blue
    return red, green, blue, opacity

目前项目的文件结构
在这里插入图片描述
在登陆页面和注册页面实现动态验证码

在views.py中添加验证码的视图函数
#生成验证码,需要导入这个类
from polls.captcha import Captcha
def get_captcha(request: HttpResponse) ->HttpResponse:
    image_data = Captcha.instance().generate('1Qaz')     #1Qaz为给出指定字符
    return HttpResponse(image_data,content_type='image/png')
  




在urls.py中添加请求验证码的url(注意需要导入这个视图函数
path('captcha/',get_captcha)
#完成此步可以访问 http://127.0.0.1:8001/captcha/  可以看到验证码





修改login.html中验证码
<div class="input">
                <label>验证码:</label>
                <input type="text" name="captcha">
                <img id = "code" src="/captcha/" alt="" width="150" height="40">     //会请求captcha/这个视图函数
</div>
#添加了id方便js代码拿到标签 ,修改了src





在utils.py中添加随机验证码生成的代码
ALL_CHARS = '0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
def gen_code(length=4):
    return ''.join(random.choices(ALL_CHARS,k=length))




修改views.py中的验证码视图函数
def get_captcha(request: HttpResponse) ->HttpResponse:
    code = gen_code()                             				  #默认返回四个随机字符
    image_data = Captcha.instance().generate(code)
    return HttpResponse(image_data,content_type='image/png')






增加点击验证码可以更换验证码的功能
#在login.html最后的body标签前增加js代码
</div>
<script>
    let codeImage = document.querySelector('#code')
    codeImage.addEventListener('click',(evt) => {
        evt.target.src = '/captcha/?' + Math.random()
    })
</script>
</body>
#使用jQuery来写js也可以
<!--
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
    $('#code').on('click',(evt) >= {
       $(evt.target).attr('src','/captcha/?' + Math.random())
</script>
-->

实现用户登陆

修改login.html中表单
<p class="hint">{{ hint }}</p>                      //方便输入账号密码错误时的提醒
<form action="/login/" method="post">               //enctype="multipart/form-data"    
	{% csrf_token %}                                //隐藏域,注册页面也需要加上
#给错误提醒的p标签加上css样式表,在style最后加上
        .hint{
        color: red;
        font-size: 12px;
    }

#表单用post请求/login/函数,如果表单中有文件需要加上注释的那段
	注意,在上面的表单中,我们使用了模板指令 {% csrf_token %} 为表单添加一个隐藏域(可
以在浏览器中显示网页源代码就可以看到这个指令生成的 type 属性为 hidden 的 input 标签),它
的作用是在表单中生成一个随机令牌(token)来防范跨站请求伪造(简称为CSRF),这也是
Django在提交表单时的硬性要求。如果我们的表单中没有这样的令牌,那么提交表单时,Django
框架会产生一个响应状态码为 403 的响应(禁止访问),除非我们设置了免除CSRF令牌。	




修改views.py中login.py函数
def login(request: HttpResponse) -> HttpResponse:
    hint = ''
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username and password:
            password = gen_md5_digest(password)   #需要导入之前写的md5加密模块
            user = User.objects.filter(username=username,password=password).first()
            if user:
                pass
            else:
                hint = '用户名或密码错误'
        else:
            hint = '请输入有效的用户名和密码'
    return render(request,'login.html',{'hint':hint})
    #做完此步可以测试登陆界面
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

像风一样的男人@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值