轻量级bug管理平台——登录
2.短信登录
2.1展示页面(写路由、视图、模版)注:要往数据库里添加数据用ModelForm,例如注册,不用往数据库添加的话用Form更简洁。
2.2点击发送短信
2.3点击登录
3、用户名/密码登录
3.1 python生成图片+写文字
https://www.cnblogs.com/wupeiqi/articles/5812291.html
pip install pillow
3.2 Session&Cookie
3.3 页面显示‘
3.4 登录
2.1页面展示的代码:
新建url:
from django.urls import path from web.views import account urlpatterns = [ path('register/', account.register, name='register'),#反向解析后得到:register path('login/sms/', account.login_sms, name='login_sms'), #短信登录的方法 path('send/sms/', account.send_sms, name='send_sms'), ]
copy注册的页面展示模板,进行相应的修改:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户短信登录 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户短信登录</div>
<form id="smsForm" method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{# 上述代码在html模板中隐去,在ModelForm中进行样式form-control的编写#}
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg"></span>
</div>
<div class="col-xs-5">
<input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{{ field }}
<span class="error-msg"></span>
</div>
{% endif %}
{% endfor %}
<div class="row">
<div class="col-xs-3">
<input id="btnSubmit" type="button" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
</script>
{% endblock %}
在forms中新建一个bootstrap,py文件。文件中的代码如下:
#面向对象的继承,在form和ModelForm中进行样式的设置 class BootStrapForm(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): #循环字段进行样式的设置 field.widget.attrs['class'] = 'form-control' field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
在account,py中
from web.forms.bootstrap import BootStrapForm class LoginSMSForm(BootStrapForm, forms.Form): mobile_phone = forms.CharField( label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ] ) code = forms.CharField( label='验证码', widget=forms.TextInput() ) def clean_mobile_phone(self): mobile_phone = self.cleaned_data['mobile_phone'] exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() if not user_object: raise ValidationError('手机号不存在') return user_object def clean_code(self): code = self.cleaned_data['code'] mobile_phone = self.cleaned_data.get('mobile_phone') # 手机号不存在,则验证码无需再校验 if not mobile_phone: return code conn = get_redis_connection() redis_code = conn.get(mobile_phone) if not redis_code: raise ValidationError('验证码失效或未发送,请重新发送') redis_str_code = redis_code.decode('utf-8') if code.strip() != redis_str_code: raise ValidationError('验证码错误,请重新输入') return code
2.2点击发送短信
2.3点击登录
完整的登录成功代码:(最简便的方式)
在login_sms.html中:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户短信登录 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户短信登录</div>
<form id="smsForm" method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{# 上述代码在html模板中隐去,在ModelForm中进行样式form-control的编写#}
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg"></span>
</div>
<div class="col-xs-5">
<input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{{ field }}
<span class="error-msg"></span>
</div>
{% endif %}
{% endfor %}
<div class="row">
<div class="col-xs-3">
<input id="btnSubmit" type="button" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
//页面框架加载完成之后自动执行函数
$(function () {
bindClickBtnSms();
bindClickSubmit();
});
/*
点击登录
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
//收集表单中的数据 $('#regForm').serialize()//所有字段数据+csrf token
//数据ajax发送到后台
$.ajax({
url: "{% url 'login_sms' %}",
type: "POST",
data: $('#smsForm').serialize(),//所有字段数据+ csrf token
dataType: "JSON",
success: function (res) {
if (res.status) {
{#跳转页面#}
location.href = res.data;
} else {
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
点击获取验证码的按钮绑定事件
*/
function bindClickBtnSms() {
$('#btnSms').click(function () {
$('.error-msg').empty();
//获取用户输入的手机号
//找到输入框的ID,根据ID获取值,如何找到手机号的那个ID?
{#alert($('#id_mobile_phone').val());#}
var mobilePhone = $('#id_mobile_phone').val();
//向后台发送ajax请求,把手机号发送过去
$.ajax({
url: "{% url 'send_sms' %}",//等价于/send/sms/
type: "GET",
data: {mobile_phone: mobilePhone, tpl: "login"},
dataType: "JSON",//将服务端返回的数据反序列化为字典,因为前端拿到的是字符串,而其实后端返回的是字典
success: function (res) {
{#res_dict = JSON.parse(res) 再用HttResponse返回时要写#}
//ajax请求发送成功之后,自动执行(回调)的函数,res就是后端返回的值
if (res.status) {
sendSmsRemind();
} else {
//错误信息
console.log(res);//返回的res是字典:{status:False, error:{mobile_phone:["错误信息",],code:["错误信息”,]}}
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
倒计时
*/
function sendSmsRemind() {
var $smsBtn = $('btnSms');
$smsBtn.prop("disabled", true); //禁用
var time = 60;
var remind = setInterval(function () {
$smsBtn.val(time + '秒重新发送');
time = time - 1;
if (time < 1) {
clearInterval(remind);
$smsBtn.val('点击获取验证码').prop('disabled', false);
}
}, 1000
)
}
</script>
{% endblock %}
在 form\account.py中:
class SendSmsForm(forms.Form): mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ]) print("mobile_phone", mobile_phone) def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request # 验证手机号 def clean_mobile_phone(self): """手机号校验的钩子函数""" # 校验数据前,都需要获取到被校验的数据 mobile_phone = self.cleaned_data['mobile_phone'] # 获取用户提交的手机号 # 判断短信模版是否有问题 tpl = self.request.GET.get('tpl') mobile_phone = self.request.GET.get('mobile_phone') print(tpl) template_id = settings.TENCENT_SMS_TEMPLATE[tpl] if not template_id: # self.add_error('mobile_phone', '短信模版错误') raise ValidationError('短信模版错误') exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() if tpl == 'login': if not exists: raise ValidationError('手机不存在') else: # 验证数据库中是否已有手机号 if exists: raise ValidationError('手机号已存在') # 发短信 code = random.randrange(1000, 9999) # 生成随机验证码 # 发送短信 sms = send_sms_single(mobile_phone, template_id, [code, ]) if sms['result'] != 0: raise ValidationError("短信发送失败,{}".format(sms['errmsg'])) # 发送成功后,短信验证码写入redis(利用django-redis组件) conn = get_redis_connection() # redis的连接获取到 conn.set(mobile_phone, code, ex=60) # 设置key,value,超时时间为60 return mobile_phone class LoginSMSForm(BootStrapForm, forms.Form): mobile_phone = forms.CharField( label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ] ) code = forms.CharField( label='验证码', widget=forms.TextInput() ) def clean_mobile_phone(self): mobile_phone = self.cleaned_data['mobile_phone'] # 查询数据库里是都有手机号,并且返回用户对象,即当前用户的一整个列表 user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() if not user_object: raise ValidationError('手机号不存在') return user_object # 返回用户对象 def clean_code(self): code = self.cleaned_data['code'] user_object = self.cleaned_data.get('mobile_phone') # 手机号不存在,则验证码无需再校验 if not user_object: return code conn = get_redis_connection() redis_code = conn.get(user_object.mobile_phone) if not redis_code: raise ValidationError('验证码失效或未发送,请重新发送') redis_str_code = redis_code.decode('utf-8') if code.strip() != redis_str_code: raise ValidationError('验证码错误,请重新输入') return code
在view\account.py中
def login_sms(request): '''短信登录''' if request.method == 'GET': form = LoginSMSForm() return render(request, 'web/login_sms.html', {'form': form}) form = LoginSMSForm(request.POST) # POST方式接收数据,后进行表单验证 if form.is_valid(): # 用户输入正确,登录成功 user_object = form.cleaned_data['mobile_phone'] # 把用户名写入到session中 request.session['user_id'] = user_object.id request.session['user_name'] = user_object.username print(user_object.username) return JsonResponse({"status": True, 'data': "/index/"}) return JsonResponse({"status": False, 'error': form.errors})
另外一种写法:不够简便,但是很好理解:
在 form\account.py中:
class LoginSMSForm(BootStrapForm, forms.Form): mobile_phone = forms.CharField( label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ] ) code = forms.CharField( label='验证码', widget=forms.TextInput() ) def clean_mobile_phone(self): mobile_phone = self.cleaned_data['mobile_phone'] exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() # 查询数据库里是都有手机号 # user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() if not exists: raise ValidationError('手机号不存在') return mobile_phone # 返回用户对象 def clean_code(self): code = self.cleaned_data['code'] mobile_phone = self.cleaned_data.get('mobile_phone') # 手机号不存在,则验证码无需再校验 if not mobile_phone: return code conn = get_redis_connection() redis_code = conn.get(mobile_phone) if not redis_code: raise ValidationError('验证码失效或未发送,请重新发送') redis_str_code = redis_code.decode('utf-8') if code.strip() != redis_str_code: raise ValidationError('验证码错误,请重新输入') return code
在view\account.py中
def login_sms(request): '''短信登录''' if request.method == 'GET': form = LoginSMSForm() return render(request, 'web/login_sms.html', {'form': form}) form = LoginSMSForm(request.POST) # POST方式接收数据,后进行表单验证 if form.is_valid(): # 用户输入正确,登录成功 mobile_phone = form.cleaned_data['mobile_phone'] # 把用户名写入到session中 user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() request.session['user_id'] = user_object.id request.session['user_name'] = user_object.username print(user_object.username, user_object.email) return JsonResponse({"status": True, 'data': "/index/"}) return JsonResponse({"status": False, 'error': form.errors})
3、用户名/密码登录:

3.1 python自动生成图片+写文字(即随机验证码)
3.2 Session&Cookie
3.3页面显示
3.4登录
3.1 python自动生成图片+写文字(即随机验证码)
https://www.cnblogs.com/wupeiqi/articles/5812291.html
pip install pillow

from PIL import Image img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) # 在图片查看器中打开 # img.show() # 保存在本地 with open('code.png', 'wb') as f: img.save(f, format='png') # 创建白板写入code.png里去,后缀名是png
效果如下:

在image_code.py中:
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='Monaco.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: """ return chr(random.randint(65, 90)) # 数字转化为字符ASCII 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 = random.randint(0, 4) 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__': img_object, code = check_code() # 上面返回的是一个元组即图片对象和验证码 print(code) # 把图片内容写入本地文件 with open('code.png', 'wb') as f: img_object.save(f, format='png') # 创建白板写入code.png里去,后缀名是png # 把图片内容写到内存stream from io import BytesIO stream = BytesIO() img_object.save(stream, 'png') stream.getvalue() # 获取图片内容
效果:

3.2 Session&Cookie

注:默认django的服务端session存储数据的默认过期时间是两周,但是可以通过代码去修改过期时间。客户端(浏览器端)写cookie设定超时。
数据库表中对应的session数据:

图片验证码实现过程:

注:服务器给浏览器不是直接返回图片,而是返回一个Html模板。还有各种文字和img标签。浏览器再次向图片的src地址发送请求从而获得图片。
3.3页面展示
不用ajax请求,而通过forn表单进行提交。








为什么用session存储而不用redis 存储,因为如果用redis存储的话需要自己手动生成随机字符串并写到用户浏览器上,再把随机串当成key,code 当成value写到redis,并操作
Cooie,而Session恰好可以生成唯一的标识作为key,比较简单。
而之前发短信用redis,是因为唯一标识是用户填入的手机号,不需要自动生成唯一标识。所以可以用redis.





在session中获取验证码的写法之一,但是最好是用.get去获取,因为验证码在session 可能会过期,就不存在了,要用get去获取

其中一种用户和密码的写法:
在view\account.py中
def login(request): "“”用户名和密码登录""" if request.method == "Get": form = LoginForm(request) return render(request, 'web/login.html', {'form': form}) form = LoginForm(request, data=request.POST) if form.is_valid(): username = form.cleaned_data['username'] password = form.cleaned_data['password'] user_object = models.UserInfo.objects.filter(username=username, password=password).first() # (手机=username and pwd=pwd) or (邮箱=username and pwd=pwd) # # user_object = models.UserInfo.objects.filter(Q(email=username) | Q(mobile_phone=username)).filter( # password=password).first() if user_object: # 用户名密码正确 return redirect('index') form.add_error('username', '用户名或密码错误') return render(request, 'web/login.html', {'form': form}) # 页面展示
在forms\account.py中
class LoginForm(BootStrapForm, forms.Form): username = forms.CharField(label="用户名") password = forms.CharField(label="密码", widget=forms.PasswordInput(render_value=True)) code = forms.CharField(label="图片验证码") def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request def clean_password(self): pwd = self.cleaned_data['password'] # 加密&返回 return encrypt.md5(pwd) def clean_code(self): """钩子 校验图片验证码是否正确""" # 读取用户输入的验证码 code = self.cleaned_data['code'] # 去session获取自己的验证码 session_code = self.request.session.get('image_code') if not session_code: raise ValidationError('验证码已过期,请重新获取') if code.strip().upper() != session_code.strip().upper(): raise ValidationError('验证码输入错误') return code

全部注册与登录所用到的全部代码:
在basic.html中
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugin/font-awesome/css/fontawesome.min.css' %}">
<style>
.navbar-default {
border-radius: 0;
}
</style>
{% block css %} {% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'index' %}">Tracer平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">产品功能</a></li>
<li><a href="#">企业方案</a></li>
<li><a href="#">帮助文档</a></li>
<li><a href="#">价格</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="{% url 'login' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">武沛齐 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">管理中心</a></li>
<li role="separator" class="divider"></li>
<li><a href="#"> 退 出</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% block content %} {% endblock %}
<script src="{% static 'js/jquery-3.4.1.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script>
{% block js %} {% endblock %}
</body>
</html>
在index.html中:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %}首页 {% endblock %}
{% block css %}
<style>
img {
width: 100%;
}
</style>
{% endblock %}
{% block content %}
<div>
<img src="{% static 'img/index/index-1.jpg' %}">
<img src="{% static 'img/index/index-2.png' %}">
<img src="{% static 'img/index/index-3.png' %}">
<img src="{% static 'img/index/index-4.png' %}">
</div>
{% endblock %}
在login.html中:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户登录 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户登录</div>
<form method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{# 上述代码在html模板中隐去,在ModelForm中进行样式form-control的编写#}
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg">{{ field.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img src="{% url 'image_code' %}" id="imageCode" title="点击更换图片">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{{ field }}
<span class="error-msg">{{ field.errors.0 }}</span>
</div>
{% endif %}
{% endfor %}
<div style="float: right;">
<a href="{% url 'login_sms' %}"> 短信验证码登录?</a>
</div>
<div class=" row">
<div class="col-xs-3">
<input type="submit" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
$(function () {
$('#imageCode').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc + "?"); // /index/?name=123 相等于重新再发一次请求
})
})
</script>
{% endblock %}
在login_sms.html中:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户短信登录 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户短信登录</div>
<form id="smsForm" method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{# 上述代码在html模板中隐去,在ModelForm中进行样式form-control的编写#}
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg"></span>
</div>
<div class="col-xs-5">
<input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{{ field }}
<span class="error-msg"></span>
</div>
{% endif %}
{% endfor %}
<div style="float: right;">
<a href="{% url 'login' %}">用户名密码登录?</a>
</div>
<div class=" row">
<div class="col-xs-3">
<input id="btnSubmit" type="button" class="btn btn-primary" value="登 录"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
//页面框架加载完成之后自动执行函数
$(function () {
bindClickBtnSms();
bindClickSubmit();
});
/*
点击登录
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
//收集表单中的数据 $('#regForm').serialize()//所有字段数据+csrf token
//数据ajax发送到后台
$.ajax({
url: "{% url 'login_sms' %}",
type: "POST",
data: $('#smsForm').serialize(),//所有字段数据+ csrf token
dataType: "JSON",
success: function (res) {
if (res.status) {
{#跳转页面#}
location.href = res.data;
} else {
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
点击获取验证码的按钮绑定事件
*/
function bindClickBtnSms() {
$('#btnSms').click(function () {
$('.error-msg').empty();
//获取用户输入的手机号
//找到输入框的ID,根据ID获取值,如何找到手机号的那个ID?
{#alert($('#id_mobile_phone').val());#}
var mobilePhone = $('#id_mobile_phone').val();
//向后台发送ajax请求,把手机号发送过去
$.ajax({
url: "{% url 'send_sms' %}",//等价于/send/sms/
type: "GET",
data: {mobile_phone: mobilePhone, tpl: "login"},
dataType: "JSON",//将服务端返回的数据反序列化为字典,因为前端拿到的是字符串,而其实后端返回的是字典
success: function (res) {
{#res_dict = JSON.parse(res) 再用HttResponse返回时要写#}
//ajax请求发送成功之后,自动执行(回调)的函数,res就是后端返回的值
if (res.status) {
sendSmsRemind();
} else {
//错误信息
console.log(res);//返回的res是字典:{status:False, error:{mobile_phone:["错误信息",],code:["错误信息”,]}}
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
倒计时
*/
function sendSmsRemind() {
var $smsBtn = $('btnSms');
$smsBtn.prop("disabled", true); //禁用
var time = 60;
var remind = setInterval(function () {
$smsBtn.val(time + '秒重新发送');
time = time - 1;
if (time < 1) {
clearInterval(remind);
$smsBtn.val('点击获取验证码').prop('disabled', false);
}
}, 1000
)
}
</script>
{% endblock %}
在register.html中:
{% extends 'layout/basic.html' %}
{% load static %}
{% block title %} 用户注册 {% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/account.css' %}">
<style>
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<div class="title">用户注册</div>
<form id="regForm" method="POST" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.name == 'code' %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{# 上述代码在html模板中隐去,在ModelForm中进行样式form-control的编写#}
<div class="row">
<div class="col-xs-7">
{{ field }}
<span class="error-msg"></span>
</div>
<div class="col-xs-5">
<input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{# <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">#}
{{ field }}
<span class="error-msg"></span>
</div>
{% endif %}
{% endfor %}
<div class="row">
<div class="col-xs-3">
<input id="btnSubmit" type="button" class="btn btn-primary" value="注册"/>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
//页面框架加载完成之后自动执行函数
$(function () {
bindClickBtnSms();
bindClickSubmit();
});
/*
点击提交(注册)
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
//收集表单中的数据 $('#regForm').serialize()//所有字段数据+csrf token
//数据ajax发送到后台
$.ajax({
url: "{% url 'register' %}",
type: "POST",
data: $('#regForm').serialize(),//所有字段数据+ csrf token
dataType: "JSON",
success: function (res) {
if (res.status) {
{#跳转页面#}
location.href = res.data;
} else {
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
点击获取验证码的按钮绑定事件
*/
function bindClickBtnSms() {
$('#btnSms').click(function () {
$('.error-msg').empty();
//获取用户输入的手机号
//找到输入框的ID,根据ID获取值,如何找到手机号的那个ID?
{#alert($('#id_mobile_phone').val());#}
var mobilePhone = $('#id_mobile_phone').val();
//向后台发送ajax请求,把手机号发送过去
$.ajax({
url: "{% url 'send_sms' %}",//等价于/send/sms/
type: "GET",
data: {mobile_phone: mobilePhone, tpl: "register"},
dataType: "JSON",//将服务端返回的数据反序列化为字典,因为前端拿到的是字符串,而其实后端返回的是字典
success: function (res) {
{#res_dict = JSON.parse(res) 再用HttResponse返回时要写#}
//ajax请求发送成功之后,自动执行(回调)的函数,res就是后端返回的值
if (res.status) {
sendSmsRemind();
} else {
//错误信息
console.log(res);//返回的res是字典:{status:False, error:{mobile_phone:["错误信息",],code:["错误信息”,]}}
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
倒计时
*/
function sendSmsRemind() {
var $smsBtn = $('btnSms');
$smsBtn.prop("disabled", true); //禁用
var time = 60;
var remind = setInterval(function () {
$smsBtn.val(time + '秒重新发送');
time = time - 1;
if (time < 1) {
clearInterval(remind);
$smsBtn.val('点击获取验证码').prop('disabled', false);
}
}, 1000
)
}
</script>
{% endblock %}
在url.py中:
from django.urls import path from web.views import account from web.views import home urlpatterns = [ path('register/', account.register, name='register'), # 反向解析后得到:register path('login/sms/', account.login_sms, name='login_sms'), # 短信登录的方法 path('login/', account.login, name='login'), # 用用户名和密码登录的方法 path('image/code/', account.image_code, name='image_code'), path('send/sms/', account.send_sms, name='send_sms'), path('index/', home.index, name='index'), ]
在forms\account.py中:
import random import requests from django import forms from django.conf import settings from django_redis import get_redis_connection from app01 import models from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from web.forms.bootstrap import BootStrapForm from utils import encrypt from utils.tencent.sms import send_sms_single class RegisterModelForm(BootStrapForm, forms.ModelForm): # 重写规则,进行表单展示.增加正则表达式的校验 mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ]) password = forms.CharField( label='密码', min_length=8, max_length=64, error_messages={ 'min_length': "密码长度不能小于8个字符", 'max_length': "密码长度不能大于64个字符" }, widget=forms.PasswordInput()) confirm_password = forms.CharField( label='重复密码', min_length=8, max_length=64, error_messages={ 'min_length': "重复密码长度不能小于8个字符", 'max_length': "重复密码长度不能大于64个字符" }, widget=forms.PasswordInput()) code = forms.CharField( label='验证码', widget=forms.TextInput()) class Meta: model = models.UserInfo fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code'] # 利用钩子函数(用户名),对字段进行校验 def clean_username(self): username = self.cleaned_data['username'] exists = models.UserInfo.objects.filter(username=username).exists() if exists: raise ValidationError('用户名已存在') # 抛出异常后不会在返回值,所以cleaned_data中不会有username # self.add_error('username', '用户名已存在') # 把错误信息进行添加但仍然会返回username的值 return username # 利用钩子函数(邮箱),对字段进行校验 def clean_email(self): email = self.cleaned_data['email'] exists = models.UserInfo.objects.filter(email=email).exists() if exists: raise ValidationError('邮箱已存在') return email # 利用钩子函数(密码),对字段进行校验 def clean_password(self): pwd = self.cleaned_data['password'] # 加密&返回 return encrypt.md5(pwd) # 利用钩子函数(确认密码),对字段进行校验 def clean_confirm_password(self): # pwd = self.cleaned_data['password'] pwd = self.cleaned_data.get('password') # password是否校验通过,都可以拿到这个字段的值 confirm_pwd = encrypt.md5(self.cleaned_data['confirm_password']) # 密文与密文进行匹配 if pwd != confirm_pwd: raise ValidationError('两次密码不一致') return confirm_pwd # 利用钩子函数(手机号),对字段进行校验 def clean_mobile_phone(self): mobile_phone = self.cleaned_data['mobile_phone'] exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() if exists: raise ValidationError('手机号已注册') return mobile_phone # 利用钩子函数(验证码),对字段进行校验 def clean_code(self): code = self.cleaned_data['code'] # mobile_phone = self.cleaned_data['mobile_phone'] mobile_phone = self.cleaned_data.get('mobile_phone') if not mobile_phone: return code conn = get_redis_connection() redis_code = conn.get(mobile_phone) # 根据手机号获取redis 里的code if not redis_code: # redis里没有code raise ValidationError('验证码失效或未发送,请重新发送') redis_str_code = redis_code.decode('utf-8') # 字节变成字符串 if code.strip() != redis_str_code(): raise ValidationError('验证码错误,请重新输入') return code class SendSmsForm(forms.Form): mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ]) print("mobile_phone", mobile_phone) def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request # 验证手机号 def clean_mobile_phone(self): """手机号校验的钩子函数""" # 校验数据前,都需要获取到被校验的数据 mobile_phone = self.cleaned_data['mobile_phone'] # 获取用户提交的手机号 # 判断短信模版是否有问题 tpl = self.request.GET.get('tpl') mobile_phone = self.request.GET.get('mobile_phone') print(tpl) template_id = settings.TENCENT_SMS_TEMPLATE[tpl] if not template_id: # self.add_error('mobile_phone', '短信模版错误') raise ValidationError('短信模版错误') exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() if tpl == 'login': if not exists: raise ValidationError('手机不存在') else: # 验证数据库中是否已有手机号 if exists: raise ValidationError('手机号已存在') # 发短信 code = random.randrange(1000, 9999) # 生成随机验证码 # 发送短信 sms = send_sms_single(mobile_phone, template_id, [code, ]) if sms['result'] != 0: raise ValidationError("短信发送失败,{}".format(sms['errmsg'])) # 发送成功后,短信验证码写入redis(利用django-redis组件) conn = get_redis_connection() # redis的连接获取到 conn.set(mobile_phone, code, ex=60) # 设置key,value,超时时间为60 return mobile_phone class LoginSMSForm(BootStrapForm, forms.Form): mobile_phone = forms.CharField( label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ] ) code = forms.CharField( label='验证码', widget=forms.TextInput() ) def clean_mobile_phone(self): mobile_phone = self.cleaned_data['mobile_phone'] exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() # 查询数据库里是都有手机号 # user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() if not exists: raise ValidationError('手机号不存在') return mobile_phone # 返回用户对象 def clean_code(self): code = self.cleaned_data['code'] mobile_phone = self.cleaned_data.get('mobile_phone') # 手机号不存在,则验证码无需再校验 if not mobile_phone: return code conn = get_redis_connection() redis_code = conn.get(mobile_phone) if not redis_code: raise ValidationError('验证码失效或未发送,请重新发送') redis_str_code = redis_code.decode('utf-8') if code.strip() != redis_str_code: raise ValidationError('验证码错误,请重新输入') return code class LoginForm(BootStrapForm, forms.Form): username = forms.CharField(label="邮箱或手机号") password = forms.CharField(label="密码", widget=forms.PasswordInput(render_value=True)) code = forms.CharField(label="图片验证码") def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request def clean_password(self): pwd = self.cleaned_data['password'] # 加密&返回 return encrypt.md5(pwd) def clean_code(self): """钩子 校验图片验证码是否正确""" # 读取用户输入的验证码 code = self.cleaned_data['code'] # 去session获取自己的验证码 session_code = self.request.session.get('image_code') if not session_code: raise ValidationError('验证码已过期,请重新获取') if code.strip().upper() != session_code.strip().upper(): raise ValidationError('验证码输入错误') return code
在forms/bootstrap.py中
# 面向对象的继承,在form和ModelForm中进行样式的设置 class BootStrapForm(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): # 循环字段进行样式的设置 field.widget.attrs['class'] = 'form-control' field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
在views\account.py中
# 用户账户相关功能:注册、短信、登录、注销 from io import BytesIO from django.http import JsonResponse from django.shortcuts import render, HttpResponse, redirect from django.db.models import Q from web.forms.account import RegisterModelForm, SendSmsForm, LoginSMSForm, LoginForm from app01 import models from utils.image_code import check_code def register(request): # 注册 if request.method == 'GET': form = RegisterModelForm() return render(request, 'web/register.html', {'form': form}) # print(request.POST) # 后台拿到的数据,传到ModelForm进行校验 form = RegisterModelForm(data=request.POST) if form.is_valid(): # 验证通过后,写入数据库(密码要是密文) # form.instance.password ="iudasndfiajsd;fj" #在保存之前将instance中的password进行重置 form.save() # 自动剔除数据库中没有的字段 return JsonResponse({'status': True, 'data': '/login/'}) # 注册成功后进行前端跳转页面 # 写入数据库的另外一种写法 # data =form.cleaned_data # data.pop('code') # data.pop('confirm_password') # instance = models.UserInfo.objects.create(**data) # print(form.cleaned_data) # 校验成功,输出cleaned_data return JsonResponse({'status': False, 'error': form.errors}) def send_sms(request): # 发送短信 # print(request.GET) # mobile_phone = request.GET.get('mobile phone') # tpl = request.GET.get('tpl') # register/login 根据tpl取短信模版 # sms_template_id = settings.TENCENT_SMS_TEMPLATE9(tpl) form = SendSmsForm(request, data=request.GET) # 只是校验手机号:不能为空,格式是否正确 if form.is_valid(): # 判断在form中是否校验成功 # 验证通过后,发短信 # 写redis return JsonResponse({'status': True}) # 表示短信发送成功 return JsonResponse({'status': False, 'error': form.errors}) # 表示校验失败,所有错误信息都会放在form.errors中 # return HttpResponse('{"k1":123}') def login_sms(request): '''短信登录''' if request.method == 'GET': form = LoginSMSForm() return render(request, 'web/login_sms.html', {'form': form}) form = LoginSMSForm(request.POST) # POST方式接收数据,后进行表单验证 if form.is_valid(): # 用户输入正确,登录成功 mobile_phone = form.cleaned_data['mobile_phone'] # 把用户名写入到session中 user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first() request.session['user_id'] = user_object.id # request.session['user_name'] = user_object.user_name request.session.set_expiry(60 * 60 * 24 * 14) print(user_object.username, user_object.email) return JsonResponse({"status": True, 'data': "/index/"}) return JsonResponse({"status": False, 'error': form.errors}) def login(request): "“”用户名和密码登录""" if request.method == "Get": form = LoginForm(request) return render(request, 'web/login.html', {'form': form}) form = LoginForm(request, data=request.POST) if form.is_valid(): username = form.cleaned_data['username'] password = form.cleaned_data['password'] # user_object = models.UserInfo.objects.filter(username=username, password=password).first() # (手机=username and pwd=pwd) or (邮箱=username and pwd=pwd) user_object = models.UserInfo.objects.filter(Q(email=username) | Q(mobile_phone=username)).filter( password=password).first() # 利用Q构造复杂的查询条件 if user_object: # 登录成功为止1 request.session['user_id'] = user_object.id #用户信息保存在session中 request.session.set_expiry(60 * 60 * 24 * 14) #用户超时时间重新设定为两周 return redirect('index') form.add_error('username', '用户名或密码错误') return render(request, 'web/login.html', {'form': form}) # 页面展示 def image_code(request): """生成图片验证码""" image_object, code = check_code() # 把图片上的文本内容即code放入session中 request.session['image_code'] = code request.session.set_expiry(60) # 主动修改session的过期时间为60s # 图片写入内存 stream = BytesIO() image_object.save(stream, 'png') return HttpResponse(stream.getvalue()) # 图片写入本地 # with open('code.png', 'wb') as f: # image_object.save(f, format='png') # # with open('code.png', 'rb') as f: # data = f.read() # # return HttpResponse(data)
在home.py中
from django.shortcuts import render def index(request): return render(request, 'web/index.html')
在models.py中
from django.db import models class UserInfo(models.Model): username = models.CharField(verbose_name='用户名', max_length=32,db_index=True) #db_index=True 创建索引 email = models.EmailField(verbose_name='邮箱', max_length=32) mobile_phone = models.CharField(verbose_name='手机号', max_length=32) password = models.CharField(verbose_name='密码', max_length=32) def __str__(self): return self.username class Meta: db_table = "UserInfo" verbose_name_plural = '用户表'
在scripts/画图.py中:
from PIL import Image, ImageDraw # 创建白板 img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) # 写入文本 draw = ImageDraw.Draw(img, mode='RGB') # 第一个参数:表示起始坐标 # 第二个参数:表示写入内容 # 第三个参数:表示颜色 draw.text([0, 0], 'python', "red") # 在图片查看器中打开 # img.show() # 保存在本地 with open('code.png', 'wb') as f: img.save(f, format='png') # 创建白板写入code.png里去,后缀名是png
在utils/tencent/sms.py中
#!/usr/bin/env python # -*- coding:utf-8 -*- import ssl ssl._create_default_https_context = ssl._create_unverified_context from qcloudsms_py import SmsMultiSender, SmsSingleSender from qcloudsms_py.httpclient import HTTPError from django.conf import settings def send_sms_single(phone_num, template_id, template_param_list): """ 单条发送短信 :param phone_num: 手机号 :param template_id: 腾讯云短信模板ID :param template_param_list: 短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板 :return: """ appid = settings.TENCENT_SMS_APP_ID appkey = settings.TENCENT_SMS_APP_KEY # 自己应用Key sms_sign = settings.TENCENT_SMS_SIGN # 自己腾讯云创建签名时填写的签名内容(使用公众号的话这个值一般是公众号全称或简称) sender = SmsSingleSender(appid, appkey) try: response = sender.send_with_param(86, phone_num, template_id, template_param_list, sign=sms_sign) except HTTPError as e: response = {'result': 1000, 'errmsg': "网络异常发送失败"} return response def send_sms_multi(phone_num_list, template_id, param_list): """ 批量发送短信 :param phone_num_list:手机号列表 :param template_id:腾讯云短信模板ID :param param_list:短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板 :return: """ appid = settings.TENCENT_SMS_APP_ID appkey = settings.TENCENT_SMS_APP_KEY # 自己应用Key sms_sign = settings.TENCENT_SMS_SIGN sender = SmsMultiSender(appid, appkey) try: response = sender.send_with_param(86, phone_num_list, template_id, param_list, sign=sms_sign) except HTTPError as e: response = {'result': 1000, 'errmsg': "网络异常发送失败"} return response
在utils/encrypt.py中:
# MD5加密功能 import hashlib from django.conf import settings def md5(string): """"MD5加密""" hash_object = hashlib.md5(settings.SECRET_KEY.encode('utf-8')) hash_object.update(string.encode('utf-8')) return hash_object.hexdigest()
utils/image_code.py中:
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='utils/Monaco.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: """ return chr(random.randint(65, 90)) # 数字转化为字符ASCII 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 = random.randint(0, 4) 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__': img_object, code = check_code() # 上面返回的是一个元组即图片对象和验证码 print(code) # 把图片内容写入本地文件 with open('../scripts/code.png', 'wb') as f: img_object.save(f, format='png') # 创建白板写入code.png里去,后缀名是png # 把图片内容写到内存stream from io import BytesIO stream = BytesIO() img_object.save(stream, 'png') stream.getvalue() # 获取图片内容
总的路由分发:
bugmanagment/urls.py中:
from django.contrib import admin from django.urls import path, include # 主路由分发 urlpatterns = [ path('admin/', admin.site.urls), path('app01/', include('app01.urls'), name='app01'), # 路由分发 path('web/', include('web.urls')), # 不以app01为前缀的,都走这个web的url,没有加namespace默认为前缀为空. ]
setting配置:(激活应用等):
STATIC_URL = '/static/' STATICFILES_DIRS = os.path.join(BASE_DIR, 'static'), MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media") # ######## sms(模版) ######## # 腾讯云短信应用的 app_id TENCENT_SMS_APP_ID = 6666666666 # 腾讯云短信应用的 app_key TENCENT_SMS_APP_KEY = "66666666666666666666666" # 腾讯云短信签名内容 TENCENT_SMS_SIGN = "python之路" TENCENT_SMS_TEMPLATE = { 'register': 548760, 'login': 548762 } try: from .local_settings import * except ImportError: pass # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
以上为登录注册的所有代码,接下来来的内容为首页与登录注册之间的连接。
浙公网安备 33010602011771号