本文正在参加「Python主题月」,详情查看 Python 创作季,秀出你的 Python 文章 – 2000元限定奖品等你赢
项目gitee地址 ? saas
本篇教程对应代码为 【注册验证码处理】提交,可通过对应分支查看
用户注册篇
首先,总体的思维导图如下:
1. 前期准备
1.1 腾讯云发送短信
- 项目中的登录 & 注册功能,需要使用手机号进行短信的接收,这里用到了腾讯云短信
- python 操作腾讯云短信详细教程可查看此篇博文:Python 操作腾讯云短信(sms)详细教程
1.2 redis
- redis 的下载安装以及python 操作redis 可查看此篇博文: redis 下载安装 & python 操作redis & django 连接redis
2. 注册页面展示
2.1 创建app
- 创建一个名为
web
的app,之后的代码都在这个app里面写python manage.py startapp web 复制代码
2.2 app注册
settings.py
文件中注册app,INSTALLED_APPS
添加自己刚才创建的app【默认应该是已经添加了,没有的话自己添加】一下INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'web.apps.WebConfig', ] 复制代码
2.3 母版准备
2.3.1 插件引入
- 在创建母版之前我们需要先引入 bootstrap、js 等插件,可以使用 cdn,也可以下载离线使用,这里我们将其下载下来,放置到
static
文件夹中,方便我们以后使用
离线文件可以自己去官网下载,也可以拿我这里已经下载好的,我将其放在网盘中,需要可自行下载,其中包含: js、bootstrap、font-awesome【图标】
链接:pan.baidu.com/s/1gQRN57Xg…
提取码:mnjl
解压密码: ruochen666
- 在
web
下创建一个用于存放静态文件的static
文件夹,然后再创建一个plugin
文件夹,用于存放工具类文件,然后将下载好的 js、bootstrap、font-awesome 放置到static
文件夹中,结构如下图
- 接下来我们要使用的时候就可以直接引入
static
文件夹下的文件
2.3.2 母版
为什么要用到母版?
前端页面中,注册和登录的页面基本相似,我们可以让这两个页面都继承自母版,做到代码重用
- 在
web
下创建一个templates
文件夹,在templates
文件夹下再创建一个layout
文件夹放我们的母版文件basic.html
- 结构如下
- `basic.html` 代码如下,其中的导航条样式可以直接从 [bootstrap官网组件](https://v3.bootcss.com/components/#navbar) 拿过来修改一下即可
```html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://juejin.cn/post/{% static '/plugin/bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="https://juejin.cn/post/{% static '/plugin/font-awesome/css/font-awesome.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="">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="">Link</a></li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="">Action</a></li>
<li><a href="">Another action</a></li>
<li><a href="">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="">Separated link</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<script src="https://juejin.cn/post/{% static 'js/jquery-3.4.1.min.js' %}"></script>
<script src="https://juejin.cn/post/{% static 'plugin/bootstrap/css/bootstrap.min.css' %}"></script>
{% block js %}{% endblock %}
</body>
</html>
```
复制代码
2.4 URL准备
MyDjango/MyDjango/urls.py
【我的项目名称为MyDjango
】"""MyDjango URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^/', include('web.urls')), ] 复制代码
- 在 web 文件夹下创建
urls.py
文件,用于管理该app 的路由(视图函数我们下面会写)# -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> urls @IDE :PyCharm @Author :ruochen @Date :2020/7/2 1:18 @Desc : ==================================================''' from django.conf.urls import url from web.views import account urlpatterns = [ url(r'^register/$', account.register, name='register'), # register ] 复制代码
2.5 模型准备【models.py】
- 用户注册时,要填写的数据有
- 用户名
- 邮箱
- 手机号
- 密码
- 在
web/models.py
文件中创建一个UserInfo
类,代码如下from django.db import models class UserInfo(models.Model): username = models.CharField(verbose_name='用户名', max_length=32) 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 复制代码
- 迁移数据库
python manage.py makemigrations python manage.py migrate 复制代码
2.5 视图函数
- 将web 下的
views.py
文件删除,创建一个views
文件夹,方便管理我们的视图,然后在views
文件夹下创建一个account.py
文件作为注册视图,代码如下:(RegisterModelForm
和register.html
后面会写)from django.shortcuts import render from web.forms.account import RegisterModelForm def register(request): form = RegisterModelForm() return render(request, 'register.html', {'form': form}) 复制代码
2.6 ModelForm【简单校验 & 样式添加】
- 前端页面要通过Form 表单循环生成数据,但是直接生成的话有点丑,而且数据也要先做一些基本的校验【例如手机号,钩子函数在后面校验表单时用到,这里先通过正则简单的校验一下手机号】
- 在
web
文件夹下创建一个forms
文件夹,forms
文件夹中创建account.py
文件,代码如下- 一: 对字段进行处理,例如手机号进行校验,密码为
PasswordInput
形式等 - 二:给每个字段添加
form-control
样式,前端页面显示比较美观一点 - 三:添加
code
【验证码】字段
# -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> account @IDE :PyCharm @Author :ruochen @Date :2020/7/2 12:37 @Desc : ==================================================''' from django import forms from django.core.validators import RegexValidator from web import models 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()) class Meta: model = models.UserInfo fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code'] 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'] = '请输入{}'.format(field.label,) 复制代码
- 一: 对字段进行处理,例如手机号进行校验,密码为
- 使用上述代码是没有任何问题的,但是,对于添加
form-control
属性,之后的代码中其他字段都要用到,每次使用for 循环添加很显然有些赘余,我们可以将其封装在一个类中,这样,需要添加样式的时候直接继承这个类就可以了。 - 修改如下,在
web/forms
下创建一个bootstrap.py
文件,代码如下:# -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> bootstrap @IDE :PyCharm @Author :ruochen @Date :2020/7/3 16:25 @Desc : ==================================================''' class BootStrapForm(object): bootstrap_class_exclude = [] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): if name in self.bootstrap_class_exclude: continue old_class = field.widget.attrs.get('class', '') field.widget.attrs['class'] = '{} form-control'.format(old_class) field.widget.attrs['placeholder'] = '请输入{}'.format(field.label,) 复制代码
- 然后
forms/account.py
文件修改为from django import forms from django.core.validators import RegexValidator from web import models from web.forms.bootstrap import BootStrapForm class RegisterModelForm(BootStrapForm, 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='重复密码', min_length=8, max_length=64, error_messages={ 'min_length': "重复密码长度不能小于8个字符", 'max_length': "重复密码长度不能大于64个字符" }, 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()) class Meta: model = models.UserInfo fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code'] 复制代码
2.7 前端页面
- 在
templates
文件夹下创建register.html
文件夹,让其继承自basic.html
- 前端页面对于字段的展示,直接循环展示
form
表单生成的数据即可{% extends 'layout/basic.html' %} {% load static %} {% block title %} 用户注册 {% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'css/account.css' %}"> {% endblock %} {% block content %} <div class="account"> <div class="title">用户注册</div> <form id="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> <div class="row"> <div class="col-xs-7"> {{ field }} <span class="error-msg">{{ field.errors.0 }}</span> </div> <div class="col-xs-5"> <input id="smsBtn" type="button" class="btn btn-default" value="点击获取验证码"/> </div> </div> </div> {% else %} <div class="form-group"> <label for="{{ field.id_for_label }}">{{ field.lable }}</label> {{ field }} <span class="error-msg">{{ field.errors.0 }}</span> </div> {% endif %} {% endfor %} <div class="row"> <div class="col-xs-3"> <input id="submit" type="button" class="btn btn-primary" value="注 册"/> </div> </div> </form> </div> {% endblock %} {% block js %} {% endblock %} 复制代码
3. 验证码获取
3.1 思路
- 给获取验证码按钮绑定事件,在前端页面中,用户点击获取验证码后,通过腾讯云短信向用户手机号发送验证码,并且在页面上显示60s倒计时,向后端发送ajax请求
- 后端进行手机号校验(判断手机号是否已经注册过)和短信模板的验证(腾讯云短信的一些凭证)
3.2 具体实现
3.2.1 前端代码
- 在
register.html
中添加js 代码,代码如下{% 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="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> <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="submit" type="button" class="btn btn-primary" value="注 册"/> </div> </div> </form> </div> {% endblock %} {% block js %} <script> // 页面框架加载完成之后自动执行函数 $(function () { bindClickBtnSms(); }); /* 点击获取验证码的按钮绑定事件 */ function bindClickBtnSms() { $('#btnSms').click(function () { $('.error-msg').empty(); // 获取用户输入的手机号 // 找到输入框的ID,根据ID获取值,如何找到手机号的ID? // Django ModelForm 默认生成字段ID为 “id_ + 字段名” 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) { // ajax请求发送成功之后,自动执行的函数: res就是后端返回的值 if(res.status) { sendSmsRemind(); } else { // 错误信息 // console.log(res); // {status: False, error: { mobile_phone: ["错误信息", ] } $.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 %} 复制代码
前端页面60s倒计时用到了定时器功能,如下
var obj = setInterval(function(){ // 创建定时器,此处就相当于每1秒执行一次function函数 console.log(123); }, 1000) clearInterval(obj); // 关闭定时器 复制代码
那么,对于60s的倒计时功能,我们就可以使用如下代码实现
var time = 60; var obj = setInterval(function(){ time = time - 1; if(time < 1) { clearInterval(obj); } }, 1000) 复制代码
- 其中有个
account.css
是自己写的css 样式,在web/static
文件夹下新建一个css
文件夹用于存放自己写的css 样式,然后新建一个account.css
文件,代码如下.account { width: 400px; margin-top: 30px; margin-left: auto; margin-right: auto; border: 1px solid #f0f0f0; padding: 10px 30px 30px 30px; -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05); box-shadow: 5px 10px 10px rgba(0, 0, 0, .05); } .account .title { font-size: 25px; font-weight: bold; text-align: center; } .account .form-group { margin-bottom: 20px; } 复制代码
3.2.2 后端代码
3.2.2.1 URL
- 首先,上面写到前端页面通过js 向后端发送ajax 请求,请求地址为
/send/sms/
,那么我们首先要添加一个url
,web/urls.py
中代码如下:from django.conf.urls import url from web.views import account urlpatterns = [ url(r'^register/$', account.register, name='register'), # register url(r'^send/sms/$', account.send_sms, name='send_sms'), # register ] 复制代码
3.2.2.2 视图函数
- 上面添加了路由,接下来是视图函数,在
web/views/account.py
文件中添加代码如下from django.shortcuts import render, HttpResponse from django.http import JsonResponse from web.forms.account import RegisterModelForm, SendSmsForm def register(request): """ 注册 """ form = RegisterModelForm() return render(request, 'register.html', {'form': form}) def send_sms(request): """ 发送短信 """ form = SendSmsForm(request, data=request.GET) # 只是校验手机号:不能为空、格式是否正确 if form.is_valid(): return JsonResponse({'status': True}) return JsonResponse({'status': False, 'error': form.errors}) 复制代码
3.2.2.3 配置文件
3.2.2.3.1 腾讯云短信配置文件
- 对于腾讯云短信的配置文件,我们应该将其放置在
local_settings.py
文件中(local_settings.py
文件的作用,我在上一篇文章中提到过),同时要在settings.py
文件中声明 local_settings.py
文件配置代码如下
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# --------- sms -----------
# 腾讯云短信应用的 app_id
TENCENT_SMS_APP_ID = '自己的app_id'
# 腾讯云短信应用的 app_key
TENCENT_SMS_APP_KEY = '自己的app_key'
# 腾讯云短信签名内容
TENCENT_SMS_SIGN = 'xxxx'
# 短信模板
TENCENT_SMS_TEMPLATE = {
'register': 'xxxx',
'login': 'xxxx',
}
复制代码
关于腾讯云短信的配置,可查看此篇文章:Python 操作腾讯云短信(sms)详细教程
- 这里的 app_id & app_key 就是文中提到的创建应用后的 AppID & AppKey
- 短信签名内容就是在创建签名后显示的那个内容,比如我这个是
小小猿若尘
,如下
- 短信模板就是创建了模板后对应的 ID
settings.py
文件中也要声明如下(settings.py
文件最后添加下面代码,赋值随便填,因为我们在最后导入了local_settings.py
文件,项目实际上使用的是local_settings.py
文件中的配置,这里写只是为了声明一下,因为我们的local_settings.py
文件是不会给别人的)
# --------- sms -----------
# 腾讯云短信应用的 app_id
TENCENT_SMS_APP_ID = 6666
# 腾讯云短信应用的 app_key
TENCENT_SMS_APP_KEY = '6666'
# 腾讯云短信签名内容
TENCENT_SMS_SIGN = 'xxxx'
# 短信模板
TENCENT_SMS_TEMPLATE = {
'register': 666666,
'login': 666666,
}
复制代码
3.2.2.3.2 redis 配置文件
- redis的具体操作可查看此篇文章: redis 下载安装 & python 操作redis & django 连接redis,这里用到的就是
django-redis
模块【记得安装】 - redis的配置放在
local_settings.py
文件中,代码如下:CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.1.6:6379", # 在终端中通过 [ipconfig] 命令查看 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": { "max_connections": 1000, "encoding": 'utf-8' }, "PASSWORD": "root" # 密码,上述文章有具体说明 } } 复制代码
3.2.2.4 SendSmsForm
- 在上面视图函数中我们通过
SendSmsForm
进行了校验,web/forms/account.py
文件中代码如下- 一:对手机号、短信模板进行了校验
- 二:利用 腾讯云短信 向用户发送短信
- 在项目目录下创建
utils
文件夹,存放我们的工具类,再创建一个tencent
文件夹,在文件夹下创建sms.py
文件, 如下:
sms.py
文件代码如下# -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> sms @IDE :PyCharm @Author :ruochen @Date :2020/6/21 15:57 @Desc : ==================================================''' 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 # 自己应用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 # 自己应用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 复制代码
- 在项目目录下创建
- 三: 将验证码存入redis数据库中,且超时时间为 60s【即60s后自动消失】,这样我们在提交表单的时候,就可以将用户输入的验证码同redis中存的进行比较,且如果时间超过 60s,用户就要重新获取验证码
- redis的操作可以查看此篇文章: redis 下载安装 & python 操作redis & django 连接redis
#-*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> account @IDE :PyCharm @Author :ruochen @Date :2020/7/2 12:37 @Desc : ==================================================''' import random from django import forms from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from django.conf import settings from django_redis import get_redis_connection from web import models from web.forms.bootstrap import BootStrapForm from utils.tencent.sms import send_sms_single class RegisterModelForm(BootStrapForm, 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='重复密码', min_length=8, max_length=64, error_messages={ 'min_length': "重复密码长度不能小于8个字符", 'max_length': "重复密码长度不能大于64个字符" }, 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()) class Meta: model = models.UserInfo fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code'] class SendSmsForm(forms.Form): mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ]) 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') template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl) if not template_id: # self.add_error('mobile_phone', '短信模板错误') raise ValidationError('短信模板错误') # 检验数据库中是否已有手机号 exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists() 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() conn.set(mobile_phone, code, ex=60) return mobile_phone 复制代码
4. 点击注册
4.1 前端: 获取数据 & 发送ajax请求
- 收集表单中的数据(找到每一个字段)
- 数据通过ajax发送到后台【POST请求】
register.html
文件中js 部分添加点击注册事件函数,代码如下(前面代码部分同上,只是在js 中添加了bindClickSubmit
函数,并让其在页面框架加载完成后自动执行)
ajax请求这里我没有再写一个URL,而是复用了
/register/
,只需要判断用户发的是哪种请求就可以
- 用户反正地址时发送的是
GET
请求,这时我们直接让其跳转到注册页面即可- 用户点击注册时,发送的是
POST
请求,这时我们进行表单验证 & 写入数据库等操作即可
{% block js %}
<script>
// 页面框架加载完成之后自动执行函数
$(function () {
bindClickBtnSms();
bindClickSubmit();
});
/*
点击提交(注册)
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
// 收集表单中的数据(找到每一个字段)
// 数据通过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?
// Django ModelForm 默认生成字段ID为 “id_ + 字段名”
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) {
// ajax请求发送成功之后,自动执行的函数: res就是后端返回的值
if (res.status) {
sendSmsRemind();
} else {
// 错误信息
// console.log(res); // {status: False, error: { mobile_phone: ["错误信息", ] }
$.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 %}
复制代码
4.2 后端
4.2.1 数据校验
- 校验如下:
- 用户名、邮箱、手机号在钩子函数中验证
- 密码通过md5加密后返回
- md5 加密 单独封装起来,在
utils
文件夹中添加encrypt.py
文件
- md5 加密 单独封装起来,在
– 代码如下
“`python
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)
```
- 验证码通过redis 根据手机号(键)获取值与用户输入的进行比较【注意存在过期时间】
复制代码
- 修改
forms/account.py
文件如下:(只修改了RegisterModelForm
类的代码,其余不变)
from utils import encrypt
class RegisterModelForm(BootStrapForm, 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='重复密码',
min_length=8,
max_length=64,
error_messages={
'min_length': "重复密码长度不能小于8个字符",
'max_length': "重复密码长度不能大于64个字符"
},
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())
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('用户名已存在')
# self.add_error('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')
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)
if not redis_code:
raise ValidationError('验证码失效或未发送,请重新发送')
redis_str_code = redis_code.decode('utf-8')
if code.strip() != redis_str_code:
raise ValidationError('验证码错误,请重新输入')
return code
复制代码
4.2.2 写入数据库
- 数据校验成功后,即可将在数据库中创建一条记录,跳转到
/login/
页面(登录页面下一篇博文具体介绍) web/views/account.py
文件中代码修改如下:(只修改了register
函数的内容,其余不变)
def register(request):
""" 注册 """
if request.method == 'GET':
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
form = RegisterModelForm(data=request.POST)
if form.is_valid():
# 验证通过,写入数据库(密码要是密文)
# data = form.cleaned_data
# data.pop('code')
# data.pop('confirm_password')
# instance = models.UserInfo.objects.create(**data)
# save() 等同于上述代码,会自动剔除数据库中没有的数据
# 用户表中新建了一条数据(注册)
form.save()
return JsonResponse({'status': True, 'data': '/login/'})
return JsonResponse({'status': False, 'error': form.errors})
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END