Django2.2 学习笔记 (13)_短信验证_Ajax+Redis+Form+JsonResponse+表单验证顺序

目录

一:AJAX

二:JsonResponse

三:Redis

四:Python操作Redis

五:Form表单验证顺序

六:urls.py

七:settings.py

八:forms.py

九:views.py

十:前端html代码


一:AJAX

1. XHR(XMLHttpRequest)是AJAX(Asynchronous JavaScript and XML,异步的 JavaScript 和 XML)基础

2. XHR 用于在后台与服务器交换数据,可以在不重新加载整个网页的情况下,对网页的某部分进行更新

3. AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。它通过原生的XMLHttpRequest对象发出HTTP请求,得到服务器返回的数据后,再进行处理

4. XML 被设计用来传输和存储数据,HTML 被设计用来显示数据

5. JavaScript 用来对对网页行为进行编程,是 web 开发者必学的三种语言之一(另外两个是HTML<定义网页的内容>和CSS<规定网页的布局>)

二:JsonResponse

在前端中发起ajax post请求时,需要返回json格式的数据,这时候就需要用到JsonResponse了

class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None,**kwargs)

# data: Data to be dumped into json. By default only ``dict`` objects are allowed to be passed due to a security flaw before EcmaScript 
# safe: Controls if only ``dict`` objects may be serialized. Defaults to ``True``
# json_dumps_params: A dictionary of kwargs passed to json.dumps()

另外,需要注意的是JsonResponse对象的Content-Type 被设置为: application/json

参考链接:https://blog.csdn.net/hgdl_sanren/article/details/84110068

三:Redis

1、Redis简介

  • Redis 是一个高性能的key-value数据库
  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
  • Redis同时还提供list,set,zset,hash等数据结构的存储

2、Ubuntu 安装Redis

sudo apt-get install redis-server

在安装Redis服务器的时候会自动安装Redis命令行客户端【redis-cli】,同时会自动启动服务【redis-server】

3、验证是否安装成功

# 打开终端
redis-cli
# 返回127.0.0.1:6379
# 127.0.0.1 是本机 IP ,6379 是 redis 服务端口

# 输入 PING 命令
127.0.0.1 > ping
# 返回PONG,代表安装成功

4、Redis常用命令

SET key value [EX seconds] [PX milliseconds] [NX|XX]

# 将字符串值 value 关联到 key
# 如果 key 已经持有其他值, SET 就覆写旧值
# 对于某个原本带有生存时间(TTL)的键来说, 当 SET 命令成功在这个键上执行时, 这个键原有的 TTL 将被清除。

# 可选参数
# 1 EX second :设置键的过期时间为 second 秒
# 2 PX millisecond :设置键的过期时间为 millisecond 毫秒
# 3 NX :只在键不存在时,才对键进行设置操作
# 4 XX :只在键已经存在时,才对键进行设置操作
EXPIRE key seconds

# 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。

示例参考

# 将key-with-expire-time键的值设为“hello”,同时设置过期时间为360秒
SET key-with-expire-time "hello" EX 360

# 获取key-with-expire-time键的值
GET key-with-expire-time
# 返回:"hello"

# 获取key-with-expire-time键的有效时间
TTL key-with-expire-time
# 返回(integer) 359

参考链接1:http://doc.redisfans.com/

参考链接2:https://www.cnblogs.com/zgaspnet/p/10374960.html

四:Python操作Redis

1、安装命令

pip install redis

2、 使用示例

import redis
r = redis.Redis(host='localhost', port=6379, db=0)

r.set('foo', 'bar')
# 返回TRUE
r.get('foo')
# 返回'bar'

官方文档:https://pypi.org/project/redis/

 

五:Form表单验证顺序

第1步
self.is_bound 查看我们创建的MyForm是否空字段,和实例中是否传入了(request.POST)数据

第2步
self.errors 开始校验

第2.1步
self.full_clean()
第2.1.1步
full_clean()会依次调用每个field的clean()函数,该函数针对field的max_length,max_value,unique等约束进行验证
第2.1.2步
调用以clean_开头,以field名字结尾的自定义field验证函数
第2.1.3步
在2.1.1步和2.1.2步的验证中,如果验证成功则返回值,并将值放入form的cleaned_data字典中;否则抛出ValidationError错误

第2.2步
self._clean_form() 如果验证成功,就在cleaned_data字典中添加相应数据;如果验证失败,就在errors字典中填上验证错误。
在template中,每个field获取自己错误的方式是:{{ form.username.errors }}

第2.3步
最后,如果有错误is_valid()返回False,否则返回True

如下图片是网上找的相关参考

# 创建将要验证的实例
obj = MyForm(request.POST)
# 调用isvalid方法开始验证
obj.is_valid() 

############# 如下摘自源码
class BaseForm:
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
        self.is_bound = data is not None or files is not None       
    def errors(self):
        """Return an ErrorDict for the data provided for the form."""
        if self._errors is None:
            self.full_clean()
        return self._errors
    def is_valid(self):
        """Return True if the form has no errors, or False otherwise."""
        return self.is_bound and not self.errors
    def full_clean(self):
        self._clean_fields()
        self._clean_form()
    def _clean_fields(self):
        for name, field in self.fields.items():
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)
    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data
############# 如上摘自源码

六:urls.py

from django.urls import path, re_path, include
from django.views.generic import TemplateView
from django.views.decorators.csrf import csrf_exempt

from apps.users.views import SendSmsView, PhoneLoginView

urlpatterns = [
    # 手机验证码登录
    # TemplateView 可以省略view层,直接在url层返回页面
    path('phone_login/', PhoneLoginView.as_view(), name="phone_login"),

    # 手机验证码相关
    # send_sms跟login.js中的url保持一致
    # csrf_exempt 放弃对csrf_token的验证
    re_path('send_sms/', csrf_exempt(SendSmsView.as_view()), name="send_sms"),
]

七:settings.py

# Redis设置
REDIS_HOST = "localhost"
REDIS_PORT = 6379

八:forms.py

from django import forms
import redis
from MxOnline.settings import REDIS_HOST, REDIS_PORT


# 手机号表单,发送验证码前的验证
class PhoneLoginForm_mobile(forms.Form):
    # 这里的变量名(mobile, code)必须跟前端(js,html)的变量名一致
    mobile = forms.CharField(required=True, min_length=11, max_length=11)

# 手机号+验证码表单
class PhoneLoginForm(forms.Form):
    # 这里的变量名(mobile, code)必须跟前端(js,html)的变量名一致
    mobile = forms.CharField(required=True, min_length=11, max_length=11)
    code = forms.CharField(required=True, min_length=4, max_length=4)

    # 自定义的field验证内容(只验证code字段)
    def clean_code(self):
        # 获取前端填写的手机号和手机短信验证码 
        mobile = self.cleaned_data["mobile"]
        code = self.cleaned_data["code"]
        # 第三方短信服务商发送的验证码均写入了reids中
        # python从redis中读取的数据均是byte格式,只有转码成前端的格式才能比较,所以设置charset="utf8", decode_responses=True
        # 连接redis库
        r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0,charset="utf8", decode_responses=True)
        # 获取手机号对应的验证码
        code_redis = r.get(str(mobile))
        # 如果正确则返回cleaned_data中的内容
        if code_redis == code:
            return self.cleaned_data
        # 如果不正确则返回错误提示
        else:
            raise forms.ValidationError("短信验证码错误")

九:views.py

from django.shortcuts import render
from django.views.generic.base import View
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponseRedirect, JsonResponse
from django.urls import reverse
import redis

from MxOnline.settings import REDIS_HOST, REDIS_PORT

from apps.users.forms import PhoneLoginForm_mobile, PhoneLoginForm
# aliyun_sms 是根据阿里云的短信验证码发送接口做的py文件,request_ready, send_single_sms是用来发送短信的函数
from apps.utils.aliyun_sms import request_ready, send_single_sms
# random_str 是网上找的可以生成随机验证码的py文件,generate_random用来生成随机验证
from apps.utils.random_str import generate_random
from apps.users.models import UserProfile


# 利用axjx发送手机验证码
class SendSmsView(View):
    def post(self, request, *args, **kwargs):
        # request.POST 接收从前端表单中传过来的数据
        send_sms_form = PhoneLoginForm_mobile(request.POST)
        res_dict = {}
        # 验证send_sms_form
        if send_sms_form.is_valid():
            # 获取手机号
            mobile = send_sms_form.cleaned_data["mobile"]
            # 生成随机四位数字验证码
            code = generate_random(4, 0)
            # 发送验证码
            send_res_dict = send_single_sms(request_ready, mobile, code)
            # 如果发送成功,返回信息如下
            # 根据阿里云发送短信后返回的信息,send_res_dict["Message"] == "OK"代表发送成功
            if send_res_dict["Message"] == "OK":
                res_dict["status"] = "success"
                # 链接Redis
                r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
                # 把手机号及对应的验证码写进去
                r.set(str(mobile), code)
                # 设置300后秒过期
                r.expire(str(mobile), 300)
            # 如果发送失败,返回信息如下
            # send_res_dict["Message"]是阿里云短信返回的错误信息
            else:
                res_dict["msg"] = send_res_dict["Message"]
        else:
            for key, value in send_sms_form.errors.items():
                res_dict[key] = value[0]
        # 返回信息给前端的axjx
        return JsonResponse(res_dict)


# 手机验证码登录
class PhoneLoginView(View):
    def post(self, request, *args, **kwargs):
        # phone_login是用来辅助前端判断是显示账号+密码登录,还是手机号+验证码登录
        phone_login = True
        phone_loginform = PhoneLoginForm(request.POST)
        if phone_loginform.is_valid():
            # 获取前端输入的手机号
            mobile = phone_loginform.cleaned_data["mobile"]
            # 判断用户是否存在
            # get 如果数据库中有匹配的数据,则返回相应的匹配的结果(只有一个),如果记录不存在的话,则报错
            # filter 如果数据库中有匹配的数据,则返回所有匹配的结果(可以是多个),如果记录不存在的话,则返回[]
            exist_user = UserProfile.objects.filter(mobile= mobile)
            if exist_user:
                # 如果存在,则返回用户表中的用户相关信息
                user = exist_user[0]
            else:
                # 如果不存在,则新建用户
                user = UserProfile(username=mobile)
                # 生成随机密码
                password = generate_random(6,2)
                # 因为密码要存密文,所以用set_password
                user.set_password(password)
                user.mobile = mobile
                user.save()
            # 执行登录操作
            login(request, user)
            # 登录成功之后,跳转到index页面
            # HttpResponseRedirect(reverse("index"))在之前的文章中有解释
            return HttpResponseRedirect(reverse("index"))
        else:
            return render(request, "login.html", {"login_form": phone_loginform, "phone_login":phone_login})

十:前端html代码

真的仅供参考,跟上边不太匹配(多了图片动态验证码)

<div class="fl form-box">
	<div class="tab">
		<h2 class="{% if phone_login %}{% else %}active{% endif %}">账号登录</h2>
		<!--如果phone_login是True,则class=active,否则class是空-->
		<h2 class="{% if phone_login %}active{% else %}{% endif %}">动态登录</h2>
	</div>
	<!--注意tab-form后的空格,如果有空格就是两个class名称,否则是一个-->
	<form class="tab-form {% if phone_login %}hide{% else %}{% endif %}" action="{% url 'login' %}" method="post" autocomplete="off" id="form1">
		<div class="form-group marb20 {% if login_form.errors.username %} errorput {% endif %}  ">
			<!--回填username的值:login_form.username.value -->
			<input name="username" id="account_l" value="{{ login_form.username.value }}" type="text" placeholder="手机号/邮箱" />
		</div>
		<div class="form-group marb8 {% if login_form.errors.password %} errorput {% endif %} ">
			<!--回填password的值:login_form.password.value -->
			<input name="password" id="password_l" value="{{ login_form.password.value }}" type="password" placeholder="请输入您的密码" />
		</div>
		<div class="error btns login-form-tips" id="jsLoginTips">{% if login_form.errors %} {% for key,error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %} </div>
		 <div class="auto-box marb38">
			<a class="fr" href="forgetpwd.html">忘记密码?</a>
		 </div>
		 <input class="btn btn-green" id="jsLoginBtn" type="submit" value="立即登录 > " />
		{% csrf_token %}
	</form>
	<form class="tab-form {% if phone_login %}{% else %}hide{% endif %}" action="{% url 'phone_login' %}" id="mobile_register_form" autocomplete="off" method="post" id="form2">

		<div class="form-group marb20">
			<!--回填mobile的值:login_form.umobile.value -->
			<input id="jsRegMobile" name="mobile" value="{{ login_form.mobile.value|default_if_none:'' }}"type="text" placeholder="请输入您的手机号码">
		</div>
		<div class="form-group marb20 blur" id="jsRefreshCode">
			<!--获取图片动态验证码的值-->
			{{ dynamic_form.captcha }}
			{{ d_form.captcha }}
		</div>
		<div class="clearfix">
			<div class="form-group marb8 verify-code">
				<input id="jsPhoneRegCaptcha" name="code" value="{{ login_form.code.value |default_if_none:''}}" type="text" placeholder="输入手机验证码">
			</div>
			<input class="verify-code-btn sendcode" id="jsSendCode" value="发送验证码">
		</div>
		<div class="error btns" id="jsMobileTips">{% if login_form.errors %} {% for key,error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %}</div>
		<div class="auto-box marb8">
		</div>
		<input class="btn btn-green" id="jsMobileRegBtn" type="button" value="立即登录">
		{% csrf_token %}
	</form>

end

发布了18 篇原创文章 · 获赞 0 · 访问量 565

猜你喜欢

转载自blog.csdn.net/zhsworld/article/details/104017918