05-Day04-注册功能实现02
一、注册功能实现
- 第一步 :点击收集数据 ajax 将数据发送到后端
- 第二步 :后端获取到用户注册信息后进行对每个字段的数据校验
- 第三步 :数据校验完成后写入数据库
1.1、用户注册提交ajax
- 将前端提交按钮根据ID进行事件绑定
- 将 form 标签定义一个 id;
- 使用
$('#Form标签').serialize(),
就可以获取到前端所有的标签的 name 及 value 都可以获取到以及csrf token
;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/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>
<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>
{{ 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 () {
bindClickBtnSubmit(); // 点击获取验证码事件
bindClickSubmit(); // 注册事件
});
/*
点击提交,注册
*/
function bindClickSubmit(){
$('#btnSubmit').click(function () {
$('.error-msg').empty();
// 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
// 数据ajax发送到后台
$.ajax({
url: "{% url 'register' %}",
type: "POST",
data: $('#regForm').serialize(), // 所有字段数据 + csrf token
dataType: "JSON",
success: function (res) {
if(res.status){
location.href = res.data; // 跳转到后端传入的 data 地址
}else{
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
/*
点击获取验证码的按钮 id=btnSms 绑定事件
*/
function bindClickBtnSubmit() {
$('#btnSms').click(function () {
// 清空上次的错误提示信息
$('.error-msg').empty();
// 如何获取用户输入的手机号? :找到输入框ID,根据ID获取值
// alert($('#id_mobile_phone').val());
var mobilePhone = $('#id_mobile_phone').val();
// 发送ajax请求,把手机号发送到后端
$.ajax({ // http://127.0.0.1:8000/index/?mobile_phone=mobilePhone&tpl=register
url: "{% url 'send_sms' %}", // 后端的地址,反向生成,直接使用 urls 中的name名称生成 url ,等价 /send/sms
type: "GET", // 方法
data: { mobile_phone: mobilePhone, tpl: "register" }, // data : mobile_phone=用户手机号,tpl=注册短信模板
dataType: "JSON", // 将服务端返回的数据反序列化为字典
success: function (res) { // success 是 ajax 请求发送成功之后,自动执行的函数 , res表示后端返回的值
// res_doct = JSON.parse(res);
if (res.status){
console.log('发送成功,倒计时')
sendSmsRemind(); // 启动定时器
} else {
// 错误信息
console.log(res); // {status: False, error:{ mobile_phone: ["错误信息"]}}
// $.each() 循环
$.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 %}
1.2、数据校验
- 校验还使用
ModelForm
中钩子函数
进行校验; form.cleaned_data
用于获取 Form 校验成功的字段
;form.errors
用于获取 Form 校验失败的字段
;
- 第一步 :用户名校验,先在 models 中为 username 用户名字段创建索引,提升查询速度;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/models.py
from django.db import models
# Create your models here.
class UserInfo(models.Model):
"""
CharField : 字符串类型字段
verbose_name : 字段注释
max_length : 字段长度
Django ORM 中也提供了一个 EmailField 但是本质上 还是 CharField
"""
username = models.CharField(verbose_name='用户名', max_length=32, 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)
- 第二步 :用户名钩子函数校验,不允许重名;
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
class RegisterModelForm(forms.ModelForm):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])
password = forms.CharField(label='密码',widget=forms.PasswordInput())
confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
code = forms.CharField(label='验证码', widget=forms.TextInput())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
def clean_username(self):
"""
用户名校验的钩子函数
"""
username = self.cleaned_data['username']
exists = models.UserInfo.objects.filter(username=username).exists()
if exists:
raise ValidationError('用户名存在')
return username
- 第三步 :邮箱校验钩子函数,不允许重复
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
class RegisterModelForm(forms.ModelForm):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])
password = forms.CharField(label='密码',widget=forms.PasswordInput())
confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
code = forms.CharField(label='验证码', widget=forms.TextInput())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
def clean_username(self):
"""
用户名校验的钩子函数
"""
username = self.cleaned_data['username']
exists = models.UserInfo.objects.filter(username=username).exists()
if exists:
raise ValidationError('用户名已存在')
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
- 第四步 :密码校验,长度限制(在Form校验时可指定),在钩子函数中进行密码和确认密码判断
Form 中的字段校验方式是从上到下的顺序;所以需要在 From 中调整此前定义的顺序,不然clean_confirm_password() 确认密码钩子函数中无法获取到在confirm_password字段之后校验的字段;
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
class RegisterModelForm(forms.ModelForm):
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='重复密码', widget=forms.PasswordInput())
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())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
...
...
def clean_confirm_password(self):
"""
密码和确认密码对比校验
"""
pwd = self.cleaned_data['password']
confirm_pwd = self.cleaned_data['confirm_password']
if pwd != confirm_pwd:
raise ValidationError('两次密码不一致')
return confirm_pwd
- 第五步 : 手机号校验钩子函数
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
class RegisterModelForm(forms.ModelForm):
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='重复密码', widget=forms.PasswordInput())
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())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
...
...
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
- 第六步 :验证码校验钩子函数,从redis读取对比是否正确,或是否超时
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
class RegisterModelForm(forms.ModelForm):
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='重复密码', widget=forms.PasswordInput())
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())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
...
...
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)
if not redis_code:
raise ValidationError('验证码失效或未发送,请重新发送')
redis_str_code = redis_code.decode('utf-8')
if code.strip() != redis_str_code:
raise ValidationError('验证码错误,请重新输入')
return code
1.3、成功入库并跳转
- 第一步 :对密码字段使用钩子函数进行密码加密
utils/encrypt.py MD5 加密工具 (将SECRET_KEY当做盐)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat utils/encrypt.py
# -*- coding:utf-8 -*-
import uuid
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()
def uid(string):
data = "{}-{}".format(str(uuid.uuid4()), string)
return md5(data)
- 第二步 :对密码字段使用钩子函数,并进行MD5加密,确认密码也同理
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
from utils import encrypt
class RegisterModelForm(forms.ModelForm):
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='重复密码', widget=forms.PasswordInput())
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())
# model 定义元数据
class Meta:
# 对应的Model类
model = models.UserInfo
# Model类中哪些字段可以展示,__all__ 表示所有
# fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
"""
重写RegisterModelForm 的 初始化方法
name 表示字段名称
field 表示forms.CharField对象
code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
"""
super(RegisterModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
...
...
def clean_password(self):
pwd = self.cleaned_data['password']
# 加密 & 返回
return encrypt.md5(pwd)
def clean_confirm_password(self):
pwd = self.cleaned_data.get('password')
confirm_pwd = encrypt.md5(self.cleaned_data['confirm_password'])
if pwd != confirm_pwd:
raise ValidationError('两次密码不一致')
return confirm_pwd
...
...
- 第三步 :校验完成后,返回跳转地址即登录地址
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py
"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from web_app.forms.account import RegisterModelForm,SendSmsForm
def register(request):
""" 注册 """
if request.method =='GET':
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
# POST
# form 数据校验
form = RegisterModelForm(data=request.POST)
# 若form 数据校验成功
if form.is_valid():
# print(form.cleaned_data) # form.cleaned_data 存放校验成功的数据
# 写入数据库也可以使用create方法,但是需要手动剔除不存在的字段
# from web_app import models
# data = form.cleaned_data
# data.pop('code')
# data.pop('confirm_password')
# instance = models.UserInfo.objects.create(**data)
# 验证通过,写入数据库 (数据库密码加密) ,通过save()方法进行保存数据库,还可以将那些models中不存在的字段会自动剔除
instance = form.save()
return JsonResponse({'status': True, 'data': '/login/'})
return JsonResponse({'status': False, 'error': form.errors}) # 如果校验未通过则返回 Form 校验的错误信息
- 前端根据后端转入的 data 字段,进行跳转;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/register.html
...
...
/*
点击提交,注册
*/
function bindClickSubmit(){
$('#btnSubmit').click(function () {
// 清空上次的错误提示信息
$('.error-msg').empty();
// 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
// 数据ajax发送到后台
$.ajax({
url: "{% url 'register' %}",
type: "POST",
data: $('#regForm').serialize(), // 所有字段数据 + csrf token
dataType: "JSON",
success: function (res) {
if(res.status){
location.href = res.data; // 根据后端返回的 data 进行跳转
}else{
$.each(res.error, function (key, value) { // 错误信息
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
...
...
向往的地方很远,喜欢的东西很贵,这就是我努力的目标。