登录,生成随机图片验证码
一、登录 - 随机生成图片验证码
1、随机生成验证码
Python随机生成图片验证码,需要使用PIL模块,安装方式如下:
pip3 install pillow
1)创建图片
from PIL import Image img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) with open('code.png', 'wb') as f: # 保存在本地(即写入硬盘) img.save(f, format='png')
参数说明:
mode='RGB' 表示以RGB来表示颜色
size=(120,30) 表示坐标
color=(255, 255, 255) 表示白色
此时,打开启动文件所在目录,里面就有了一个宽120,高30的白色code.png图片。
2)创建画笔(用于在图片上画任意内容)
from PIL import Image, ImageDraw
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 创建画笔对象draw img.show() # 在图片查看器中打开,这句会调用系统默认的图片管理工具
3)画点 - point()方法
from PIL import Image, ImageDraw
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # point()第一个参数:表示坐标, 第二个参数:表示颜色 draw.point([100, 20], fill='red') draw.point([60, 10], fill=(0, 255, 0)) # 保存在本地 with open('code.png', 'wb') as f: img.save(f, format='png')
效果如下图:
4)画线 - line()方法
from PIL import Image, ImageDraw
img = Image.new(mode='RGB', size=(540, 150), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # line()第一个参数:表示起始坐标和结束坐标,第二个参数:表示颜色 draw.line((50, 50, 50, 150), fill='red') # 上面一句表示画一条坐标(x=100,y=100)到(x=100,y=300)的直线 draw.line((50, 100, 150, 50), fill=(120, 120, 120)) draw.line((50, 50, 150, 50), fill=(0, 255, 255)) with open('code.png', 'wb') as f: img.save(f, format='png')
效果如下:
5)画圆 - arc()方法
from PIL import Image, ImageDraw
img = Image.new(mode='RGB', size=(500, 140), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一个参数:表示起始坐标和结束坐标(圆要画在其中间,两点确定的矩形的内切圆) # 第二个参数:表示开始角度 # 第三个参数:表示结束角度 # 第四个参数:表示颜色 draw.arc((200, 20, 300, 120), 0, 360, fill='red') with open('code.png', 'wb') as f: img.save(f, format='png')
效果如下:
6)写文本 - text()方法
from PIL import Image, ImageDraw
img = Image.new(mode='RGB', size=(80, 20), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一个参数:表示起始坐标,第二个参数:表示写入的文本,第三个参数:表示颜色 draw.text([0, 0], 'python', 'red') with open('code.png', 'wb') as f: img.save(f, format='png')
效果如下:
7)特殊字体文字(下载好引用的字体文件)
from PIL import Image, ImageDraw, ImageFont
img = Image.new(mode='RGB', size=(120, 40), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') font = ImageFont.truetype('kumo.ttf', 28) # 第一个参数:表示字体文件路径 # 第二个参数:表示字体大小 draw.text((0, 0), 'python', 'red', font=font) # 第一个参数:表示起始坐标 # 第二个参数:表示写入内容 # 第三个参数:表示颜色 # 第四个参数:表示字体 with open('code.png', 'wb') as f: img.save(f, format='png')
效果如下:
8)随机生成图片验证码
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='../static/font/kumo.ttf', font_size=28): code = [] img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') def rndChar(): """ 生成随机字符(包括大小写字母和数字) :return: """ ranNum = str(random.randint(0, 9)) ranLower = chr(random.randint(65, 90)) ranUpper = chr(random.randint(97, 120)) return random.choice([ranNum, ranLower, ranUpper]) def rndColor(): """ 生成随机颜色 :return: """ return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255)) # 写文字 font = ImageFont.truetype(font_file, font_size) for i in range(char_length): char = rndChar() code.append(char) h = ( height - font_size ) / 2 draw.text([i * width / char_length, h], char, font=font, fill=rndColor()) # 写干扰点 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) # 写干扰圆圈 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor()) # 画干扰线 for i in range(5): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=rndColor()) # 对图像加滤波 - 深度边缘增强滤波 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) return img, ''.join(code) if __name__ == '__main__': # 1. 直接打开,即用图片查看器查看 # img,code = check_code() # img.show() # 2. 写入文件 # img,code = check_code() # with open('code.png','wb') as f: # f是写入磁盘的文件句柄 # img.save(f, format='png') # data = f.read() # data是读取图片的字节 # 3. 写入内存(Python3) # img,code = check_code() # from io import BytesIO # 内存管理的模块 # stream = BytesIO() # stream是写入内存的文件句柄 # img.save(stream, 'png') # data = stream.getvalue() # 4. 写入内存(Python2) # img,code = check_code() # import StringIO # stream = StringIO.StringIO() # stream是写入内存的文件句柄 # img.save(stream, 'png') # data = stream.getvalue()
效果如下:
2、基于ajax实现登录的示例代码
1)urls.py中关于登录代码:
path('login/', views.login,), # 获取登录页面url path('get_identifyCode/', views.get_identifyCode,), # 获取验证码对应url
2)login.html核心代码:
<body> <div id="particles-js"> <div class="login"> <p class="login-top">登录</p> {% csrf_token %} <div class="login-center clearfix"> <label class="" for="user">用户名</label> <input type="text" id="user" placeholder="用户名" /> </div> <div class="login-center clearfix"> <label class="iconfont labelFS" for="pwd">密码</label> <input type="password" id="pwd" placeholder="密码" /> </div> <div class="login-center clearfix"> <label class="iconfont labelFS" for="validcode"></label> <input type="text" id="validcode" placeholder="验证码" /> <img src="/get_identifyCode/" alt="验证码" title="换一张" class="validImg" id="img" width="88" height="30" > </div> <a href="javascript:void(0);" class="login_btn">登录</a> <p class="error"></p> </div> </div> <script src="jquery.min.js"></script> <script> // ajax 登录 $(".login_btn").click(function () { $.ajax({ url:"", type:"post", // data发送urlencoded格式就行,数据没那么深,没必要发json格式 data:{ user:$("#user").val(), pwd:$("#pwd").val(), validcode:$("#validcode").val(), csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, success:function (response) { console.log(response); if(response.user){ // 登录成功 location.href="/index/" } else{ // 登录失败 $(".error").html(response.err_msg) } } }) }); // 验证码刷新:img标签有一个天然的发请求的模式,即src的路径后边拼接一个问号就会发一次请求,利用这一原理可以实现验证码刷新 $("#img").click(function () { this.src += "?" }); </script> </body>
3)views.py中获取随机验证码的视图函数代码(验证码保存利用session)
def get_identifyCode(request): img,code = check_code() # 利用上面的模块得到img对象和验证码code f = BytesIO() # 得到写入内存的文件句柄 img.save(f, "png") # 写入内存 data = f.getvalue() # 从内存中读出 # 将验证码存在各自的session中,这样做的好处是每个人都有自己的验证码,不会相互混淆(一定不能设为全局变量) request.session['keep_str'] = code return HttpResponse(data)
4)views.py中login视图函数代码
from django.contrib import auth def login(request): # if request.method == "POST": if request.is_ajax(): # 判断是否ajax请求 user = request.POST.get("user") pwd = request.POST.get("pwd") validcode = request.POST.get("validcode") # Ajax请求通常返回一个自己构建的字典 response={"user": None, "err_msg": ""} # request.session.get("keep_str")取出session中验证码与用户输入作判断 if validcode.upper() == request.session.get("keep_str").upper(): user_obj = auth.authenticate(username=user, password=pwd) print("user_obj", user_obj, bool(user_obj)) if user_obj: response["user"] = user auth.login(request, user_obj) # 保存用户状态 else: response['err_msg'] = "用户名或者密码错误!" else: response["err_msg"] = "验证码错误!" return JsonResponse(response) else: return render(request, "login.html")
二、基于ajax和forms组件实现注册示例
1)model.py
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): # 将原生auth_user表扩展一个tel手机号字段 tel=models.CharField(max_length=32)
2)forms.py(其实代码放在哪里没关系,最重要的是程序能找到,为了解耦,我们可以定义一个form.py)
from django import forms # exceptions中存着django的所有错误,错误在核心组件中 from django.core.exceptions import ValidationError from django.forms import widgets from app01.models import UserInfo class UserForm(forms.Form): # UserForm中定义需要校验的字段 username=forms.CharField(min_length=5, label="用户名") password=forms.CharField(min_length=5, widget=widgets.PasswordInput(), label="密码") r_pwd=forms.CharField(min_length=5, widget=widgets.PasswordInput(), label="确认密码") email=forms.EmailField(min_length=5, label="邮箱") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({'class': 'form-control'}) # 统一加class def clean_user(self): val=self.cleaned_data.get("username") user=UserInfo.objects.filter(username=val).first() if user: raise ValidationError("用户已存在!") else: return val def clean_pwd(self): val=self.cleaned_data.get("password") if val.isdigit(): raise ValidationError("密码不能是纯数字!") else: return val def clean_email(self): val = self.cleaned_data.get("email") if re.search("w+@163.com$", val): return val else: raise ValidationError("邮箱必须是163邮箱!") def clean(self): pwd=self.cleaned_data.get("password") r_pwd=self.cleaned_data.get("r_pwd") if pwd and r_pwd and r_pwd!=pwd: self.add_error("r_pwd", ValidationError("两次密码不一致!")) else: return self.cleaned_data
3)reg.html核心代码:
<body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <form action="" method=""> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="">{{ field.label }}</label> {{ field }} <span class="error"></span> </div> {% endfor %} <input type="button" class="btn btn-primary reg_btn" value="注册"> </form> </div> </div> </div> <script src="jquery.min.js"></script> <script> $(".reg_btn").click(function () { $.ajax({ url:"", type:"post", data:{ username:$("#id_username").val(), password:$("#id_password").val(), r_pwd:$("#id_r_pwd").val(), email:$("#id_email").val(), csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, success:function (res) { if (res.user){ // 注册成功 location.href="/login/" } else{ // 清除错误 $(".error").html(""); // 展示新的错误 $.each(res.err_msg,function (i,j) { $("#id_"+i).next().html(j[0]); }) } } }) }) </script> </body>
4)views.py中注册的视图函数reg
def reg(request): if request.method == "POST": form = UserInfo(request.POST) res = {"user": None, "err_msg": ""} if form.is_valid(): res["user"] = form.cleaned_data.get("username") del form.cleaned_data["r_pwd"] # 因表中无此字段,只需校验,不插入 UserInfo.objects.create_user(**form.cleaned_data) else: res["err_msg"] =form.errors return JsonResponse(res) else: # get请求 form = UserInfoModelForm() return render(request,"reg.html",{"form": form})
三、补充知识点
1、对原生auth_user表扩展字段(使用AbstractUser)
我们之前学习用户认证组件时,用的是django提供的auth_user表,即通过引入User对象(from django.contrib.auth.models import User)去操作它,我们又发现源码中User类继承了AbstractUser类,所以AbstractUser和User其实就是一张表,所以当我们想要有用户认证功能,又想要一些auth_user表中没有的字段时,可以按照如下这样做:
在models.py中,引入AbstractUser,并且自己定义一个用户类(表),这时类中只定义django的auth_user表中没有而你又想使用的字段即可,并且让你定义的类继承AbstractUser,如下:
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser): tel=models.CharField(max_length=32) # 扩展了一个手机号字段
注意:写完以上代码就直接去迁移数据库会报错“HINT: Add or change a related_name argument to the definition for ......”,我们需要在settings.py中加上下面这句话,来告诉Django我们要使用自己定义的表作为用户认证表(因此登录的使用方法不变,认证时django会自己去找这张表):
AUTH_USER_MODEL="app01.UserInfo"
这时再去进行数据库迁移,我们发现,数据库中没有auth_user表了,而我们自己定义的表中除了有自己定义的那些字段外,还有之前auth_user表中的所有字段,这就代表已经达到了我们的目的。
补充:通过命令创建超级用户的方式:
Tools -- > Run manage.py Task # 运行起来manage.py,再输入如下命令 manage.py@myproject > createsuperuser # 执行后根据提示输入用户名,密码,邮箱 # 注意:输入的密码会进行加密处理,再存入表中,并且命令输入密码要求最少8位
2、JsonResponse的使用
我们发现,一般浏览器发送Ajax请求给服务器时,都会返回一个字典,我们需要先将字典序列化,浏览器接收到后再进行反序列化,你会不会觉得这样做有点繁琐?其实,django为我们提供了一个JsonResponse类,它为我们做好了json的序列化,并且浏览器接收到之后,ajax也会自动为我们反序列化,即ajax中success函数接收到的response就是反序列化之后的数据,直接使用即可,如上面登录示例部分代码:
from django.http import JsonResponse # 引入JsonResponse def login(request): if request.is_ajax(): ...... response={"user":None, "err_msg": ""} ...... return JsonResponse(response)
分析原因:JsonResponse本质也继承了HttpResponse,而且既为我们做了序列化的操作,还将数据格式设置为json,ajax收到设置了json格式的数据也会为我们自动反序列化,也说明了不仅仅请求头中有content-type,响应头中也有,JsonResponse源码如下:
class JsonResponse(HttpResponse): def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs): if safe and not isinstance(data, dict): raise TypeError( 'In order to allow non-dict objects to be serialized set the ' 'safe parameter to False.' ) if json_dumps_params is None: json_dumps_params = {} kwargs.setdefault('content_type', 'application/json') data = json.dumps(data, cls=encoder, **json_dumps_params) super().__init__(content=data, **kwargs)
3、forms组件中对渲染出来的input输入框统一增加一个类名
我们在学习forms组件时,可以分别给每个字段设置一个类名,如class="form-control",但发现像之前那样写的有代码冗余的问题,按照如下方式写可以解决此问题:
from django import forms from django.forms import widgets class UserForm(forms.Form): user=forms.CharField(min_length=5, label="用户名") pwd=forms.CharField(min_length=5, widget=widgets.PasswordInput(), label="密码") r_pwd=forms.CharField(min_length=5, widget=widgets.PasswordInput(), label="确认密码") email=forms.EmailField(min_length=5, label="邮箱") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for filed in self.fields.values(): filed.widget.attrs.update({'class': 'form-control'})
4、关于全局钩子__all__的问题
我们知道全局钩子的错误信息都在__all__中,源码中是这样写的:
所以知道了这些,我们可以自己设置全局钩子的字段,避免跟其他字段规律不一致造成单独判断的问题,如下方式:
# 全局钩子:校验两次密码不一致 def clean(self): pwd=self.cleaned_data.get("pwd") r_pwd=self.cleaned_data.get("r_pwd") if pwd and r_pwd and r_pwd!=pwd: self.add_error("r_pwd", ValidationError("两次密码不一致!")) # 自己定义错误信息对应的字段是r_pwd else: return self.cleaned_data
5、关于具有提交功能的按钮问题
我们知道form表单是浏览器向服务器发请求的一种方式,提交按钮也有多种,但是要注意,具有提交功能的按钮有两种:<input type="submit" value="提交" />和<button>提交</button>,也就是说,当你想用form表单发请求时,可以用以上两种的任一种,但是当你想基于ajax发送请求时,若有form标签,则一定不要用以上两种提交按钮,否则当你点击按钮发送ajax时会自动以form表单的方式再发一次请求,使用<input type="button" value="提交" />是可以的,因为它没有提交form表单功能。
原文地址:https://www.cnblogs.com/li-li/p/9911603.html