前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【玩转腾讯云】django 开发Bug追踪平台之用户注册篇(基于腾讯云短信 & redis)

【玩转腾讯云】django 开发Bug追踪平台之用户注册篇(基于腾讯云短信 & redis)

原创
作者头像
ruochen
修改2021-04-12 10:40:17
26.7K0
修改2021-04-12 10:40:17
举报

项目预览 👉 Bug追踪平台【云短信买不起了,可通过 手机号:18203503747 密码:ruochen666 登入体验】

项目gitee地址 👉 saas

本篇教程对应代码为 【注册验证码处理】提交,可通过对应分支查看

用户注册篇

首先,总体的<font color="red">思维导图</font>如下:

1. 前期准备

1.1 腾讯云发送短信

1.2 redis

2. 注册页面展示

2.1 创建app

  • 创建一个名为 web 的app,之后的代码都在这个app里面写```python python manage.py startapp web ```2.2 app注册
  • settings.py 文件中注册app,INSTALLED_APPS 添加自己刚才创建的app【默认应该是已经添加了,没有的话自己添加】一下```python 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【图标】 链接:https://pan.baidu.com/s/1gQRN57XgYcD9y3-Dz8_oaA 提取码:mnjl 解压密码: ruochen666
  • web 下创建一个用于存放静态文件的 static 文件夹,然后再创建一个 plugin 文件夹,用于存放工具类文件,然后将下载好的 js、bootstrap、font-awesome 放置到 static 文件夹中,结构如下图
  • 接下来我们要使用的时候就可以直接引入 static 文件夹下的文件2.3.2 母版为什么要用到母版? 前端页面中,注册和登录的页面基本相似,我们可以让这两个页面都继承自母版,做到代码重用
  • web 下创建一个 templates 文件夹,在 templates 文件夹下再创建一个 layout 文件夹放我们的母版文件 basic.html - 结构如下
代码语言:txt
复制
- `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="{% static '/plugin/bootstrap/css/bootstrap.min.css' %}">
	    <link rel="stylesheet" href="{% 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>
	
代码语言:txt
复制
	        <!-- Collect the nav links, forms, and other content for toggling -->
代码语言:txt
复制
	        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
代码语言:txt
复制
	            <ul class="nav navbar-nav">
代码语言:txt
复制
	                <li><a href="#">产品功能</a></li>
代码语言:txt
复制
	                <li><a href="#">企业方案</a></li>
代码语言:txt
复制
	                <li><a href="#">帮助文档</a></li>
代码语言:txt
复制
	                <li><a href="#">价格</a></li>
代码语言:txt
复制
	            </ul>
代码语言:txt
复制
	            <ul class="nav navbar-nav navbar-right">
代码语言:txt
复制
	                <li><a href="#">Link</a></li>
代码语言:txt
复制
	                <li class="dropdown">
代码语言:txt
复制
	                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
代码语言:txt
复制
	                       aria-expanded="false">Dropdown <span class="caret"></span></a>
代码语言:txt
复制
	                    <ul class="dropdown-menu">
代码语言:txt
复制
	                        <li><a href="#">Action</a></li>
代码语言:txt
复制
	                        <li><a href="#">Another action</a></li>
代码语言:txt
复制
	                        <li><a href="#">Something else here</a></li>
代码语言:txt
复制
	                        <li role="separator" class="divider"></li>
代码语言:txt
复制
	                        <li><a href="#">Separated link</a></li>
代码语言:txt
复制
	                    </ul>
代码语言:txt
复制
	                </li>
代码语言:txt
复制
	            </ul>
代码语言:txt
复制
	        </div>
代码语言:txt
复制
	    </div>
代码语言:txt
复制
	</nav>
代码语言:txt
复制
	{% block content %}{% endblock %}
代码语言:txt
复制
	<script src="{% static 'js/jquery-3.4.1.min.js' %}"></script>
代码语言:txt
复制
	<script src="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}"></script>
代码语言:txt
复制
	{% block js %}{% endblock %}
代码语言:txt
复制
	</body>
代码语言:txt
复制
	</html>
代码语言:txt
复制
	```

2.4 URL准备

  • MyDjango/MyDjango/urls.py 【我的项目名称为 MyDjango】 ```python """MyDjango URL Configuration
代码语言:txt
复制
The `urlpatterns` list routes URLs to views. For more information please see:
代码语言:txt
复制
    https://docs.djangoproject.com/en/1.11/topics/http/urls/
代码语言:txt
复制
Examples:
代码语言:txt
复制
Function views
代码语言:txt
复制
    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
代码语言:txt
复制
urlpatterns = [
代码语言:txt
复制
    url(r'^admin/', admin.site.urls),
代码语言:txt
复制
    url(r'^/', include('web.urls')),
代码语言:txt
复制
]
代码语言:txt
复制
```
  • 在 web 文件夹下创建 urls.py 文件,用于管理该app 的路由(视图函数我们下面会写) ```python # -*- 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
代码语言:txt
复制
urlpatterns = [
代码语言:txt
复制
    url(r'^register/$', account.register, name='register'),  # register
代码语言:txt
复制
]
代码语言:txt
复制
```

2.5 模型准备【models.py】

  • 用户注册时,要填写的数据有 - 用户名 - 邮箱 - 手机号 - 密码
  • web/models.py 文件中创建一个 UserInfo 类,代码如下 ```python from django.db import models
代码语言:txt
复制
class UserInfo(models.Model):
代码语言:txt
复制
    username = models.CharField(verbose_name='用户名', max_length=32)
代码语言:txt
复制
    email = models.EmailField(verbose_name='邮箱', max_length=32)
代码语言:txt
复制
    mobile_phone = models.CharField(verbose_name='手机号', max_length=32)
代码语言:txt
复制
    password = models.CharField(verbose_name='密码', max_length=32)
代码语言:txt
复制
    def __str__(self):
代码语言:txt
复制
        return self.username
代码语言:txt
复制
```
  • 迁移数据库```python python manage.py makemigrations python manage.py migrate ```2.5 视图函数
  • 将web 下的 views.py 文件删除,创建一个 views 文件夹,方便管理我们的视图,然后在 views 文件夹下创建一个 account.py 文件作为注册视图,代码如下:(RegisterModelFormregister.html后面会写) ```python from django.shortcuts import render from web.forms.account import RegisterModelForm
代码语言:txt
复制
def register(request):
代码语言:txt
复制
    form = RegisterModelForm()
代码语言:txt
复制
    return render(request, 'register.html', {'form': form})
代码语言:txt
复制
```

2.6 ModelForm【简单校验 & 样式添加】

  • 前端页面要通过Form 表单循环生成数据,但是直接生成的话有点丑,而且数据也要先做一些基本的校验【例如手机号,钩子函数在后面校验表单时用到,这里先通过正则简单的校验一下手机号】
  • web 文件夹下创建一个 forms 文件夹,forms 文件夹中创建 account.py 文件,代码如下 - 一: 对字段进行处理,例如手机号进行校验,密码为 PasswordInput 形式等 - 二:给每个字段添加 form-control 样式,前端页面显示比较美观一点 - 三:添加 code 【验证码】字段 ```python # -*- 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
代码语言:txt
复制
from web import models
代码语言:txt
复制
class RegisterModelForm(forms.ModelForm):
代码语言:txt
复制
    mobile_phone = forms.CharField(
代码语言:txt
复制
        label='手机号',
代码语言:txt
复制
        validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
代码语言:txt
复制
    password = forms.CharField(
代码语言:txt
复制
        label='密码', widget=forms.PasswordInput())
代码语言:txt
复制
    confirm_password = forms.CharField(
代码语言:txt
复制
        label='重复密码',
代码语言:txt
复制
        widget=forms.PasswordInput())
代码语言:txt
复制
    code = forms.CharField(
代码语言:txt
复制
        label='验证码',
代码语言:txt
复制
        widget=forms.TextInput())
代码语言:txt
复制
    class Meta:
代码语言:txt
复制
        model = models.UserInfo
代码语言:txt
复制
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
代码语言:txt
复制
    def __init__(self, *args, **kwargs):
代码语言:txt
复制
        super().__init__(*args, **kwargs)
代码语言:txt
复制
        for name, field in self.fields.items():
代码语言:txt
复制
            field.widget.attrs['class'] = 'form-control'
代码语言:txt
复制
            field.widget.attrs['placeholder'] = '请输入{}'.format(field.label,)
代码语言:txt
复制
```
  • 使用上述代码是没有任何问题的,但是,对于添加 form-control 属性,之后的代码中其他字段都要用到,每次使用for 循环添加很显然有些赘余,我们可以将其封装在一个类中,这样,需要添加样式的时候直接继承这个类就可以了。
  • 修改如下,在 web/forms 下创建一个 bootstrap.py 文件,代码如下: ```python # -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> bootstrap @IDE :PyCharm @Author :ruochen @Date :2020/7/3 16:25 @Desc : ==================================================''' class BootStrapForm(object):
代码语言:txt
复制
    bootstrap_class_exclude = []
代码语言:txt
复制
    def __init__(self, *args, **kwargs):
代码语言:txt
复制
        super().__init__(*args, **kwargs)
代码语言:txt
复制
        for name, field in self.fields.items():
代码语言:txt
复制
            if name in self.bootstrap_class_exclude:
代码语言:txt
复制
                continue
代码语言:txt
复制
            old_class = field.widget.attrs.get('class', '')
代码语言:txt
复制
            field.widget.attrs['class'] = '{} form-control'.format(old_class)
代码语言:txt
复制
            field.widget.attrs['placeholder'] = '请输入{}'.format(field.label,)
代码语言:txt
复制
```
  • 然后 forms/account.py 文件修改为 ```python from django import forms from django.core.validators import RegexValidator
代码语言:txt
复制
from web import models
代码语言:txt
复制
from web.forms.bootstrap import BootStrapForm
代码语言:txt
复制
class RegisterModelForm(BootStrapForm, forms.ModelForm):
代码语言:txt
复制
    password = forms.CharField(
代码语言:txt
复制
        label='密码',
代码语言:txt
复制
        min_length=8,
代码语言:txt
复制
        max_length=64,
代码语言:txt
复制
        error_messages={
代码语言:txt
复制
            'min_length': "密码长度不能小于8个字符",
代码语言:txt
复制
            'max_length': "密码长度不能大于64个字符"
代码语言:txt
复制
        },
代码语言:txt
复制
        widget=forms.PasswordInput())
代码语言:txt
复制
    confirm_password = forms.CharField(
代码语言:txt
复制
        label='重复密码',
代码语言:txt
复制
        min_length=8,
代码语言:txt
复制
        max_length=64,
代码语言:txt
复制
        error_messages={
代码语言:txt
复制
            'min_length': "重复密码长度不能小于8个字符",
代码语言:txt
复制
            'max_length': "重复密码长度不能大于64个字符"
代码语言:txt
复制
        },
代码语言:txt
复制
        widget=forms.PasswordInput()
代码语言:txt
复制
    )
代码语言:txt
复制
    mobile_phone = forms.CharField(
代码语言:txt
复制
        label='手机号',
代码语言:txt
复制
        validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
代码语言:txt
复制
    code = forms.CharField(
代码语言:txt
复制
        label='验证码',
代码语言:txt
复制
        widget=forms.TextInput())
代码语言:txt
复制
    class Meta:
代码语言:txt
复制
        model = models.UserInfo
代码语言:txt
复制
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
代码语言:txt
复制
```

2.7 前端页面

  • templates 文件夹下创建 register.html 文件夹,让其继承自 basic.html
  • 前端页面对于字段的展示,直接循环展示 form 表单生成的数据即可 ```html {% extends 'layout/basic.html' %} {% load static %}
代码语言:txt
复制
{% block title %} 用户注册 {% endblock %}
代码语言:txt
复制
{% block css %}
代码语言:txt
复制
    <link rel="stylesheet" href="{% static 'css/account.css' %}">
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
{% block content %}
代码语言:txt
复制
    <div class="account">
代码语言:txt
复制
        <div class="title">用户注册</div>
代码语言:txt
复制
        <form id="form" method="post" novalidate>
代码语言:txt
复制
            {% csrf_token %}
代码语言:txt
复制
            {% for field in form %}
代码语言:txt
复制
                {% if field.name == 'code' %}
代码语言:txt
复制
                    <div class="form-group">
代码语言:txt
复制
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
代码语言:txt
复制
                        <div class="row">
代码语言:txt
复制
                            <div class="col-xs-7">
代码语言:txt
复制
                                {{ field }}
代码语言:txt
复制
                                <span class="error-msg">{{ field.errors.0 }}</span>
代码语言:txt
复制
                            </div>
代码语言:txt
复制
                            <div class="col-xs-5">
代码语言:txt
复制
                                <input id="smsBtn" type="button" class="btn btn-default" value="点击获取验证码"/>
代码语言:txt
复制
                            </div>
代码语言:txt
复制
                        </div>
代码语言:txt
复制
                    </div>
代码语言:txt
复制
                {% else %}
代码语言:txt
复制
                    <div class="form-group">
代码语言:txt
复制
                        <label for="{{ field.id_for_label }}">{{ field.lable }}</label>
代码语言:txt
复制
                        {{ field }}
代码语言:txt
复制
                        <span class="error-msg">{{ field.errors.0 }}</span>
代码语言:txt
复制
                    </div>
代码语言:txt
复制
                {% endif %}
代码语言:txt
复制
            {% endfor %}
代码语言:txt
复制
            <div class="row">
代码语言:txt
复制
                <div class="col-xs-3">
代码语言:txt
复制
                    <input id="submit" type="button" class="btn btn-primary" value="注  册"/>
代码语言:txt
复制
                </div>
代码语言:txt
复制
            </div>
代码语言:txt
复制
        </form>
代码语言:txt
复制
    </div>
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
{% block js %}
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
```

3. 验证码获取

3.1 思路

  • 给获取验证码按钮绑定事件,在前端页面中,用户点击获取验证码后,通过腾讯云短信向用户手机号发送验证码,并且在页面上显示60s倒计时,向后端发送ajax请求
  • 后端进行手机号校验(判断手机号是否已经注册过)和短信模板的验证(腾讯云短信的一些凭证)

3.2 具体实现

3.2.1 前端代码
  • register.html 中添加js 代码,代码如下 ```html {% extends 'layout/basic.html' %} {% load static %}
代码语言:txt
复制
{% block title %} 用户注册 {% endblock %}
代码语言:txt
复制
{% block css %}
代码语言:txt
复制
    <link rel="stylesheet" href="{% static 'css/account.css' %}">
代码语言:txt
复制
    <style>
代码语言:txt
复制
        .error-msg {
代码语言:txt
复制
            color: red;
代码语言:txt
复制
            position: absolute;
代码语言:txt
复制
            font-size: 13px;
代码语言:txt
复制
        }
代码语言:txt
复制
    </style>
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
{% block content %}
代码语言:txt
复制
    <div class="account">
代码语言:txt
复制
        <div class="title">用户注册</div>
代码语言:txt
复制
        <form id="form" method="POST" novalidate>
代码语言:txt
复制
            {% csrf_token %}
代码语言:txt
复制
            {% for field in form %}
代码语言:txt
复制
                {% if field.name == 'code' %}
代码语言:txt
复制
                    <div class="form-group">
代码语言:txt
复制
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
代码语言:txt
复制
                        <div class="row">
代码语言:txt
复制
                            <div class="col-xs-7">
代码语言:txt
复制
                                {{ field }}
代码语言:txt
复制
                                <span class="error-msg"></span>
代码语言:txt
复制
                            </div>
代码语言:txt
复制
                            <div class="col-xs-5">
代码语言:txt
复制
                                <input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
代码语言:txt
复制
                            </div>
代码语言:txt
复制
                        </div>
代码语言:txt
复制
                    </div>
代码语言:txt
复制
                {% else %}
代码语言:txt
复制
                    <div class="form-group">
代码语言:txt
复制
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
代码语言:txt
复制
                        {{ field }}
代码语言:txt
复制
                        <span class="error-msg"></span>
代码语言:txt
复制
                    </div>
代码语言:txt
复制
                {% endif %}
代码语言:txt
复制
            {% endfor %}
代码语言:txt
复制
            <div class="row">
代码语言:txt
复制
                <div class="col-xs-3">
代码语言:txt
复制
                    <input id="submit" type="button" class="btn btn-primary" value="注  册"/>
代码语言:txt
复制
                </div>
代码语言:txt
复制
            </div>
代码语言:txt
复制
        </form>
代码语言:txt
复制
    </div>
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
{% block js %}
代码语言:txt
复制
    <script>
代码语言:txt
复制
        // 页面框架加载完成之后自动执行函数
代码语言:txt
复制
        $(function () {
代码语言:txt
复制
            bindClickBtnSms();
代码语言:txt
复制
        });
代码语言:txt
复制
        /*
代码语言:txt
复制
        点击获取验证码的按钮绑定事件
代码语言:txt
复制
         */
代码语言:txt
复制
        function bindClickBtnSms() {
代码语言:txt
复制
            $('#btnSms').click(function () {
代码语言:txt
复制
                $('.error-msg').empty();
代码语言:txt
复制
                // 获取用户输入的手机号
代码语言:txt
复制
                // 找到输入框的ID,根据ID获取值,如何找到手机号的ID?
代码语言:txt
复制
                // Django ModelForm 默认生成字段ID为 “id_ + 字段名”
代码语言:txt
复制
                var mobilePhone = $('#id_mobile_phone').val();
代码语言:txt
复制
                // 发送ajax 请求,把手机号发送过去
代码语言:txt
复制
                $.ajax({
代码语言:txt
复制
                    url: "{% url 'send_sms' %}",  // 等价于 /send/sms/
代码语言:txt
复制
                    type: "GET",
代码语言:txt
复制
                    data: {mobile_phone: mobilePhone, tpl: "register"},  // 手机号和注册的模板
代码语言:txt
复制
                    dataType: "JSON",  // 将服务端返回的数据反序列化为字典
代码语言:txt
复制
                    success: function (res) {
代码语言:txt
复制
                        // ajax请求发送成功之后,自动执行的函数: res就是后端返回的值
代码语言:txt
复制
                        if(res.status) {
代码语言:txt
复制
                            sendSmsRemind();
代码语言:txt
复制
                        } else {
代码语言:txt
复制
                            // 错误信息
代码语言:txt
复制
                            // console.log(res);  // {status: False, error: { mobile_phone: ["错误信息", ] }
代码语言:txt
复制
                            $.each(res.error, function (key, value) {
代码语言:txt
复制
                                $("#id_" + key).next().text(value[0]);
代码语言:txt
复制
                            })
代码语言:txt
复制
                        }
代码语言:txt
复制
                    }
代码语言:txt
复制
                })
代码语言:txt
复制
            })
代码语言:txt
复制
        }
代码语言:txt
复制
        /*
代码语言:txt
复制
        倒计时
代码语言:txt
复制
         */
代码语言:txt
复制
        function sendSmsRemind() {
代码语言:txt
复制
            var $smsBtn = $('#btnSms');
代码语言:txt
复制
            // 将按钮变为不可点击
代码语言:txt
复制
            $smsBtn.prop('disabled', true);
代码语言:txt
复制
            var time = 60;
代码语言:txt
复制
            var remind = setInterval(function () {
代码语言:txt
复制
                $smsBtn.val(time + '秒重新发送');
代码语言:txt
复制
                time = time - 1;
代码语言:txt
复制
                if (time < 1) {
代码语言:txt
复制
                    clearInterval(remind);
代码语言:txt
复制
                    $smsBtn.val('点击获取验证码').prop('disabled', false);
代码语言:txt
复制
                }
代码语言:txt
复制
            }, 1000)
代码语言:txt
复制
        }
代码语言:txt
复制
    </script>
代码语言:txt
复制
{% endblock %}
代码语言:txt
复制
```

前端页面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 文件,代码如下 ```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); }
代码语言:txt
复制
.account .title {
代码语言:txt
复制
    font-size: 25px;
代码语言:txt
复制
    font-weight: bold;
代码语言:txt
复制
    text-align: center;
代码语言:txt
复制
}
代码语言:txt
复制
.account .form-group {
代码语言:txt
复制
    margin-bottom: 20px;
代码语言:txt
复制
}
代码语言:txt
复制
```
3.2.2 后端代码
3.2.2.1 URL
  • 首先,上面写到前端页面通过js 向后端发送ajax 请求,请求地址为 /send/sms/,那么我们首先要添加一个 urlweb/urls.py 中代码如下: ```python from django.conf.urls import url from web.views import account
代码语言:txt
复制
urlpatterns = [
代码语言:txt
复制
    url(r'^register/$', account.register, name='register'),  # register
代码语言:txt
复制
    url(r'^send/sms/$', account.send_sms, name='send_sms'),  # register
代码语言:txt
复制
]
代码语言:txt
复制
```
3.2.2.2 视图函数
  • 上面添加了路由,接下来是视图函数,在 web/views/account.py 文件中添加代码如下 ```python from django.shortcuts import render, HttpResponse from django.http import JsonResponse from web.forms.account import RegisterModelForm, SendSmsForm
代码语言:txt
复制
def register(request):
代码语言:txt
复制
    """ 注册 """
代码语言:txt
复制
    form = RegisterModelForm()
代码语言:txt
复制
    return render(request, 'register.html', {'form': form})
代码语言:txt
复制
def send_sms(request):
代码语言:txt
复制
    """ 发送短信 """
代码语言:txt
复制
    form = SendSmsForm(request, data=request.GET)
代码语言:txt
复制
    # 只是校验手机号:不能为空、格式是否正确
代码语言:txt
复制
    if form.is_valid():
代码语言:txt
复制
        return JsonResponse({'status': True})
代码语言:txt
复制
    return JsonResponse({'status': False, 'error': form.errors})
代码语言:txt
复制
```
3.2.2.3 配置文件
3.2.2.3.1 腾讯云短信配置文件
  • 对于腾讯云短信的配置文件,我们应该将其放置在 local_settings.py 文件中( local_settings.py 文件的作用,我在上一篇文章中提到过),同时要在 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)详细教程
  • local_settings.py 文件配置代码如下
    • 这里的 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文件中,代码如下: ```python 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 文件代码如下 ```python # -*- coding: UTF-8 -*- '''================================================= @Project -> File :MyDjango -> sms @IDE :PyCharm @Author :ruochen @Date :2020/6/21 15:57 @Desc : =================================================='''
代码语言:txt
复制
		import ssl
代码语言:txt
复制
		# ssl._create_default_https_context = ssl._create_unverified_context
代码语言:txt
复制
		from qcloudsms_py import SmsMultiSender, SmsSingleSender
代码语言:txt
复制
		from qcloudsms_py.httpclient import HTTPError
代码语言:txt
复制
		from django.conf import settings
代码语言:txt
复制
		def send_sms_single(phone_num, template_id, template_param_list):
代码语言:txt
复制
		    """
代码语言:txt
复制
		    单条发送短信
代码语言:txt
复制
		    :param phone_num: 手机号
代码语言:txt
复制
		    :param template_id: 腾讯云短信模板ID
代码语言:txt
复制
		    :param template_param_list: 短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板
代码语言:txt
复制
		    :return:
代码语言:txt
复制
		    """
代码语言:txt
复制
		    appid = settings.TENCENT_SMS_APP_ID  # 自己应用ID
代码语言:txt
复制
		    appkey = settings.TENCENT_SMS_APP_KEY  # 自己应用Key
代码语言:txt
复制
		    sms_sign = settings.TENCENT_SMS_SIGN  # 自己腾讯云创建签名时填写的签名内容(使用公众号的话这个值一般是公众号全称或简称)
代码语言:txt
复制
		    sender = SmsSingleSender(appid, appkey)
代码语言:txt
复制
		    try:
代码语言:txt
复制
		        response = sender.send_with_param(86, phone_num, template_id, template_param_list, sign=sms_sign)
代码语言:txt
复制
		    except HTTPError as e:
代码语言:txt
复制
		        response = {'result': 1000, 'errmsg': "网络异常发送失败"}
代码语言:txt
复制
		    return response
代码语言:txt
复制
		def send_sms_multi(phone_num_list, template_id, param_list):
代码语言:txt
复制
		    """
代码语言:txt
复制
		    批量发送短信
代码语言:txt
复制
		    :param phone_num_list:手机号列表
代码语言:txt
复制
		    :param template_id:腾讯云短信模板ID
代码语言:txt
复制
		    :param param_list:短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板
代码语言:txt
复制
		    :return:
代码语言:txt
复制
		    """
代码语言:txt
复制
		    appid = settings.TENCENT_SMS_APP_ID  # 自己应用ID
代码语言:txt
复制
		    appkey = settings.TENCENT_SMS_APP_KEY  # 自己应用Key
代码语言:txt
复制
		    sms_sign = settings.TENCENT_SMS_SIGN  # 自己腾讯云创建签名时填写的签名内容(使用公众号的话这个值一般是公众号全称或简称)
代码语言:txt
复制
		    sender = SmsMultiSender(appid, appkey)
代码语言:txt
复制
		    try:
代码语言:txt
复制
		        response = sender.send_with_param(86, phone_num_list, template_id, param_list, sign=sms_sign)
代码语言:txt
复制
		    except HTTPError as e:
代码语言:txt
复制
		        response = {'result': 1000, 'errmsg': "网络异常发送失败"}
代码语言:txt
复制
		    return response
代码语言:txt
复制
		```
代码语言:txt
复制
- 三: 将验证码存入redis数据库中,且超时时间为 60s【即60s后自动消失】,这样我们在提交表单的时候,就可以将用户输入的验证码同redis中存的进行比较,且如果时间超过 60s,用户就要重新获取验证码
	- redis的操作可以查看此篇文章:  [redis 下载安装 & python 操作redis & django 连接redis](https://blog.csdn.net/qq_29339467/article/details/107920255)
```python
#-*- 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
代码语言:txt
复制
from django_redis import get_redis_connection
代码语言:txt
复制
from web import models
代码语言:txt
复制
from web.forms.bootstrap import BootStrapForm
代码语言:txt
复制
from utils.tencent.sms import send_sms_single
代码语言:txt
复制
class RegisterModelForm(BootStrapForm, forms.ModelForm):
代码语言:txt
复制
    password = forms.CharField(
代码语言:txt
复制
        label='密码',
代码语言:txt
复制
        min_length=8,
代码语言:txt
复制
        max_length=64,
代码语言:txt
复制
        error_messages={
代码语言:txt
复制
            'min_length': "密码长度不能小于8个字符",
代码语言:txt
复制
            'max_length': "密码长度不能大于64个字符"
代码语言:txt
复制
        },
代码语言:txt
复制
        widget=forms.PasswordInput())
代码语言:txt
复制
    confirm_password = forms.CharField(
代码语言:txt
复制
        label='重复密码',
代码语言:txt
复制
        min_length=8,
代码语言:txt
复制
        max_length=64,
代码语言:txt
复制
        error_messages={
代码语言:txt
复制
            'min_length': "重复密码长度不能小于8个字符",
代码语言:txt
复制
            'max_length': "重复密码长度不能大于64个字符"
代码语言:txt
复制
        },
代码语言:txt
复制
        widget=forms.PasswordInput()
代码语言:txt
复制
    )
代码语言:txt
复制
    mobile_phone = forms.CharField(
代码语言:txt
复制
        label='手机号',
代码语言:txt
复制
        validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
代码语言:txt
复制
    code = forms.CharField(
代码语言:txt
复制
        label='验证码',
代码语言:txt
复制
        widget=forms.TextInput())
代码语言:txt
复制
    class Meta:
代码语言:txt
复制
        model = models.UserInfo
代码语言:txt
复制
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']
代码语言:txt
复制
class SendSmsForm(forms.Form):
代码语言:txt
复制
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
代码语言:txt
复制
    def __init__(self, request, *args, **kwargs):
代码语言:txt
复制
        super().__init__(*args, **kwargs)
代码语言:txt
复制
        self.request = request
代码语言:txt
复制
    def clean_mobile_phone(self):
代码语言:txt
复制
        """ 手机号校验的钩子 """
代码语言:txt
复制
        mobile_phone = self.cleaned_data['mobile_phone']
代码语言:txt
复制
        # 判断短信模板是否有问题
代码语言:txt
复制
        tpl = self.request.GET.get('tpl')
代码语言:txt
复制
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
代码语言:txt
复制
        if not template_id:
代码语言:txt
复制
            # self.add_error('mobile_phone', '短信模板错误')
代码语言:txt
复制
            raise ValidationError('短信模板错误')
代码语言:txt
复制
        # 检验数据库中是否已有手机号
代码语言:txt
复制
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
代码语言:txt
复制
        if exists:
代码语言:txt
复制
            raise ValidationError('手机号已存在')
代码语言:txt
复制
        # 发短信
代码语言:txt
复制
        code = random.randrange(1000, 9999)
代码语言:txt
复制
        # 发送短信
代码语言:txt
复制
        sms = send_sms_single(mobile_phone, template_id, [code, ])
代码语言:txt
复制
        if sms['result'] != 0:
代码语言:txt
复制
            raise ValidationError('短信发送失败,{}'.format(sms['errmsg']))
代码语言:txt
复制
        # 验证码写入redis(django-redis)
代码语言:txt
复制
        conn = get_redis_connection()
代码语言:txt
复制
        conn.set(mobile_phone, code, ex=60)
代码语言:txt
复制
        return mobile_phone
代码语言:txt
复制
```

4. 点击注册

4.1 前端: 获取数据 & 发送ajax请求

  • 收集表单中的数据(找到每一个字段)
  • 数据通过ajax发送到后台【POST请求】
  • register.html 文件中js 部分添加点击注册事件函数,代码如下(前面代码部分同上,只是在js 中添加了 bindClickSubmit 函数,并让其在页面框架加载完成后自动执行)ajax请求这里我没有再写一个URL,而是复用了 /register/,只需要判断用户发的是哪种请求就可以
    • 用户反正地址时发送的是 GET 请求,这时我们直接让其跳转到注册页面即可
    • 用户点击注册时,发送的是 POST 请求,这时我们进行表单验证 & 写入数据库等操作即可
代码语言:txt
复制
{% 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 文件
    - 代码如下 ```python import uuid import hashlib
代码语言:txt
复制
		from django.conf import settings
代码语言:txt
复制
		def md5(string):
代码语言:txt
复制
		    """ MD5加密 """
代码语言:txt
复制
		    hash_object = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
代码语言:txt
复制
		    hash_object.update(string.encode('utf-8'))
代码语言:txt
复制
		    return hash_object.hexdigest()
代码语言:txt
复制
		def uid(string):
代码语言:txt
复制
		    data = "{}-{}".format(str(uuid.uuid4()), string)
代码语言:txt
复制
		    return md5(data)
代码语言:txt
复制
		```
代码语言:txt
复制
- 验证码通过redis 根据手机号(键)获取值与用户输入的进行比较【注意存在过期时间】修改 
4.2.2 写入数据库
  • 数据校验成功后,即可将在数据库中创建一条记录,跳转到 /login/ 页面(登录页面下一篇博文具体介绍)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})
  • web/views/account.py 文件中代码修改如下:(只修改了 register 函数的内容,其余不变)

持续更新中,欢迎大家关注博主 :smile:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用户注册篇
    • 1. 前期准备
      • 1.1 腾讯云发送短信
      • 1.2 redis
    • 2. 注册页面展示
      • 2.1 创建app
      • 2.4 URL准备
      • 2.5 模型准备【models.py】
      • 2.6 ModelForm【简单校验 & 样式添加】
      • 2.7 前端页面
    • 3. 验证码获取
      • 3.1 思路
      • 3.2 具体实现
    • 4. 点击注册
      • 4.1 前端: 获取数据 & 发送ajax请求
      • 4.2 后端
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档