04-Day03-注册功能实现01

一、Python快速操作Redis

1.1、什么是redis?

  • 官方:Redis是一个使用 C语言 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。
  • 白话:Redis是一个软件,这个软件可以帮助我们维护一部分内存,让我们往那块内存中进行存取值。如果数据在内存中存储,遇到宕机那么数据就会丢失,而redis解决了这个问题,他可以将内存中的数据以某种策略存储到硬盘,以保证宕机数据不丢失。

1.2、python连接redis

image

  • 第一步:安装python操作redis模块
pip3 install redis
  • 第二步:写代码去操作redis
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
# 直接连接redis
conn = redis.Redis(host='10.211.55.218', port=6379, password='foobared', encoding='utf-8')
# 设置键值:15131255089="9999" 且超时时间为10秒(值写入到redis时会自动转字符串)
conn.set('12345678910', 9999, ex=10)
# 根据键获取值:如果存在获取值(获取到的是字节类型);不存在则返回None
value = conn.get('12345678910')
print(value)
  • 上面python操作redis的示例是以直接创建连接的方式实现,每次操作redis如果都重新连接一次效率会比较低,建议使用redis连接池来替换,例如:
import redis
# 创建redis连接池(默认连接池最大连接数 2**31=2147483648)
pool = redis.ConnectionPool(host='10.211.55.218', port=6379, password='foobared', encoding='utf-8', max_connections=1000)
# 去连接池中获取一个连接
conn = redis.Redis(connection_pool=pool)
# 设置键值:15131255089="9999" 且超时时间为10秒(值写入到redis时会自动转字符串)
conn.set('name', "字符串", ex=10)
# 根据键获取值:如果存在获取值(获取到的是字节类型);不存在则返回None
value = conn.get('name')
print(value)

1.3、django连接redis

  • 按理说搞定上一步python代码操作redis之后,在django中应用只需要把上面的代码写到django就可以了。
  • 例如:django的视图函数中操作redis
import redis
from django.shortcuts import HttpResponse
# 创建redis连接池
POOL = redis.ConnectionPool(host='10.211.55.28', port=6379, password='foobared', encoding='utf-8', max_connections=1000)
def index(request):
    # 去连接池中获取一个连接
    conn = redis.Redis(connection_pool=POOL)
    conn.set('name', "武沛齐", ex=10)
    value = conn.get('name')
    print(value)
    return HttpResponse("ok")
  • 上述可以实现在django中操作redis。但是,这种形式有点非主流,因为在django中一般不这么干,而是用另一种更加简便的的方式。

  • 第一步:安装django-redis模块(内部依赖redis模块)

pip3 install django-redis
  • 第二步:在django项目的settings.py中添加相关配置
# 上面是django项目settings中的其他配置....
######## Redis ########
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379", # 安装redis的主机的 IP 和 端口
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            # 连接池
            "CONNECTION_POOL_KWARGS": {
                "max_connections": 1000,
                "encoding": 'utf-8'
            },
            # "PASSWORD": "foobared" # redis密码
        }
    },
    # "master": {
    #     "BACKEND": "django_redis.cache.RedisCache",
    #     "LOCATION": "redis://127.0.0.1:6379",  # 安装redis的主机的 IP 和 端口
    #     "OPTIONS": {
    #         "CLIENT_CLASS": "django_redis.client.DefaultClient",
    #         # 连接池
    #         "CONNECTION_POOL_KWARGS": {
    #             "max_connections": 1000,
    #             "encoding": 'utf-8'
    #         },
    #         # "PASSWORD": "foobared" # redis密码
    #     }
    # }
}
  • 第三步:在django的视图中操作redis
from django.shortcuts import HttpResponse
from django_redis import get_redis_connection
def index(request):
    # 去连接池中获取一个连接 -> default 表示连接 redis配置中名称为default的连接项,多用于读写分离场景
    conn = get_redis_connection("default")	# 默认 default
    conn.set('nickname', "你好", ex=10)
    value = conn.get('nickname')
    print(value)
    return HttpResponse("OK")

二、注册功能实现

2.1、多app模板注册,目录规范

  • 第一步 :创建web_app应用,并进行注册
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % python3 manage.py startapp web_app
# settings.py

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users_app.apps.UsersAppConfig',
    'web_app.apps.WebAppConfig'
]
  • 第二步 :对web_app应用目录进行调整
  • 注意 :开发规范
    • 将template目录(模板)创建在每个APP目录中
    • 模板的查找顺序 :
      • 首先,查找 项目根目录下 template目录下查找模板,如 Bug_manager/template
      • 其次,通过Settings配置文件中的 INSTALLED_APPS 应用注册顺序,去每一个 APP 里面的 template 目录进行模板查找
# 如果项目庞大,每个APP中前端页面有同名的情况设计模板目录建议使用下面组织方式 :
Bug_manager |
	user_app |	# APP1
		templates|
			user_app|
				index.html
		views|
			index.py|
			# return render(request, 'user_app/index.html', {'form': payload})
	web_app |	# APP2
		templates|
			web_app|
				index.html
		views|
			index.py|
			# return render(request, 'web_app/index.html', {'form': payload})
# 本web_app项目目录调整如下 :
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % tree web_app
web_app
├── admin.py
├── apps.py
├── migrations
│   ├── __init__.py
│   └── __pycache__
│       └── __init__.cpython-38.pyc
├── forms		# 项目 ModelForm
├── models.py
├── templates	# 
│   ├── layout	# 	web_app 母模板
│   └── register.html
├── static		# 	web_app 静态资源(查找顺序与templates模板同理)
│   ├── css
│   ├── img
│   ├── js
│   └── plugin	# 前端用到的插件,如Bootstrap

├── tests.py
└── views		# 
    ├── __init__.py
    └── account.py

2.2、Templates注册母模板准备

  • 母模板通常需要定义如下四个块 ,生成可以被子模板重写;
    • title 标题
    • css 样式
    • content 头部菜单,页面主体
    • js

image
image

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % ls web_app/static/plugin 
bootstrap

image
image

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % ls web_app/static/plugin 
bootstrap       font-awesome

image
image

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % ls web_app/static/js 
jquery-3.6.0.min.js
  • 第三步 :basic.html 母模板引入公共样式及声明子模板可重写的模块;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.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' %}">

    {#  css 样式  #}
    {% block css %}{% endblock %}

</head>
<body>
    {# 头部菜单,页面主体  #}
    <h1>头部菜单,页面主体</h1>
    {% block content %}{% endblock %}

    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
    <script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script>
    {#   js #}
    {% block js %}{% endblock %}

</body>
</html>
  • 第四步 :子模板就开始继承或重写母模板的内容;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/register.html    
{% extends 'layout/basic.html' %}

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

{% block content %}主体{% endblock %}%   

2.3、urls路由处理

  • 实现路由分发,分而治之
  • 主路由入口
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat Bug_manager/urls.py 
"""Bug_manager URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include


urlpatterns = [
    path('admin/', admin.site.urls),
    # namespace 表示,所有由此条主路由分发的子路由,如果想实现反向解析时必须写为 "namespace :子路由"
    url(r'^users_app/', include('users_app.urls', namespace='users_app')),	
    url(r'^web_app/', include('web_app.urls')),
]

  • 子路由
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py 
"""Bug_manager URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
from web_app.views import account

urlpatterns = [
    url(r'^send/sms/', account.send_sms, name='sms'),
    url(r'^register/', account.register, name='register'),		# name 的名称起到可以根据url名称实现反向解析
]

2.4、导航条处理

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/layout/basic.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>
    {#  css 样式  #}
    {% 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="#">BugManager</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
          <li><a href="#">产品功能</a></li>
          <li><a href="#">企业方案</a></li>
          <li><a href="#">帮助文档</a></li>
          <li><a href="#">价格参考</a></li>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

    {% block content %}{% endblock %}

    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
    <script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script>
    {#   js #}
    {% block js %}{% endblock %}

</body>
</html>

image

2.5、注册页面展示

  • 第一步 :公共css样式 :用于注册、登录
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/static/css/account.css 
.account {
    width: 400px;
    margin-top: 30px;
    margin-left: auto;
    margin-right: auto;
    border: 1px solid #f0f0f0;
    padding: 10px 30px 30px 30px;
    -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
    box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
}

.account .title {
    font-size: 25px;
    font-weight: bold;
    text-align: center;
}

.account .form-group {
    margin-bottom: 20px;
}
  • 第二步 :主页模板,引入公共css
(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' %}">
{% 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 %}
  • 第三步 :将ModelForm存放到执行的账户相关的文件
# models.py
(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)
    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)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % python3 manage.py makemigrations
Migrations for 'web_app':
  web_app/migrations/0001_initial.py
    - Create model UserInfo

(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % python3 manage.py migrate       
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users_app, web_app
Running migrations:
  Applying web_app.0001_initial... OK
# forms/account.py

(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

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)
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render
from web_app.forms.account import RegisterModelForm

def register(request):
    form = RegisterModelForm()
    return render(request, 'register.html', {'form': form})

image

2.6、获取验证码思路

  • 第一步 :按钮绑定点击事件
  • 第二步 :获取手机号
  • 第三步 :发送Ajax请求
  • 第四步 :手机号校验
    • 不为空、格式校验、首次注册
  • 第五步 :通过验证
    • 发送短信,将短信保存至redis

2.7、获取手机号并发送和Ajax请求

  • 第一步 :按钮绑定点击事件,获取手机号
    • 在后端 ModelForm 帮我们生成的字段名称,会在前端渲染的时候也会自动帮我们生成一个 id 属性如下图示例 : (id=id_字段名)

image

{% block js %}
    <script>
    /*
    页面框架加载完成之后自动执行函数
    */
    $(function () {
        bindClickBtnSubmit();
    });

    /*
    点击获取验证码的按钮 id=btnSms 绑定事件
     */
    function bindClickBtnSubmit() {
        $('#btnSms').click(function () {
            // 如何获取用户输入的手机号? :找到输入框ID,根据ID获取值
            // alert($('#id_mobile_phone').val());
            var mobilePhone = $('#id_mobile_phone').val();
        })
    }
    </script>
{% endblock %}
  • 第二步 :发送Ajax请求,将用户手机号发送到后端
// 仅为 ajax 发送请求示例演示

    <script>
    /*
    页面框架加载完成之后自动执行函数
    */
    $(function () {
        bindClickBtnSubmit();
    });

    /*
    点击获取验证码的按钮 id=btnSms 绑定事件
     */
    function bindClickBtnSubmit() {
        $('#btnSms').click(function () {
            // 如何获取用户输入的手机号? :找到输入框ID,根据ID获取值
            // alert($('#id_mobile_phone').val());
            var mobilePhone = $('#id_mobile_phone').val();

            // 发送ajax请求,把手机号发送到后端
            $.ajax({                        // http://127.0.0.1:8000/index/?k1=123&k2=567
                url: "/index/",             // 后端的地址
                type: "GET",                // 方法
                data: {k1: 123, k2: 567},    // data
                success: function (res) {   // success 是 ajax 请求发送成功之后,自动执行的函数 , res表示后端返回的值

                }
            })

        })
    }
    </script>
(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' %}">

{% 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();
    });

    /*
    点击获取验证码的按钮 id=btnSms 绑定事件
     */
    function bindClickBtnSubmit() {
        $('#btnSms').click(function () {
            // 如何获取用户输入的手机号? :找到输入框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=注册短信模板
                success: function (res) {    // success 是 ajax 请求发送成功之后,自动执行的函数 , res表示后端返回的值
                    console.log(res);
                }
            })

        })
    }
    </script>
{% endblock %}
  • 第三步 :后端获取手机号,并返回前端成功获取
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/urls.py 
from django.conf.urls import url, include
from web_app.views import account

urlpatterns = [
    url(r'^send/sms/', account.send_sms, name='send_sms'),	# name 字段用于反向解析
    url(r'^register/', account.register, name='register'),
]
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse
from web_app.forms.account import RegisterModelForm

def register(request):
    form = RegisterModelForm()
    return render(request, 'register.html', {'form': form})

def send_sms(request):
    print(request.GET)
    return HttpResponse('成功')

image

# 后端日志
[03/Jul/2021 10:28:53] "GET /static/plugin/bootstrap/css/bootstrap.min.css.map HTTP/1.1" 200 540434
<QueryDict: {'mobile_phone': ['15354210326'], 'tpl': ['register']}>
[03/Jul/2021 10:28:57] "GET /web_app/send/sms/?mobile_phone=15354210326&tpl=register HTTP/1.1" 200 6

2.8、手机号格式校验

  • 知识点 :
    • form.Form -> is_valid :字段校验规则
    • form.Form -> clean :钩子函数,字段格式通过验证都会触发下面的clean方法
    • form.Form -> 重写__init__ ,增加传递的参数,然后views视图将request传入Form进行校验(实现视图的request传递到Form)
# 视图
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/views/account.py 
"""
用户账户相关功能 :注册、登录、短信、注销
"""
from django.shortcuts import render, HttpResponse
from web_app.forms.account import RegisterModelForm,SendSmsForm

def register(request):
    form = RegisterModelForm()
    return render(request, 'register.html', {'form': form})

def send_sms(request):
    # 校验方法一
    # mobile_phone = request.GET.get('mobile_phone')
    # tpl = request.GET.get('tpl')

    # 校验方法二 将前端数据数据使用Form校验,SendSmsForm中定义的仅校验手机号
    form = SendSmsForm(request, data=request.GET)
    # is_valid 只是校验手机号,不能为空、格式正确
    if form.is_valid():
        pass

    return HttpResponse('成功')
# 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

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)


class SendSmsForm(forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super(SendSmsForm, self).__init__(*args, **kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """手机号校验钩子函数 (单个字段方法如 def cleand_字段名())"""
        mobile_phone = self.cleaned_data['mobile_phone']
        # 判断短信模板是否有问题
        tpl = self.request.GET.get('tpl')
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        if not template_id:
            raise ValidationError('短信模板错误')

        # 校验数据库是否存在
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            raise ValidationError('手机号已存在')
        return mobile_phone

2.9、发送短信和redis存储

  • 知识点 :
    • views视图中可以直接使用 form.errors 获取到Form中的异常
# 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):
....
....

class SendSmsForm(forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super(SendSmsForm, self).__init__(*args, **kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """手机号校验钩子函数 (单个字段方法如 def cleand_字段名())"""
        mobile_phone = self.cleaned_data['mobile_phone']
        # 判断短信模板是否有问题
        tpl = self.request.GET.get('tpl')
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        if not template_id:
            raise ValidationError('短信模板错误')

        # 校验数据库是否存在
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            raise ValidationError('手机号已存在')

        # 发短信
        code = random.randrange(1000, 9999)
        sms = send_sms_single(mobile_phone, template_id, [code, ])
        if sms['result'] != 0:
            raise ValidationError('短信发送失败,{}'.format(sms['errmsg']))

        # 验证码redis(django-redis)
        conn = get_redis_connection()
        conn.set(mobile_phone, code, ex=60)

        return mobile_phone
# views视图

(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):
    form = RegisterModelForm()
    return render(request, 'register.html', {'form': form})

def send_sms(request):
    # 校验方法一
    # mobile_phone = request.GET.get('mobile_phone')
    # tpl = request.GET.get('tpl')

    # 校验方法二 将前端数据数据使用Form校验,SendSmsForm中定义的仅校验手机号
    form = SendSmsForm(request, data=request.GET)
    # is_valid 只是校验手机号,不能为空、格式正确
    if form.is_valid():
        return JsonResponse({'status': True})
    return JsonResponse({'status': False, 'error': form.errors})

2.10、短信验证错误信息

image

  • 需要将后端返回的错误信息反馈给浏览器页面供用户查看;
  • 知识点 :
    • 前端 ajax 请求增加 dataType: "JSON" 属性,会将后端传入来的 HttpResponse 字符串也会解析成JSON,前端使用res_doct = JSON.parse(res); 转换接收;
    • 如果后使用JsonResponse 传入前端的数据默认就是JSON数据类型;
(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();
    });

    /*
    点击获取验证码的按钮 id=btnSms 绑定事件
     */
    function bindClickBtnSubmit() {
        $('#btnSms').click(function () {
            // 如何获取用户输入的手机号? :找到输入框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('发送成功,倒计时')
                    } else {
                       // 错误信息
                       console.log(res);    // {status: False, error:{ mobile_phone: ["错误信息"]}}
                        // $.each() 循环
                        $.each(res.error, function (key, value){
                            $("#id_" + key).next().text(value[0]);  // 拼接标签后,找下一个标签就是对应的提示的 span标签
                        })
                    }
                }
            })

        })
    }
    </script>
{% endblock %}

image

  • 进一步优化,在每一次提示失败后,下一个点击获取都应该清空上次的错误提示信息;
(Bug_manager) daizhe@daizhedeMacBook-Pro Bug_manager % cat web_app/templates/register.html 
...
...

{% block js %}
    <script>
    /*
    页面框架加载完成之后自动执行函数
    */
    $(function () {
        bindClickBtnSubmit();
    });

    /*
    点击获取验证码的按钮 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('发送成功,倒计时')
                    } else {
                       // 错误信息
                       console.log(res);    // {status: False, error:{ mobile_phone: ["错误信息"]}}
                        // $.each() 循环
                        $.each(res.error, function (key, value){
                            $("#id_" + key).next().text(value[0]);
                        })
                    }
                }
            })

        })
    }
    </script>
{% endblock %}
  • 发送短信测试,查看redis

image

2.11、Form钩子中的错误处理

  • 本质上使用 在Form 使用 raise和 self.add_error() 是等价的,但是推荐使用raise 因为Python 遇到raise主动抛出异常,下面的代码不会继续执行;

  • 使用raise
class SendSmsForm(forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super(SendSmsForm, self).__init__(*args, **kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """手机号校验钩子函数 (单个字段方法如 def cleand_字段名())"""
        mobile_phone = self.cleaned_data['mobile_phone']
        # 判断短信模板是否有问题
        tpl = self.request.GET.get('tpl')
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        if not template_id:
            raise ValidationError('短信模板错误')

        # 校验数据库是否存在
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            raise ValidationError('手机号已存在')

        # 发短信
        code = random.randrange(1000, 9999)
        sms = send_sms_single(mobile_phone, template_id, [code, ])
        if sms['result'] != 0:
            raise ValidationError('短信发送失败,{}'.format(sms['errmsg']))

        # 验证码redis(django-redis)
        conn = get_redis_connection()
        conn.set(mobile_phone, code, ex=60)

        return mobile_phone
  • 使用self.add_error('xxxx')
class SendSmsForm(forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'),])

    # 重写__init__ 仅为在views视图调用Form初始化函数式多传递request参数
    def __init__(self, request, *args, **kwargs):
        super(SendSmsForm, self).__init__(*args, **kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """手机号校验钩子函数 (单个字段方法如 def cleand_字段名())"""
        mobile_phone = self.cleaned_data['mobile_phone']
        # 判断短信模板是否有问题
        tpl = self.request.GET.get('tpl')
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        if not template_id:
            self.add_error('短信模板错误')
            # raise ValidationError('短信模板错误')

        # 校验数据库是否存在
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            self.add_error('手机号已存在')
            # raise ValidationError('手机号已存在')

        # 发短信
        code = random.randrange(1000, 9999)
        sms = send_sms_single(mobile_phone, template_id, [code, ])
        if sms['result'] != 0:
            raise ValidationError('短信发送失败,{}'.format(sms['errmsg']))

        # 验证码redis(django-redis)
        conn = get_redis_connection()
        conn.set(mobile_phone, code, ex=60)

        return mobile_phone

2.12、验证码成功倒计时

  • 知识点:

    • 前端 :disabled 属性,当点击验证码发送成功倒计时之后,点击获取验证码之后应该是不可以点击的,直到下一次获取时间才可以进行点击;
    // 根据 标点ID 选择
    $("#btnSms").prop("disabled", true);   添加disabled 属性,不可操作;
    $("#btnSms").prop("disabled", flase);  移除disabled 属性,可操作;
    
    • 前端 :js定时器
    // 创建定时器
    var obj = setInterval(function() {
    	console.log("每隔1m执行一次");
    }, 1000)
    
    // 关闭定时器
    clearInterval(obj);
    
(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();
    });

    /*
    点击获取验证码的按钮 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 %}

image

三、知识点补充

3.1、Django之Forms_字段类型_参数

核心通用参数:

  • 1、rqeuired 字段是否为必填 默认为True
  • 2、label 类似于输入框前边的提示信息
  • 3、initial 初始值(占位符)就是给出一个默认值
  • 4、help_text 字段的辅助描述
  • 5、error_message={} 覆盖{{form.name.error}}信息
  • 6、disable 字段是否可以修改
  • 7、widget 重要参数。
    • 首先要认识到:::表单field类型字段负责验证输入并直接在模板中使用。而Widget负责渲染网页上HTML表单的输入元素和提取提交的原始数据。
    • 1、所以可以指定字段的html表现形式比如widget=forms.Textarea(变现为一个多行文本输入框)
    • 2、其他的参数类似于一下
    BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
    birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES))
    
    • 3、添加css样式。使用到widget=forms.Textinput(attrs={})参数,这个参数制定一个字典。在html表现形式就是input标签中的{键=“值”}这样的
      • 所以可以是widget=forms.Textinput(attrs={'class':'header'})或者widget=forms.Textinput(attrs={'size':'40'})

3.2、Django之Form组件is_valid校验机制

3.2.1、先来归纳一下整个流程

  • 1)首先is_valid()起手,看seld.errors中是否值,只要有值就是flase
  • 2)接着分析errors.里面判断_errors是都为空,如果为空返回self.full_clean(),否则返回self._errors
  • 3)现在就要看full_clean(),是何方神圣了,里面设置_errors和cleaned_data这两个字典,一个存错误字段,一个存储正确字段。
  • 4)在full_clean最后有一句self._clean_fields(),表示校验字段
  • 5)在_clean_fields函数中开始循环校验每个字段,真正校验字段的是field.clean(value),怎么校验的不管
  • 6)在_clean_fields中可以看到,会将字段分别添加到_errors和cleaned_data这两个字典中
  • 7)结尾部分还设置了钩子,找clean_XX形式的,有就执行。执行错误信息也会添加到_errors中
  • 8)整个校验过程完成

PS 参考博文 :https://www.cnblogs.com/ellisonzhang/p/10709970.html

3.2.2、下面分析form组件中is_valid校验的流程

#在分析过程中重点关注_erroes和clean_data这两个字典
def login(request):
    if request.method == "POST":
        form_obj = LoginForm(request.POST)
        if form_obj.is_valid():
            #如果检验全部通过
            print(form_obj.clean_data) #这里全部都没问题
            return HttpResponse("你好,欢迎回来!")
        else:
            #print(form_obj.clean_data)
            #print(form_obj.errors)
            return render(request, "login.html", {"form_obj": form_obj,)

    form_obj = LoginForm()
    return render(request, "login.html", {"form_obj": form_obj})

3.2.3、钩子代码实例

def clean_user(self):
    val1 = self.cleaned_data.get("user")
    #从正确的字段字典中取值
    #如果这个字符串全部都是由数组组成
    if not val1.isdigit():
        return val1
    else:
        # 注意这个报错信息已经确定了
        raise ValidationError("用户名不能全部是数字组成")
        #在校验的循环中except ValidationError as e:,捕捉的就是这个异常
        #所以能将错误信息添加到_errors中

3.2.4、代码分析部分

#代码分析部分
def is_valid(self):
    """
    Returns True if the form has no errors. Otherwise, False. If errors are
    being ignored, returns False.
    如果表单没有错误,则返回true。否则为假。如果错误是被忽略,返回false。
    """
    return self.is_bound and not self.errors
    #is_bound默认有值
    #只要self.errors中有一个值,not True = false,返回的就是false

def errors(self):
    """
    Returns an ErrorDict for the data provided for the form
    返回一个ErrorDict在form表单存在的前提下
    """
    if self._errors is None:
        self.full_clean()
    return self._errors

def full_clean(self):
    """
    Cleans all of self.data and populates self._errors and self.cleaned_data.
    清除所有的self.data和本地的self._errors和selif.cleaned_data
    """
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing.停止进一步的处理
        return
    self.cleaned_data = {}

    """
    # If the form is permitted to be empty, and none of the form data has
    # changed from the initial data, short circuit any validation.
    #如果表单允许为空,和原始数据也是空的话,允许不进行任何验证
    """

    if self.empty_permitted and not self.has_changed():
        return

    self._clean_fields()   #字面意思校验字段
    self._clean_form()
    self._post_clean()

def _clean_fields(self):
    #每个form组件实例化的过程中都会创建一个fields。fields实质上是一个字典。
    #储存着类似{"user":"user规则","pwd":"pwd的规则对象"}
    for name, field in self.fields.items():
        #name是你调用的一个个规则字段,field是调用字段的规则
        #items是有顺序的,因为他要校验字段的一致性
        """
        # value_from_datadict() gets the data from the data dictionaries.
        # Each widget type knows how to retrieve its own data, because some
        # widgets split data over several HTML fields.
        
        value_from_datadict()从数据字典中获取数据。
        每个部件类型知道如何找回自己的数据,因为有些部件拆分数据在几个HTML字段。
        """
        #现在假设第一个字段是user
        if field.disabled:
            value = self.get_initial_for_field(field, name)
        else:
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
        try:
            if isinstance(field, FileField):  #判断是不是文件
                #你是文件的时候怎么校验
                initial = self.get_initial_for_field(field, name)
                value = field.clean(value, initial)
                #filed是一个对象,field.clean才是真正的规则校验
            else:
                #你不是文件的时候怎么校验
                #实际中也是走的这一部,value是你输入的字段值
                #如果没有问题,那么原样返回
                value = field.clean(value)
                #如果一旦出现问题,那么就会走except中的代码
            self.cleaned_data[name] = value

            if hasattr(self, 'clean_%s' % name):  #这里找是否有clean_XX这个名字存在
                value = getattr(self, 'clean_%s' % name)()  #如果有执行这个函数
                self.cleaned_data[name] = value  #而在钩子中必须报错的返回值是确定的
                #如果上面有问题,就又把错误添加到了_error中
                #上面这三行代码是我们能添加钩子的原因,而且规定了钩子名的格式

                #如果这个值是正确的话,就会给这个字典添加一个键值对
                #刚才在full_clean中self.cleaned_data = {}已经初始化了。
                #{”pws“:123}
        except ValidationError as e:
            self.add_error(name, e)
            #如果出现错误,就会给_error这个字典添加一个键值对
            #至于add_error这个函数如何添加这个键值对的,我们先不管
            #键就是name,值就是错误信息e
            #在full_clean中已经初始化self._errors = ErrorDict()
            #假设现在user有问题,那么_error就是这样{”user“:e}

3.3、Django之Form验证clean方法

3.3.1、form验证预留了3个可自定制数据验证的三个方法

  • self._clean_字段名() :针对单个字段预留的方法(也就是该字段通过form验证以后就会触发该对应名字的自定义方法)
  • self._clean_form() : 针对多个字段预留的方法
  • self._post_clean() : 针对多个字段预留的方法

  • 为什么要预留呢?因为数据格式的验证通过以后,不代表数据存入数据库正常,如果存入数据库出现问题,我们就需要使用到预留的方法,因为如果在is_valid判断True以后,你进行数据存储出现问题,那么错误信息我们就不可以封装到form自身生成的错误结构里面。简单理解就是中间件。

3.3.2、整个流程 同 3.2.1

  • 验证流程:
    • 1.调用了该is_valid方法
    • 2.执行了self.fuu_clean()
    • 3.执行了self_clean_fields(开始字段匹配验证)
    • 4.执行源码的 self._clean_form()#执行该的方法(我们可以自定义)(支持异常,直接返回异常)
    • 5.执行源码的 self._post_clean()#执行该的方法(我们可以自定义)(不允许直接返回异常,需要调用add_error方法把异常作为实参进行有效返回)
    • 6.is_valid进行赋值:只有2个一个是true代表数据正确,一个是false:代表数据错误
    • 7.url对应函数里面进行判断id_valid
    • 8.正确通过obj.clean进行获取 错误的话通过obj.errors获取

3.3.3、代码示例

from django import forms 
from django.forms import fields
from django.forms import widgets
 
class UserForm(forms.Form):
    username = fields.CharField(label='用户名')
    email = fields.EmailField(label='邮箱')
 
    ###每个字段数据格式通过验证都会触发下面的对应的字段名字自定方法
    # def clean_username(self):
    #     value=self.cleaned_data['username']
    #     #if models.UserAuth.object.filter(username = value)
    #     if value == 'root':
    #         return value
    #     else:
    #         from django.core.exceptions import ValidationError
    #         raise ValidationError('该用户不正确')
 
    ###所有的字段格式通过验证都会触发下面的clean方法和_post_clean的方法
    def clean(self):
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        if v1 == "root" and v2 =="root@live.com":
            pass
        else:
            from django.core.exceptions import ValidationError
            raise ValidationError('用户名或邮箱错误')
        return self.cleaned_data
 
    # def _post_clean(self):
    #     v1 = self.cleaned_data['username']
    #     v2 = self.cleaned_data['email']
    #     if v1 == "root" and v2 =="root@live.com":
    #         pass
    #     else:
    #         from django.core.exceptions import ValidationError
    #         self.add_error("__all__",ValidationError('用户名或邮箱错误'))
    #                          第一个实参必须为: "__all__" 或者 None

只要记住上面3个方法判断以后:

  • 1.单个字段方法如 def cleand_字段名():进行判断以后如果正确直接pass或者返回字段的value,错误进行raise,当然可以修改用户提交的数据进行返回,然后回归正常流程进行is_valid判断,true我们可以通过obj.clean() or obj.cleaned_data提取数据,错误就obj.errors提取
  • 2.全部字段方法如 def cleand():进行判断以后如果正确直接pass或者返回本身的self.cleaned_data,错误进行raise,当然可以修改用户提交的数据进行返回。然后回归正常流程进行is_valid判断,true我们可以通过obj.clean() or obj.cleaned_data提取数据,错误就obj.errors提取
  • 3.全部字段方法如 def _post_cleand():进行判断以后如果正确直接pass或者返回self.cleaned_data,错误进行self.add_error,当然可以修改用户提交的数据进行返回。然后回归正常流程进行is_valid判断,true我们可以通过obj.clean() or obj.cleaned_data提取数据,错误就obj.errors提取
posted @ 2021-07-03 11:02  SRE运维充电站  阅读(144)  评论(0编辑  收藏  举报