目录
一: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