本章内容的补充知识点

导入库的良好顺序:

1.系统库 2.django库 3.自己定义的库(第三方库)

redis缓存数据库的数据调用速度快,但是不利于长时间保存。

mysql用于长时间存储,但是调用比较慢。

session会话存储的内容(以字典的方式存放)放在redis缓存里面,要设置过期时间

用户注册功能

一、用户模型设计

1. 用户表字段分析

  • 用户名

  • 密码

  • 手机号

  • 邮箱

2.用户模型设计

django的强大之处在于开发效率高,内置了权限模块之类的很多常用功能。在开始一个新的django项目时,如果权限模块中的User模型不满足项目要求,我们需要扩展或者自定义User模型。

扩展User模型有两种方法

  1. 如果你不需要改变数据库存储内容,只是改变行为,那么可以建立有一个基于User模型的代理模型。

  2. 如果想存储与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.注册功能分析

  1. 流程图

  

  1. 功能

根据流程图总结注册业务包含如下功能

  • 注册页面

  • 图片验证码

  • 用户名检测是否注册

  • 手机号检测是否注册

  • 短信验证码

  • 注册保存用户数据

因为图片验证码,短信验证码考虑到后续可能会在其他业务中用到,因此将验证码功能独立出来,创建一个新应用verification

三、图形验证码功能实现

1.接口设计

接口说明:

类目
说明
请求方法
GET
url定义 /image_code/
参数格式 查询参数

参数说明:

参数名 类型 是否必须 描述
rand 字符串

输入的用户名

返回结果:验证码图片

2.后端代码

  1. 将验证码生成模块复制到根目录utils文件夹下

  2. 创建新的app verification专门用来处理验证

    cd ~/code/tztz/apps/
    python ../manage.py startapp verification

别忘了在settings文件中注册app

必须在这里要先安装pillow才能用chptcha(pip install pillow)

  1. 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')
  2. 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'),
    ]
  3. 根urls.py代码如下:
    from django.urls import path, include
    
    urlpatterns = [
    path('', include('news.urls')),
    path('', include('verification.urls')),
    ]

    验证码的走向图:

四、注册页面

1.接口设计

  1. 接口说明:

    类目

    说明
    请求方法
    GET
    url定义
      /user/register/
    参数格式  
  2. 返回结果: 注册页面

2.后端代码

  1. 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')
  2. user/urls.py
    from django.urls import path, include
    from . import views
    app_name = 'user' urlpatterns = [
    path('register/', views.RegisterView.as_view(), name='register')
    ]
  3. 根urls.py
    from django.urls import path, include
    
    urlpatterns = [
    path('', include('news.urls')),
    path('user/', include('user.urls'))
    ]

3.前端页面代码

  1. 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">立即登录 &gt;</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 %}
  2. 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.后端代码

  1. 创建新的app verification专门用来处理验证(上面图形验证码中已经创建,所以这个是多余的)

    cd ~/code/tztz/apps/
    python ../manage.py startapp verification
  2. verification/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)
  3. 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">立即登录 &gt;</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.后端代码

  1. 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)
  2. 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'),
    ]
  3. 前端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.后端代码

  1. 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)
  2. 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('手机号已注册,请重新输入')
  3. 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.业务流程分析

  1. 判断用户名是否为空,是否已注册

  2. 判断密码是否为空,格式是否正确

  3. 判断两次密码是否一致

  4. 判断手机号码是否为空,格式是否正确

  5. 判断短信验证码是否为空,格式是否正确,是否与真实短信验证码相同

2.接口设计

接口说明:

类目 说明
请求方法 POST
url定义 /user/register/
参数格式 表单

注意:post请求,前端请求要带上csrf token

参数说明:

参数名 类型 是否必须 描述
username 字符串 用户输入的用户名
password 字符串 用户输入的密码
password_repeat 字符串 用户输入的重复密码
mobile 字符串 用户输入的手机号码
sms_code 字符串 用户输入的短信验证码

返回结果:

{
"errno": "0",
"errmsg": "恭喜您,注册成功!",
}

3.后端代码

  1. 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)
  2. 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.用户注册功能的更多相关文章

  1. Django项目实战 - 搜索功能(转)

    首先,前端已实现搜索功能页面, 我们直接写后台逻辑: Q()可以实现 逻辑或的判断,   name_ _ icontains 表示 name字段包含搜索的内容,i表示忽略大小写. from djang ...

  2. 9、Django实战第9天:用户注册功能

    今天完成的是用户注册功能... 首先把注册页面的前端文件register.html复制到templates目录下 编辑users.views.py,创建一个注册的类 class RegisterVie ...

  3. 接口自动化平台搭建(二),搭建django项目与接口自动化平台的由来与功能特征

    1.创建django项目 a.使用命令创建,安装完django之后就有django-admin命令了,执行命令创建即可,命令如下: django-admin startproject my_djang ...

  4. Django项目部署(阿里云)(1)--基本功能实现

    新博客地址:http://muker.net/django-server.html 手头需要部署一个Django项目,前面的博客也因为偷懒也没有部署,这里记录一下部署过程.ps:其实网上比较靠谱的说明 ...

  5. Web框架之Django_02基本操作(Django项目启动配置、数据库连接、orm、增删改查)

    摘要: Django项目简单现实过程 pycharm连接数据库 Django之orm简单操作增删改查 一.新建Django项目.配置.设置: 新建Django项目:(为了熟悉Django操作,暂时全部 ...

  6. python——创建django项目全攻略(野生程序员到家养程序员的完美进化)

    新建工程 我用pycharm写代码,所以一般就用pycharm创建django工程.右上角File-New Project.选择路径,修改项目名称,确定.就可以创建一个新的django工程.     ...

  7. Python开发入门与实战2-第一个Django项目

    2.第一个Django项目 上一章节我们完成了python,django和数据库等运行环境的安装,现在我们来创建第一个django project吧,迈出使用django开发应用的第一步. 2.1.创 ...

  8. Web---创建Servlet的3种方式、简单的用户注册功能

    说明: 创建Servlet的方式,在上篇博客中,已经用了方式1(实现Servlet接口),接下来本节讲的是另外2种方式. 上篇博客地址:http://blog.csdn.net/qq_26525215 ...

  9. 如何创建一个Django项目

    Django 软件框架 软件框架是由其中的各个模块组成,每个模块负责特定的功能,模块与模块之间相互协作来完成软件开发. MVC简介 MVC框架的核心思想是:解耦,让不同的代码块之间降低耦合,增强代码的 ...

随机推荐

  1. Random类和Math.random()方法

    一.Random类的定义Random类位于 java.util 包中,主要用于生成伪 随机数Random类将 种子数 作为随机算法的起源数字,计算生成伪随机数,其与生成的随机数字的区间无关创建Rand ...

  2. Ansible实现批量无密码登录

    如果机器多,假如有一百台服务器,每台服务器登录前都得先输入yes,使用交互式的方式下发公钥的话就很麻烦(ssh-copy-id). 第一次操作需要通过密码来操作服务器,所以配置文件需要把密码配置好 a ...

  3. 当对象转换成JSON的时候处理时间格式

    /// <summary> /// 格式化日期 /// </summary> /// <param name="foramt">格式化规则< ...

  4. CF 540D Bad Luck Island

    一看就是DP题(很水的一道紫题) 设\(dp[i][j][k]\)为留下\(i\)个\(r\)族的人,死去\(j\)个\(s\)族的人,死去\(k\)个\(p\)族的人的概率(跟其他的题解有点差别,但 ...

  5. 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 ...

  6. scala中的闭包简单使用

    object Closure { /** * scala中的闭包 * 函数在变量不处于其有效作用域内,还能够对变量进行访问 * * @param args */ def main(args: Arra ...

  7. C++调用python(C++)

    C++源代码:python部分就是正常的python代码 #include <string.h> #include <math.h> #include "iostre ...

  8. RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)

    摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...

  9. 像bootstrap一样的去做web编程

    1: 闭包 boot的闭包方式有点特别,普通的闭包是这样的: (function ($) { })(jQuery) 这种写法是怕全局污染,把$封闭在自己的空间里,暴露在外面的只有jQuery,这样,如 ...

  10. python编码(31-01)

    以什么方式编码,就以什么方式解码! 第一种编码与解码方式: encode()编码 decode()解码 type()查看数据类型 repr()查看数据内容 s = '你好'print(type(s)) ...