Django项目: 3.用户注册功能
本章内容的补充知识点
导入库的良好顺序:
1.系统库 2.django库 3.自己定义的库(第三方库)
redis缓存数据库的数据调用速度快,但是不利于长时间保存。
mysql用于长时间存储,但是调用比较慢。
session会话存储的内容(以字典的方式存放)放在redis缓存里面,要设置过期时间
用户注册功能
一、用户模型设计
1. 用户表字段分析
用户名
密码
手机号
邮箱
2.用户模型设计
django的强大之处在于开发效率高,内置了权限模块之类的很多常用功能。在开始一个新的django项目时,如果权限模块中的User模型不满足项目要求,我们需要扩展或者自定义User模型。
扩展User模型有两种方法:
如果你不需要改变数据库存储内容,只是改变行为,那么可以建立有一个基于User模型的代理模型。
如果想存储与User模型关联的信息,可以使用OneToOneField到包含其他信息字段的模型。这种one-to-one模型经常被称作Profile模型,因为它可能存储站点用户的非身份验证的相关信息。例如:
from django.contrib.auth.models import User class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)
自定义User模型:
如果不想使用django内置的权限系统,当然你需要自定义用户模型,这种情况不讨论。当然也不建议这么做,django内置权限系统有大的自定义功能扩展,而不是重复造轮子。
开启一个新项目,官方强烈推荐用户自定义用户模型,即是默认的用户模型目前已经足够,但是未来可能会要扩展。
from django.contrib.auth.models import AbstractUser class User(AbstractUser):
pass
注意:不要忘记在settings.py中设置AUTH_USER_MODEL指向它。
一旦已经创立数据库表之后再去修改AUTH_USER_MODEL,会困难很多,因为它会影响外键和多对多关系。这个改动并不能自动完成,需要手动修复(巨坑)。
3.用户模型代码
根据上面的分析我们的用户模型代码如下:
from django.db import models
from django.contrib.auth.models import AbstractUser, UserManager as _UserManager class UserManager(_UserManager):
"""
自定义 user manager 修改在使用`python manage.py createsuperuser`命令时
可以提供email
"""
def create_superuser(self, username, password, email=None, **extra_fields):
return super().create_superuser(username=username, password=password, email=email, **extra_fields) class User(AbstractUser):
"""
add mobile, email_active fields to Django user model.
"""
mobile = models.CharField('手机号', max_length=11, unique=True, help_text='手机号', error_messages={'unique': '此手机号码已注册'}) email_active = models.BooleanField('邮箱状态', default=False) class Meta:
db_table = 'tb_user' # 指定数据库表名
verbose_name = '用户' # 在admin站点中显示名称
verbose_name_plural = verbose_name # 显示复数 def __str__(self):
return self.username # A list of the field names that will be prompted for
# when create a user via createsuperuser management command.
REQUIRED_FIELDS = ['mobile']
# specify manager
objects = UserManager()
在settings.py文件中添加如下配置:
# 自定义用户模型
AUTH_USER_MODEL = 'user.User'
然后运行命令进行数据库迁移:
# 1. 相当于 在该app下建立 migrations目录,并记录下你所有的关于modes.py的改动,比如0001_initial.py, 但是这个改动还没有作用到数据库文件你可以手动打开这个文件,看看里面是什么
python manage.py makemigrations # 2. 将该改动作用到数据库文件,比如产生table之类
python manage.py migrate
再创建一个管理用户
(tzproject) ~/code/tztz$ python manage.py createsuperuser
用户名: admin
手机号: 158xxxxxxxx
Password:
Password (again):
密码长度太短。密码必须包含至少 个字符。
这个密码太常见了。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
1.设计接口思路
分析业务逻辑,明确在这个业务中需要涉及到几个相关子业务,将每个子业务党组欧一个接口来设计
分析接口的功能任务,明确接口的访问方式与返回数据:
接口的请求方式,如GET,POST,PUT等
接口的URL路径定义
需要接受的参数及参数格式(如路径参数,查询字符串,请求表单,JSON等)
返回的数据及数据格式
2.注册功能分析
流程图

功能
根据流程图总结注册业务包含如下功能
注册页面
图片验证码
用户名检测是否注册
手机号检测是否注册
短信验证码
注册保存用户数据
因为图片验证码,短信验证码考虑到后续可能会在其他业务中用到,因此将验证码功能独立出来,创建一个新应用verification。
三、图形验证码功能实现
1.接口设计
接口说明:
|
说明 | |
|
GET | |
| url定义 | /image_code/ | |
| 参数格式 | 查询参数 |
参数说明:
| 参数名 | 类型 | 是否必须 | 描述 | |
| rand | 字符串 | 否 |
|
返回结果:验证码图片
2.后端代码
将验证码生成模块复制到根目录utils文件夹下
创建新的app verification专门用来处理验证
cd ~/code/tztz/apps/
python ../manage.py startapp verification
别忘了在settings文件中注册app
必须在这里要先安装pillow才能用chptcha(pip install pillow)
constants.py(用于设置常量的文件) 和 verification/views.py代码如下:
# constants.py 文件
# 保存设置常量,单位秒
IMAGE_CODE_EXPIRES = 300# views.py 文件
import logging from django.http import HttpResponse from utils.captcha.captcha import captcha
from . import constants # 先要在本app中创建constants.py文件
# 日志器
logger = logging.getLogger('django') def image_code_view(request):
"""
生成图片验证码
url:/image_code/
:param request:
:return:
"""
text, image = captcha.generate_captcha()
request.session['image_code'] = text
# 将验证码存入session中
request.session.set_expiry(constants.IMAGE_CODE_EXPIRES)
logger.info('Image code:{}'.format(text)) return HttpResponse(content=image, content_type='image/jpg')verification/urls.py代码如下:
from django.urls import path
from . import views
# url的命名空间
app_name = 'verification' urlpatterns = [
path('image_code/', views.image_code_view, name='image_code'),
]- 根urls.py代码如下:
from django.urls import path, include urlpatterns = [
path('', include('news.urls')),
path('', include('verification.urls')),
]验证码的走向图:

四、注册页面
1.接口设计
接口说明:
类目 说明 请求方法 GET url定义 /user/register/ 参数格式 - 返回结果: 注册页面
2.后端代码
user/views.py
from django.shortcuts import render
from django.views import View class RegisterView(View):
def get(self, request):
return render(request, 'user/register.html')- user/urls.py
from django.urls import path, include
from . import views
app_name = 'user' urlpatterns = [
path('register/', views.RegisterView.as_view(), name='register')
] - 根urls.py
from django.urls import path, include urlpatterns = [
path('', include('news.urls')),
path('user/', include('user.urls'))
]
3.前端页面代码
user/register.html代码如下:
{% extends 'base/base.html' %}
{% load static %}
{% block title %}注册{% endblock title %} {% block link %}
<link rel="stylesheet" href="{% static 'css/user/auth.css' %}">
{% endblock link %} {% block main_start %}
<main id="container">
<div class="register-contain">
<div class="top-contain">
<h4 class="please-register">请注册</h4>
<a href="javascript:void(0);" class="login">立即登录 ></a>
</div>
<form action="" method="post" class="form-contain"> <div class="form-item">
<input type="text" placeholder="请输入用户名" name="username" class="form-control" autocomplete="off">
</div>
<div class="form-item">
<input type="password" placeholder="请输入密码" name="password" class="form-control">
</div>
<div class="form-item">
<input type="password" placeholder="请输入确认密码" name="password_repeat" class="form-control">
</div>
<div class="form-item">
<input type="tel" placeholder="请输入手机号" name="telephone" class="form-control" autocomplete="off" autofocus>
</div>
<div class="form-item">
<input type="text" placeholder="请输入图形验证码" name="captcha_graph" class="form-captcha">
<a href="javascript:void(0);" class="captcha-graph-img">
<img src="{% url 'verification:image_code' %}" alt="验证码" title="点击刷新">
</a>
</div>
<div class="form-item">
<input type="text" placeholder="请输入短信验证码" name="sms_captcha" class="form-captcha" autocomplete="off">
<a href="javascript:void(0);" class="sms-captcha" title="发送验证码">获取短信验证码</a>
</div>
<div class="form-item">
<input type="submit" value="立即注册" class="register-btn">
</div>
</form>
</div>
</main>
{% endblock main_start %}
{% block script %}
<script src="{% static 'js/user/auth.js' %}"></script>
{% endblock script %}js代码
点击验证码图片刷新的js代码如下:
$(function () {
let $img = $('.form-contain .form-item .captcha-graph-img img'); // 1.点击刷新图像验证码
$img.click(function () {
$img.attr('src', '/image_code/?rand=' + Math.random())
});
});
五、json响应数据结构设计
1.结构设计
实际项目是多人协同开发,特别是前后端交互,后端返回数据结构要一致。
{"errno": "0", "errmsg": "OK", "data": {...}}
| 字段 | 类型 | 说明 | |
| errno | 字符串 |
|
|
| errmsg | 字符串 | 错误信息 | |
| data | 返回数据 |
在项目根目录中utils文件夹下创建res_code.py文件,用于定义错误编码,代码如下:
class Code:
OK = ""
DBERR = ""
NODATA = ""
DATAEXIST = ""
DATAERR = ""
METHERR = ""
SMSERROR = ""
SMSFAIL = "" SESSIONERR = ""
LOGINERR = ""
PARAMERR = ""
USERERR = ""
ROLEERR = ""
PWDERR = "" SERVERERR = ""
UNKOWNERR = "" error_map = {
Code.OK: "成功",
Code.DBERR: "数据库查询错误",
Code.NODATA: "无数据",
Code.DATAEXIST: "数据已存在",
Code.DATAERR: "数据错误",
Code.METHERR: "方法错误",
Code.SMSERROR: "发送短信验证码异常",
Code.SMSFAIL: "发送短信验证码失败", Code.SESSIONERR: "用户未登录",
Code.LOGINERR: "用户登录失败",
Code.PARAMERR: "参数错误",
Code.USERERR: "用户不存在或未激活",
Code.ROLEERR: "用户身份错误",
Code.PWDERR: "密码错误", Code.SERVERERR: "内部错误",
Code.UNKOWNERR: "未知错误",
}
2.快捷方法
为了方便定义一个快捷方法,在utils目录下创建json_res.py文件(也可以直接在res_code文件中直接加上)代码如下:
from django.http import JsonResponse # 在res_code文件中时不用加下面这个导入
from .res_code import Code def json_response(errno=Code.OK, errmsg='', data=None, kwargs=None):
json_dict = {
'errno': errno,
'errmsg': errmsg,
'data': data
}
# 额外的字段的扩展
if kwargs and isinstance(kwargs, dict) :
json_dict.update(kwargs) return JsonResponse(json_dict)
六、判断用户是否注册功能实现
1.接口设计
接口说明:
| 类目 | 说明 |
|---|---|
| 请求方法 | GET |
| url定义 | /username/(?P<username>\w{5,20})/ |
| 参数格式 | url路径参数 |
参数说明:
| 参数名 | 类型 | 是否必须 | 描述 |
|---|---|---|---|
| username | 字符串 | 是 | 输入的用户名 |
返回结果:
{
"errno": "0",
"errmsg": "OK", # 错误信息
"data": {
"username": "username", # 查询的用户名
"count": 1 # 用户名查询的数量
}
}
2.后端代码
创建新的app verification专门用来处理验证(上面图形验证码中已经创建,所以这个是多余的)
cd ~/code/tztz/apps/
python ../manage.py startapp verificationverification/views.py代码
from user.models import User
from utils.json_res import json_response def check_username_view(request, username):
"""
校验用户名
url: /username/(?p<username>\w{5,20})/
"""
data = {
'username': username,
'count': User.objects.filter(username=username).count()
} return json_response(data=data)- verification/urls.py代码
from django.urls import path, re_path
from . import views
# url的命名空间
app_name = 'verification' urlpatterns = [
path('image_code/', views.image_code_view, name='image_code'),
re_path('username/(?P<username>\w{5,20})/', views.check_username_view, name='check_username'),
]
3.前端页面代码
user/register.html代码如下:
{% extends 'base/base.html' %}
{% load static %}
{% block title %}注册{% endblock title %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/user/auth.css' %}">
{% endblock link %}
{% block main_start %}
<main id="container">
<div class="register-contain">
<div class="top-contain">
<h4 class="please-register">请注册</h4>
<a href="javascript:void(0);" class="login">立即登录 ></a>
</div>
<form action="" method="post" class="form-contain">
<div class="form-item">
<input type="text" placeholder="请输入用户名" id="username" name="username" class="form-control" >
</div>
<div class="form-item">
<input type="password" placeholder="请输入密码" name="password" class="form-control">
</div>
<div class="form-item">
<input type="password" placeholder="请输入确认密码" name="password_repeat" class="form-control">
</div>
<div class="form-item">
<input type="tel" placeholder="请输入手机号" name="telephone" class="form-control" autocomplete="off">
</div>
<div class="form-item">
<input type="text" placeholder="请输入图形验证码" name="captcha_graph" class="form-captcha">
<a href="javascript:void(0);" class="captcha-graph-img">
<img src="{% url 'verification:image_code' %}" alt="验证码" title="点击刷新">
</a>
</div>
<div class="form-item">
<input type="text" placeholder="请输入短信验证码" name="sms_captcha" class="form-captcha" autocomplete="off">
<a href="javascript:void(0);" class="sms-captcha" title="发送验证码">获取短信验证码</a>
</div>
<div class="form-item">
<input type="submit" value="立即注册" class="register-btn">
</div>
</form>
</div>
</main>
{% endblock main_start %}
{% block script %}
<script src="{% static 'js/user/auth.js' %}"></script>
{% endblock script %}
4.前端js代码
user/auth.js代码:
$(function () {
// 定义状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false,
isSmsCodeReady = false;
// 1.点击刷新图像验证码
let $img = $('.form-contain .form-item .captcha-graph-img img');
$img.click(function () {
$img.attr('src', '/image_code/?rand=' + Math.random())
});
// 2.鼠标离开用户名输入框校验用户名
let $username = $('#username');
$username.blur(fnCheckUsername);
function fnCheckUsername () {
isUsernameReady = false;
let sUsername = $username.val(); //获取用户字符串
if (sUsername === ''){
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError('请输入5-20个字符的用户名');
return
}
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.username + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.username + '可以正常使用!')
isUsernameReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 3.检测密码是否一致
let $passwordRepeat = $('input[name="password_repeat"]');
$passwordRepeat.blur(fnCheckPassword);
function fnCheckPassword () {
isPasswordReady = false;
let password = $('input[name="password"]').val();
let passwordRepeat = $passwordRepeat.val();
if (password === '' || passwordRepeat === ''){
message.showError('密码不能为空');
return
}
if (password !== passwordRepeat){
message.showError('两次密码输入不一致');
return
}
if (password === passwordRepeat){
isPasswordReady = true
}
}
七、判断手机号码是否注册功能
1.接口设计
接口说明:
| 类目 | 说明 |
|---|---|
| 请求方法 | GET |
| url定义 | /mobile/(?P<mobile>\1[3-9]\d{9})/ |
| 参数格式 | url路径参数 |
参数说明:
| 参数名 | 类型 | 是否必须 | 描述 |
|---|---|---|---|
| moblie | 字符串 | 是 | 输入的手机号码 |
返回结果:
{
"errno": "0",
"errmsg": "OK",
"data": {
"mobile": "13xxxxxxxxx", # 查询的手机号
"count": 1 # 手机号查询的数量
}
}
2.后端代码
verification/views.py代码
# ····
def check_mobile_view(request, mobile):
"""
校验手机号是否存在
url:/moblie/(?P<moblie>1[3-9]\d{9})/
:param request:
:param username:
:return:
"""
data = {
'mobile': mobile,
'count': User.objects.filter(mobile=mobile).count()
} return json_response(data=data)- verification/urls.py
from django.urls import path, re_path
from . import views
# url的命名空间
app_name = 'verification' urlpatterns = [
path('image_code/', views.image_code_view, name='image_code'),
re_path('username/(?P<username>\w{5,20})/', views.check_username_view, name='check_username'),
re_path('mobile/(?P<mobile>1[3-9]\d{9})/', views.check_mobile_view, name='check_mobile'),
] - 前端js代码:
$(function () {
// 定义状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false,
isSmsCodeReady = false;
// 1.点击刷新图像验证码
let $img = $('.form-contain .form-item .captcha-graph-img img'); $img.click(function () {
$img.attr('src', '/image_code/?rand=' + Math.random())
}); // 2.鼠标离开用户名输入框校验用户名
let $username = $('#username');
$username.blur(fnCheckUsername); function fnCheckUsername () {
isUsernameReady = false;
let sUsername = $username.val(); //获取用户字符串
if (sUsername === ''){
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError('请输入5-20个字符的用户名');
return
}
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.username + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.username + '可以正常使用!')
isUsernameReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
} // 3.检测密码是否一致
let $passwordRepeat = $('input[name="password_repeat"]');
$passwordRepeat.blur(fnCheckPassword); function fnCheckPassword () {
isPasswordReady = false;
let password = $('input[name="password"]').val();
let passwordRepeat = $passwordRepeat.val();
if (password === '' || passwordRepeat === ''){
message.showError('密码不能为空');
return
}
if (password !== passwordRepeat){
message.showError('两次密码输入不一致');
return
}
if (password === passwordRepeat){
isPasswordReady = true
}
} // 4.检查手机号码是否可用
let $mobile = $('input[name="mobile"]');
$mobile.blur(fnCheckMobile); function fnCheckMobile () {
isMobileReady = true;
let sMobile = $mobile.val();
if(sMobile === ''){
message.showError('手机号码不能为空');
return
}
if(!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError('手机号码格式不正确');
return
} $.ajax({
url: '/mobile/' + sMobile + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.mobile + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.mobile + '可以正常使用!');
isMobileReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
}); }
});
八、获取短信验证码功能
1.业务流程分析
校验手机号码
检查图片验证码是否正确
检查是否在60s内发送记录
生成短信验证码
发送短信
保存短信验证码与发送记录
2.接口设计
接口说明:
| 类目 | 说明 |
|---|---|
| 请求方法 | POST |
| url定义 | /sms_code/ |
| 参数格式 | 表单 |
参数说明:
| 参数名 | 类型 | 是否必须 | 描述 |
|---|---|---|---|
| moblie | 字符串 | 是 | 用户输入的手机号码 |
| captcha | 字符串 | 是 | 用户输入的验证码文本 |
返回结果:
{
"errno": "0",
"errmsg": "发送短信验证码成功!",
}
3.后端代码
verification/views.py代码如下:
import logging
import random from django.http import HttpResponse
from django.views import View
from django_redis import get_redis_connection from user.models import User
from utils.json_res import json_response
from utils.res_code import Code, error_map
from utils.captcha.captcha import captcha
from utils.yuntongxun.sms import CCP from . import constants
from .forms import CheckImagForm # 日志器
logger = logging.getLogger('django') def image_code_view(request):
"""
生成图片验证码
url:/image_code/
:param request:
:return:
"""
text, image = captcha.generate_captcha()
request.session['image_code'] = text
# 将验证码存入session中
request.session.set_expiry(constants.IMAGE_CODE_EXPIRES)
logger.info('Image code:{}'.format(text)) return HttpResponse(content=image, content_type='image/jpg') def check_username_view(request, username):
"""
校验用户名是否存在
url:/username/(?P<username>\w{5,20})/
:param request:
:param username:
:return:
"""
data = {
'username': username,
'count': User.objects.filter(username=username).count()
} return json_response(data=data) def check_mobile_view(request, mobile):
"""
校验手机号是否存在
url:/moblie/(?P<moblie>1[3-9]\d{9})/
:param request:
:param username:
:return:
"""
data = {
'mobile': mobile,
'count': User.objects.filter(mobile=mobile).count()
} return json_response(data=data) class SmsCodeView(View):
"""
发送短信验证码
POST /sms_codes/
"""
def post(self, request):
# 1.校验参数 form = CheckImagForm(request.POST, request=request)
if form.is_valid():
# 2.获取手机
mobile = form.cleaned_data.get('mobile')
# 3.生成手机验证码
sms_code = ''.join([random.choice('') for _ in range(constants.SMS_CODE_LENGTH)])
# 4.发送手机验证码
ccp = CCP()
try:
res = ccp.send_template_sms(mobile, [sms_code, constants.SMS_CODE_EXPIRES], "")
if res == 0:
logger.info('发送短信验证码[正常][mobile: %s sms_code: %s]' % (mobile, sms_code))
else:
logger.error('发送短信验证码[失败][moblie: %s sms_code: %s]' % (mobile, sms_code))
return json_response(errno=Code.SMSFAIL, errmsg=error_map[Code.SMSFAIL])
except Exception as e:
logger.error('发送短信验证码[异常][mobile: %s message: %s]' % (mobile, e))
return json_response(errno=Code.SMSERROR, errmsg=error_map[Code.SMSERROR]) # 5.保存到redis数据库
# 创建短信验证码发送记录
sms_flag_key = 'sms_flag_{}'.format(mobile)
# 创建短信验证码内容记录
sms_text_key = 'sms_text_{}'.format(mobile) redis_conn = get_redis_connection(alias='verify_code')
pl = redis_conn.pipeline() try:
pl.setex(sms_flag_key, constants.SMS_CODE_INTERVAL, 1)
pl.setex(sms_text_key, constants.SMS_CODE_EXPIRES*60, sms_code)
# 让管道通知redis执行命令
pl.execute()
return json_response(errmsg="短信验证码发送成功!")
except Exception as e:
logger.error('redis 执行异常:{}'.format(e)) return json_response(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR]) else:
# 将表单的报错信息进行拼接
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
# print(item[0].get('message')) # for test
err_msg_str = '/'.join(err_msg_list) # 拼接错误信息为一个字符串 return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)- verification/forms.py文件代码如下:
from django import forms
from django.core.validators import RegexValidator
from django_redis import get_redis_connection from user.models import User # 创建手机号的正则校验器
mobile_validator = RegexValidator(r'^1[3-9]\d{9}$', '手机号码格式不正确') class CheckImagForm(forms.Form):
"""
check image code
""" def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs) mobile = forms.CharField(max_length=11, min_length=11, validators=[mobile_validator, ],
error_messages={
'max_length': '手机长度有误',
'min_length': '手机长度有误',
'required': '手机号不能为空'
}) captcha = forms.CharField(max_length=4, min_length=4,
error_messages={
'max_length': '验证码长度有误',
'min_length': '图片验证码长度有误',
'required': '图片验证码不能为空'
}) def clean(self):
clean_data = super().clean()
mobile = clean_data.get('mobile')
captcha = clean_data.get('captcha')
# 1.校验图片验证码
image_code = self.request.session.get('image_code')
if (not image_code) or (image_code.upper() != captcha.upper()):
raise forms.ValidationError('图片验证码校验失败!') # 2.校验是否在60秒内已发送过短信
redis_conn = get_redis_connection(alias='verify_code')
if redis_conn.get('sms_flag_{}'.format(mobile)):
raise forms.ValidationError('获取短信验证码过于频繁') # 3.校验手机号码是否已注册
if User.objects.filter(mobile=mobile).count():
raise forms.ValidationError('手机号已注册,请重新输入') - verification/constants.py代码如下:
# 图片验证码过期时间 单位秒
IMAGE_CODE_EXPIRES = 300 # 短信验证码长度
SMS_CODE_LENGTH = 4 # 短信验证码发送间隔 秒
SMS_CODE_INTERVAL = 60 # 短信验证码过期时间 分
SMS_CODE_EXPIRES = 5 # 短信发送模板
SMS_CODE_TEMP_ID = 1
4.短信验证码平台-云通讯
本项目中使用的短信验证码平台为云通讯平台,文档参考地址
主要是因为可以免费测试,注册后赠送8元用于测试。
开发参数:
_accountSid = '开发者主账号中的ACCOUNT SID' # 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
_accountToken = '开发者主账号中的AUTH TOKEN' # 请使用管理控制台首页的APPID或自己创建应用的APPID
_appId = '开发者主账号中的AppID(默认)' # 说明:请求地址,生产环境配置成app.cloopen.com
_serverIP = 'sandboxapp.cloopen.com' # 说明:请求端口 ,生产环境为8883
_serverPort = "" # 说明:REST API版本号保持不变
_softVersion = '2013-12-26'
设置测试手机号码

5.前端js代码
user/auth.js代码:
$(function () {
// 定义状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false,
isSmsCodeReady = false;
// 1.点击刷新图像验证码
let $img = $('.form-contain .form-item .captcha-graph-img img');
$img.click(function () {
$img.attr('src', '/image_code/?rand=' + Math.random())
});
// 2.鼠标离开用户名输入框校验用户名
let $username = $('#username');
$username.blur(fnCheckUsername);
function fnCheckUsername () {
isUsernameReady = false;
let sUsername = $username.val(); //获取用户字符串
if (sUsername === ''){
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError('请输入5-20个字符的用户名');
return
}
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.username + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.username + '可以正常使用!')
isUsernameReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 3.检测密码是否一致
let $passwordRepeat = $('input[name="password_repeat"]');
$passwordRepeat.blur(fnCheckPassword);
function fnCheckPassword () {
isPasswordReady = false;
let password = $('input[name="password"]').val();
let passwordRepeat = $passwordRepeat.val();
if (password === '' || passwordRepeat === ''){
message.showError('密码不能为空');
return
}
if (password !== passwordRepeat){
message.showError('两次密码输入不一致');
return
}
if (password === passwordRepeat){
isPasswordReady = true
}
}
// 4.检查手机号码是否可用
let $mobile = $('input[name="mobile"]');
$mobile.blur(fnCheckMobile);
function fnCheckMobile () {
isMobileReady = true;
let sMobile = $mobile.val();
if(sMobile === ''){
message.showError('手机号码不能为空');
return
}
if(!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError('手机号码格式不正确');
return
}
$.ajax({
url: '/mobile/' + sMobile + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.mobile + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.mobile + '可以正常使用!');
isMobileReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 5.发送手机验证码
let $smsButton = $('.sms-captcha');
$smsButton.click(function () {
let sCaptcha = $('input[name="captcha_graph"]').val();
if(sCaptcha === ''){
message.showError('请输入验证码');
return
}
if(!isMobileReady){
fnCheckMobile();
return
}
$.ajax({
url: '/sms_code/',
type: 'POST',
data: {
mobile: $mobile.val(),
captcha: sCaptcha
},
dataType: 'json',
success: function (data) {
if(data.errno !== '0'){
message.showError(data.errmsg)
}else {
message.showSuccess(data.errmsg);
let num = 60;
//设置计时器
let t = setInterval(function () {
if(num===1){
clearInterval(t)
}
})
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
});
});
因为用到了post方法,django默认带有csrf防护,所以在base/common.js中添加如下代码:
$(()=>{
let $navLi = $('#header .nav .menu li');
$navLi.click(function(){
$(this).addClass('active').siblings('li').removeClass('active')
});
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
详解django文档
九、注册功能
1.业务流程分析
判断用户名是否为空,是否已注册
判断密码是否为空,格式是否正确
判断两次密码是否一致
判断手机号码是否为空,格式是否正确
判断短信验证码是否为空,格式是否正确,是否与真实短信验证码相同
2.接口设计
接口说明:
| 类目 | 说明 |
|---|---|
| 请求方法 | POST |
| url定义 | /user/register/ |
| 参数格式 | 表单 |
注意:post请求,前端请求要带上csrf token
参数说明:
| 参数名 | 类型 | 是否必须 | 描述 |
|---|---|---|---|
| username | 字符串 | 是 | 用户输入的用户名 |
| password | 字符串 | 是 | 用户输入的密码 |
| password_repeat | 字符串 | 是 | 用户输入的重复密码 |
| mobile | 字符串 | 是 | 用户输入的手机号码 |
| sms_code | 字符串 | 是 | 用户输入的短信验证码 |
返回结果:
{
"errno": "0",
"errmsg": "恭喜您,注册成功!",
}
3.后端代码
user/views.py代码:
from django.shortcuts import render
from django.views import View from .forms import RegisterForm
from .models import User
from utils.json_res import json_response
from utils.res_code import Code, error_map class RegisterView(View):
def get(self, request):
return render(request, 'user/register.html') def post(self, request):
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
User.objects.create_user(username=username, password=password, mobile=mobile)
return json_response(errmsg='恭喜你,注册成功!')
else:
# 定义一个错误信息列表
err_msg_list = []
for item in form.errors.values():
err_msg_list.append(item[0])
err_msg_str = '/'.join(err_msg_list) return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)user/forms.py代码:
import re from django import forms
from django_redis import get_redis_connection from .models import User
from verification.constants import SMS_CODE_LENGTH class RegisterForm(forms.Form):
username = forms.CharField(label='用户名', max_length=20, min_length=5,
error_messages={
'max_length': '用户名长度要小于20',
'min_length': '用户名长度要大于4',
'required': '用户名不能为空'
})
password = forms.CharField(label='密码', max_length=20, min_length=6,
error_messages={
'max_length': '密码长度要小于20',
'min_length': '密码长度要大于5',
'required': '用户名不能为空'
})
password_repeat = forms.CharField(label='确认密码', max_length=20, min_length=6,
error_messages={
'max_length': '密码长度要小于20',
'min_length': '密码长度要大于5',
'required': '用户名不能为空'
})
mobile = forms.CharField(label='手机号码', max_length=11, min_length=11,
error_messages={
'max_length': '手机号码长度有误',
'min_length': '手机号码长度有误',
'required': '手机号码不能为空'
})
sms_code = forms.CharField(label='短信验证码', max_length=SMS_CODE_LENGTH, min_length=SMS_CODE_LENGTH,
error_messages={
'max_length': '短信验证码长度有误',
'min_length': '短信验证码长度有误长度有误',
'required': '短信验证码不能为空'
})
### clean_username这种是单独校验,写多少就校验多少,上面的错了,下面的同方照样执行
### clean一般用于多个字段联合校验,但是只要上面错了下面就不会校验了
def clean_username(self):
"""
校验用户名
:return:
"""
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
return forms.ValidationError('用户名已存在!')
return username def clean_mobile(self):
"""
校验手机号
:return:
"""
mobile = self.cleaned_data.get('mobile')
if not re.match(r'^1[3-9]\d{9}$', mobile):
raise forms.ValidationError('手机号码格式不正确') if User.objects.filter(mobile=mobile).exists():
raise forms.ValidationError('手机号码已注册!') return mobile def clean(self):
"""
校验,密码,和短信验证码
:return:
"""
clean_data = super().clean()
# 校验密码是否一致
password = clean_data.get('password')
password_repeat = clean_data.get('password_repeat')
if password != password_repeat:
raise forms.ValidationError('两次密码不一致!') # 校验短信验证码
sms_code = clean_data.get('sms_code')
moblie = clean_data.get('mobile') redis_conn = get_redis_connection(alias='verify_code')
real_code = redis_conn.get('sms_text_{}'.format(moblie))
if (not real_code) or (real_code.decode('utf-8') != sms_code):
raise forms.ValidationError('短信验证码错误!')
clean_username和clean的差别用法:
4.前端js代码
$(function () {
// 定义状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false;
// 1.点击刷新图像验证码
let $img = $('.form-contain .form-item .captcha-graph-img img');
$img.click(function () {
$img.attr('src', '/image_code/?rand=' + Math.random())
});
// 2.鼠标离开用户名输入框校验用户名
let $username = $('#username');
$username.blur(fnCheckUsername);
function fnCheckUsername () {
isUsernameReady = false;
let sUsername = $username.val(); //获取用户字符串
if (sUsername === ''){
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError('请输入5-20个字符的用户名');
return
}
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.username + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.username + '可以正常使用!')
isUsernameReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 3.检测密码是否一致
let $passwordRepeat = $('input[name="password_repeat"]');
$passwordRepeat.blur(fnCheckPassword);
function fnCheckPassword () {
isPasswordReady = false;
let password = $('input[name="password"]').val();
let passwordRepeat = $passwordRepeat.val();
if (password === '' || passwordRepeat === ''){
message.showError('密码不能为空');
return
}
if (password !== passwordRepeat){
message.showError('两次密码输入不一致');
return
}
if (password === passwordRepeat){
isPasswordReady = true
}
}
// 4.检查手机号码是否可用
let $mobile = $('input[name="mobile"]');
$mobile.blur(fnCheckMobile);
function fnCheckMobile () {
isMobileReady = true;
let sMobile = $mobile.val();
if(sMobile === ''){
message.showError('手机号码不能为空');
return
}
if(!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError('手机号码格式不正确');
return
}
$.ajax({
url: '/mobile/' + sMobile + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.mobile + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.mobile + '可以正常使用!');
isMobileReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 5.发送手机验证码
let $smsButton = $('.sms-captcha');
$smsButton.click(function () {
let sCaptcha = $('input[name="captcha_graph"]').val();
if(sCaptcha === ''){
message.showError('请输入验证码');
return
}
if(!isMobileReady){
fnCheckMobile();
return
}
$.ajax({
url: '/sms_code/',
type: 'POST',
data: {
mobile: $mobile.val(),
captcha: sCaptcha
},
dataType: 'json',
success: function (data) {
if(data.errno !== '0'){
message.showError(data.errmsg)
}else {
message.showSuccess(data.errmsg);
let num = 60;
//设置计时器
let t = setInterval(function () {
if(num===1){
clearInterval(t)
}
})
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
});
// 6.注册
let $submitBtn = $('.register-btn');
$submitBtn.click(function (e) {
//阻止默认提交
e.preventDefault();
// 1.检查用户名
if(!isUsernameReady){
fnCheckUsername();
return
}
// 2.检查密码
if(!isPasswordReady){
fnCheckPassword();
return
}
// 3.检查电话号码
if(!isMobileReady){
fnCheckMobile();
return
}
// 4.检查短信验证码
let sSmsCode = $('input[name="sms_captcha"]').val();
if(sSmsCode === ''){
message.showError('短信验证码不能为空!');
return
}
if(!(/^\d{4}$/).test(sSmsCode)){
message.showError('短信验证码长度不正确,必须是4位数字!');
return
}
$.ajax({
url: '/user/register/',
type: 'POST',
data:{
username: $username.val(),
password: $('input[name="password"]').val(),
password_repeat: $passwordRepeat.val(),
mobile: $mobile.val(),
sms_code: sSmsCode
},
dataType: 'json',
success: function (res) {
if(res.errno === '0'){
message.showSuccess('恭喜您,注册成功!');
setTimeout(function () {
//注册成功后重定向到登录页面
window.location.href = '/user/login/'
}, 3000)
}else{
//注册失败
message.showError(res.errmsg)
}
},
error: function () {
message.showError('服务器超时,请重试!')
}
})
});
});
Django项目: 3.用户注册功能的更多相关文章
- Django项目实战 - 搜索功能(转)
首先,前端已实现搜索功能页面, 我们直接写后台逻辑: Q()可以实现 逻辑或的判断, name_ _ icontains 表示 name字段包含搜索的内容,i表示忽略大小写. from djang ...
- 9、Django实战第9天:用户注册功能
今天完成的是用户注册功能... 首先把注册页面的前端文件register.html复制到templates目录下 编辑users.views.py,创建一个注册的类 class RegisterVie ...
- 接口自动化平台搭建(二),搭建django项目与接口自动化平台的由来与功能特征
1.创建django项目 a.使用命令创建,安装完django之后就有django-admin命令了,执行命令创建即可,命令如下: django-admin startproject my_djang ...
- Django项目部署(阿里云)(1)--基本功能实现
新博客地址:http://muker.net/django-server.html 手头需要部署一个Django项目,前面的博客也因为偷懒也没有部署,这里记录一下部署过程.ps:其实网上比较靠谱的说明 ...
- Web框架之Django_02基本操作(Django项目启动配置、数据库连接、orm、增删改查)
摘要: Django项目简单现实过程 pycharm连接数据库 Django之orm简单操作增删改查 一.新建Django项目.配置.设置: 新建Django项目:(为了熟悉Django操作,暂时全部 ...
- python——创建django项目全攻略(野生程序员到家养程序员的完美进化)
新建工程 我用pycharm写代码,所以一般就用pycharm创建django工程.右上角File-New Project.选择路径,修改项目名称,确定.就可以创建一个新的django工程. ...
- Python开发入门与实战2-第一个Django项目
2.第一个Django项目 上一章节我们完成了python,django和数据库等运行环境的安装,现在我们来创建第一个django project吧,迈出使用django开发应用的第一步. 2.1.创 ...
- Web---创建Servlet的3种方式、简单的用户注册功能
说明: 创建Servlet的方式,在上篇博客中,已经用了方式1(实现Servlet接口),接下来本节讲的是另外2种方式. 上篇博客地址:http://blog.csdn.net/qq_26525215 ...
- 如何创建一个Django项目
Django 软件框架 软件框架是由其中的各个模块组成,每个模块负责特定的功能,模块与模块之间相互协作来完成软件开发. MVC简介 MVC框架的核心思想是:解耦,让不同的代码块之间降低耦合,增强代码的 ...
随机推荐
- Random类和Math.random()方法
一.Random类的定义Random类位于 java.util 包中,主要用于生成伪 随机数Random类将 种子数 作为随机算法的起源数字,计算生成伪随机数,其与生成的随机数字的区间无关创建Rand ...
- Ansible实现批量无密码登录
如果机器多,假如有一百台服务器,每台服务器登录前都得先输入yes,使用交互式的方式下发公钥的话就很麻烦(ssh-copy-id). 第一次操作需要通过密码来操作服务器,所以配置文件需要把密码配置好 a ...
- 当对象转换成JSON的时候处理时间格式
/// <summary> /// 格式化日期 /// </summary> /// <param name="foramt">格式化规则< ...
- CF 540D Bad Luck Island
一看就是DP题(很水的一道紫题) 设\(dp[i][j][k]\)为留下\(i\)个\(r\)族的人,死去\(j\)个\(s\)族的人,死去\(k\)个\(p\)族的人的概率(跟其他的题解有点差别,但 ...
- What is the difference between HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR
What is the difference between HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR? it is impossible to say. Dif ...
- scala中的闭包简单使用
object Closure { /** * scala中的闭包 * 函数在变量不处于其有效作用域内,还能够对变量进行访问 * * @param args */ def main(args: Arra ...
- C++调用python(C++)
C++源代码:python部分就是正常的python代码 #include <string.h> #include <math.h> #include "iostre ...
- RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)
摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...
- 像bootstrap一样的去做web编程
1: 闭包 boot的闭包方式有点特别,普通的闭包是这样的: (function ($) { })(jQuery) 这种写法是怕全局污染,把$封闭在自己的空间里,暴露在外面的只有jQuery,这样,如 ...
- python编码(31-01)
以什么方式编码,就以什么方式解码! 第一种编码与解码方式: encode()编码 decode()解码 type()查看数据类型 repr()查看数据内容 s = '你好'print(type(s)) ...