05-Day04-注册功能实现02

一、注册功能实现

  • 第一步 :点击收集数据 ajax 将数据发送到后端
  • 第二步 :后端获取到用户注册信息后进行对每个字段的数据校验
  • 第三步 :数据校验完成后写入数据库

1.1、用户注册提交ajax

  • 将前端提交按钮根据ID进行事件绑定

image

  • 将 form 标签定义一个 id;

image

  • 使用$('#Form标签').serialize(), 就可以获取到前端所有的标签的 name 及 value 都可以获取到以及 csrf token

image

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/register.html
{% extends 'layout/basic.html' %}
{% load static %}

{% block title %}用户注册{% endblock %}

{% block css %}
    {#  引入公共样式  #}
    <link rel="stylesheet" href="{% static 'css/account.css' %}">
    {#  错误信息样式  #}
    <style>
        .error-msg {
            color: red;
            position: absolute;
            font-size: 13px;
        }
    </style>
{% endblock %}

{% block content %}
    <div class="account">
        <div class="title">用户注册</div>
        <form id="regForm" method="POST" novalidate>
            {% csrf_token %}
            {% for field in form %}
                {% if field.name == 'code' %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        <div class="row">
                            <div class="col-xs-7">
                                {{ field }}
                                {#  后端数据错误信息展示 #}
                                <span class="error-msg"></span>

                            </div>
                            <div class="col-xs-5">
                                <input id="btnSms" type="button" class="btn btn-default" value="点击获取验证码">
                            </div>
                        </div>
                    </div>
                {% else %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="error-msg"></span>
                    </div>
                {% endif %}
            {% endfor %}

            <div class="row">
                <div class="col-xs-3">
                    <input id="btnSubmit" type="button" class="btn btn-primary" value="注  册"/>
                </div>
            </div>
        </form>
    </div>
{% endblock %}

{% block js %}
    <script>
    /*
    页面框架加载完成之后自动执行函数
    */
    $(function () {
        bindClickBtnSubmit();   // 点击获取验证码事件
        bindClickSubmit();      // 注册事件
    });
    /*
    点击提交,注册
    */
    function bindClickSubmit(){
        $('#btnSubmit').click(function () {
                $('.error-msg').empty();
                // 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
                // 数据ajax发送到后台
                $.ajax({
                    url: "{% url 'register' %}",
                    type: "POST",
                    data: $('#regForm').serialize(), // 所有字段数据 + csrf token
                    dataType: "JSON",
                    success: function (res) {
                        if(res.status){
                            location.href = res.data;	// 跳转到后端传入的 data 地址
                        }else{
                            $.each(res.error, function (key, value) {
                                $("#id_" + key).next().text(value[0]);
                            })
                        }
                    }
                })
            })
        }

    /*
    点击获取验证码的按钮 id=btnSms 绑定事件
     */
    function bindClickBtnSubmit() {
        $('#btnSms').click(function () {
            // 清空上次的错误提示信息
            $('.error-msg').empty();

            // 如何获取用户输入的手机号? :找到输入框ID,根据ID获取值
            // alert($('#id_mobile_phone').val());
            var mobilePhone = $('#id_mobile_phone').val();

            // 发送ajax请求,把手机号发送到后端
            $.ajax({                         // http://127.0.0.1:8000/index/?mobile_phone=mobilePhone&tpl=register
                url: "{% url 'send_sms' %}", // 后端的地址,反向生成,直接使用 urls 中的name名称生成 url ,等价 /send/sms
                type: "GET",                 // 方法
                data: { mobile_phone: mobilePhone, tpl: "register" },    // data : mobile_phone=用户手机号,tpl=注册短信模板
                dataType: "JSON",   // 将服务端返回的数据反序列化为字典
                success: function (res) {    // success 是 ajax 请求发送成功之后,自动执行的函数 , res表示后端返回的值
                    // res_doct = JSON.parse(res);
                    if (res.status){
                        console.log('发送成功,倒计时')
                        sendSmsRemind();    // 启动定时器
                    } else {
                       // 错误信息
                       console.log(res);    // {status: False, error:{ mobile_phone: ["错误信息"]}}
                        // $.each() 循环
                        $.each(res.error, function (key, value){
                            $("#id_" + key).next().text(value[0]);
                        })
                    }
                }
            })
        })
    }

    /*
    倒计时,定时器
    */
    function sendSmsRemind() {
        var $smsBtn = $('#btnSms');
        $smsBtn.prop('disabled', true); // 禁用
        var time = 60;
        var remind = setInterval(function () {
            $smsBtn.val(time + '秒重新发送');
            time = time - 1;
            if (time < 1) {
                clearInterval(remind);
                $smsBtn.val('点击获取验证码').prop('disabled', false);
            }
        }, 1000)
    }

    </script>
{% endblock %}

1.2、数据校验

  • 校验还使用 ModelForm钩子函数进行校验;
  • form.cleaned_data 用于获取 Form 校验成功的字段
  • form.errors 用于获取 Form 校验失败的字段

  • 第一步 :用户名校验,先在 models 中为 username 用户名字段创建索引,提升查询速度;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/models.py 
from django.db import models
# Create your models here.

class UserInfo(models.Model):
    """
    CharField : 字符串类型字段
    verbose_name : 字段注释
    max_length : 字段长度
    Django ORM 中也提供了一个 EmailField 但是本质上 还是 CharField
    """
    username = models.CharField(verbose_name='用户名', max_length=32, db_index=True)
    email = models.EmailField(verbose_name='邮箱', max_length=32)
    mobile_phone = models.CharField(verbose_name='手机号', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=32)
  • 第二步 :用户名钩子函数校验,不允许重名;
# Form

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single

class RegisterModelForm(forms.ModelForm):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])
    password = forms.CharField(label='密码',widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

    def clean_username(self):
        """
        用户名校验的钩子函数
        """
        username = self.cleaned_data['username']

        exists = models.UserInfo.objects.filter(username=username).exists()
        if exists:
            raise ValidationError('用户名存在')
        return username
  • 第三步 :邮箱校验钩子函数,不允许重复
# Form

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single


class RegisterModelForm(forms.ModelForm):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])
    password = forms.CharField(label='密码',widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

    def clean_username(self):
        """
        用户名校验的钩子函数
        """
        username = self.cleaned_data['username']
        exists = models.UserInfo.objects.filter(username=username).exists()
        if exists:
            raise ValidationError('用户名已存在')
        return username

    def clean_email(self):
        """
        邮箱校验的钩子函数
        """
        email = self.cleaned_data['email']
        exists = models.UserInfo.objects.filter(email=email).exists()
        if exists:
            raise ValidationError('邮箱已存在')
        return email
  • 第四步 :密码校验,长度限制(在Form校验时可指定),在钩子函数中进行密码和确认密码判断
    • Form 中的字段校验方式是从上到下的顺序;所以需要在 From 中调整此前定义的顺序,不然clean_confirm_password() 确认密码钩子函数中无法获取到在confirm_password字段之后校验的字段;
# Form

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single


class RegisterModelForm(forms.ModelForm):
    password = forms.CharField(label='密码',
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

...
...

    def clean_confirm_password(self):
        """
        密码和确认密码对比校验
        """
        pwd = self.cleaned_data['password']
        confirm_pwd = self.cleaned_data['confirm_password']
        if pwd != confirm_pwd:
            raise ValidationError('两次密码不一致')
        return confirm_pwd
  • 第五步 : 手机号校验钩子函数
# Form

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single


class RegisterModelForm(forms.ModelForm):
    password = forms.CharField(label='密码',
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

...
...

    def clean_mobile_phone(self):
        """
        手机号校验钩子函数
        """
        mobile_phone = self.cleaned_data['mobile_phone']
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            raise ValidationError('手机号已注册')
        return mobile_phone
  • 第六步 :验证码校验钩子函数,从redis读取对比是否正确,或是否超时
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single


class RegisterModelForm(forms.ModelForm):
    password = forms.CharField(label='密码',
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

...
...

    def clean_code(self):
        code = self.cleaned_data['code']
        # mobile_phone = self.cleaned_data['mobile_phone']
        mobile_phone = self.cleaned_data.get('mobile_phone')
        if not mobile_phone:
            return code

        conn = get_redis_connection()
        redis_code = conn.get(mobile_phone)
        if not redis_code:
            raise ValidationError('验证码失效或未发送,请重新发送')
        redis_str_code = redis_code.decode('utf-8')
        if code.strip() != redis_str_code:
            raise ValidationError('验证码错误,请重新输入')
        return code

1.3、成功入库并跳转

  • 第一步 :对密码字段使用钩子函数进行密码加密
utils/encrypt.py  MD5 加密工具 (将SECRET_KEY当做盐)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat utils/encrypt.py 
# -*- coding:utf-8 -*-
import uuid
import hashlib

from django.conf import settings

def md5(string):
    """ MD5加密 """
    hash_object = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
    hash_object.update(string.encode('utf-8'))
    return hash_object.hexdigest()


def uid(string):
    data = "{}-{}".format(str(uuid.uuid4()), string)
    return md5(data)
  • 第二步 :对密码字段使用钩子函数,并进行MD5加密,确认密码也同理
# Form
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/forms/account.py 
from django import forms
from web_app import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.conf import settings
from django_redis import get_redis_connection
import random
from utils.tencent.sms import send_sms_single
from utils import encrypt


class RegisterModelForm(forms.ModelForm):
    password = forms.CharField(label='密码',
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   "min_length": "密码长度不能小于8个字符",
                                   "max_length": "密码长度不能大于64个字符",
                               },
                               widget=forms.PasswordInput())
    confirm_password = forms.CharField(label='重复密码', widget=forms.PasswordInput())
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    code = forms.CharField(label='验证码', widget=forms.TextInput())

    #  model 定义元数据
    class Meta:
        # 对应的Model类
        model = models.UserInfo
        # Model类中哪些字段可以展示,__all__ 表示所有
        # fields = '__all__' 也表示默认的展示顺序,可以手动指定展示顺序
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def __init__(self, *args, **kwargs):
        """
        重写RegisterModelForm 的 初始化方法
            name  表示字段名称
            field 表示forms.CharField对象
                code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "请输入验证码"}))
        """
        super(RegisterModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

...
...
    def clean_password(self):
        pwd = self.cleaned_data['password']
        # 加密 & 返回
        return encrypt.md5(pwd)

    def clean_confirm_password(self):
        pwd = self.cleaned_data.get('password')

        confirm_pwd = encrypt.md5(self.cleaned_data['confirm_password'])

        if pwd != confirm_pwd:
            raise ValidationError('两次密码不一致')

        return confirm_pwd
...
...
  • 第三步 :校验完成后,返回跳转地址即登录地址
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py

"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from web_app.forms.account import RegisterModelForm,SendSmsForm

def register(request):
    """ 注册 """
    if request.method =='GET':
        form = RegisterModelForm()
        return render(request, 'register.html', {'form': form})
    # POST
    # form 数据校验
    form = RegisterModelForm(data=request.POST)
    # 若form 数据校验成功
    if form.is_valid():
        # print(form.cleaned_data)    # form.cleaned_data 存放校验成功的数据

        # 写入数据库也可以使用create方法,但是需要手动剔除不存在的字段
        # from web_app import models
        # data = form.cleaned_data
        # data.pop('code')
        # data.pop('confirm_password')
        # instance = models.UserInfo.objects.create(**data)

        # 验证通过,写入数据库 (数据库密码加密) ,通过save()方法进行保存数据库,还可以将那些models中不存在的字段会自动剔除
        instance = form.save()

        return JsonResponse({'status': True, 'data': '/login/'})
    return JsonResponse({'status': False, 'error': form.errors})	# 如果校验未通过则返回 Form 校验的错误信息
  • 前端根据后端转入的 data 字段,进行跳转;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/register.html 

...
...

    /*
    点击提交,注册
    */
    function bindClickSubmit(){
        $('#btnSubmit').click(function () {
               // 清空上次的错误提示信息
                $('.error-msg').empty();
                // 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
                // 数据ajax发送到后台
                $.ajax({
                    url: "{% url 'register' %}",
                    type: "POST",
                    data: $('#regForm').serialize(), 				// 所有字段数据 + csrf token
                    dataType: "JSON",
                    success: function (res) {
                        if(res.status){
                            location.href = res.data;				// 根据后端返回的 data 进行跳转
                        }else{
                            $.each(res.error, function (key, value) {	// 错误信息
                                $("#id_" + key).next().text(value[0]);
                            })
                        }
                    }
                })
            })
        }
...
...
posted @ 2021-07-04 12:07  SRE运维充电站  阅读(135)  评论(0编辑  收藏  举报