Django登录(含随机生成图片验证码)注册实例
登录,生成随机图片验证码
一、登录 - 随机生成图片验证码
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


浙公网安备 33010602011771号