Ajax 初步使用
目录一. Ajax 概念
"""
异步提交
局部刷新
例子:github注册
动态获取用户名实时的跟后端确认并实时展示的前端(局部刷新)
朝发送请求的方式
1.浏览器地址栏直接输入url回车 GET请求
2.a标签href属性 GET请求
3.form表单 GET请求/POST请求
4.ajax GET请求/POST请求
AJAX 不是新的编程语言,而是一种使用现有标准的新方法(比较装饰器)
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
Ajax我们只学习jQuery封装之后的版本(不学原生的 原生的复杂并且在实际项目中也一般不用)
所以我们在前端页面使用ajax的时候需要确保导入了jQuery
ps:并不只有jQuery能够实现ajax,其他的框架也可以 但是换汤不换药 原理是一样的
"""
二. Ajax 初步使用
2.1 发送数据
#01 往后端发送数据
<script>
// 绑定点击事件
$('#btn').click(function () {
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url: '',
// 2.请求方式
type: 'post',
// 3.数据
data:{'username':'付付','password':123},
// 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
alert(args)
}})
})
</script>
#02 后端代码接收数据 并返回
def ab_ajax(request):
if request.method == "POST":
return HttpResponse(1)
return render(request,'index.html')
2.2 实现计算器
#01 需求
"""
页面上有三个input框
在前两个框中输入数字 点击按钮 朝后端发送ajax请求
后端计算出结果 再返回给前端动态展示的到第三个input框中
(整个过程页面不准有刷新,也不能在前端计算)
"""
<body>
<h1 class="text-center">在线计算器</h1>
<p>请输入参数:<input type="text" id="d1" > + <input type="text" id="d2" > = <input type="text" id="d3"></p>
<p>
<button id="btn" class="btn btn-success">点我
</button></p>
<script>
// 绑定点击事件
$('#btn').click(function () {
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url: '', //不写默认往当前地址提交
// 2.请求方式
type: 'post', //默认是get
// 3.数据
{#data:{'username':'付付','password':123},#}
data:{'i1':$('#d1').val(),'i2':$('#d2').val()},
// 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
// 接收后端的值 args 动态渲染到 第三个input框内
$('#d3').val(args)
}})
})
</script>
</body>
#02 后端代码
def ab_ajax(request):
if request.method == "POST":
i1 = request.POST.get('i1')
i2 = request.POST.get('i2')
#转成int类型相加
i3 = int(i1) + int(i2)
print(i3)
return HttpResponse(i3)
return render(request,'index.html')
"""
针对后端如果是用HttpResponse返回的数据 回调函数不会自动帮你反序列化
如果后端直接用的是JsonResponse返回的数据 回调函数会自动帮你反序列化
HttpResponse解决方式
1.自己在前端利用JSON.parse()
2.在ajax里面配置一个参数
(后面再讲)
"""
2.3 后端返回字符串
#01 后端
from django.shortcuts import render,HttpResponse
import json
from django.http import JsonResponse
# Create your views here.
def ab_ajax(request):
if request.method == "POST":
# i1 = request.POST.get('i1')
# i2 = request.POST.get('i2')
# #转成int类型相加
# i3 = int(i1) + int(i2)
# print(i3)
##返回一个字典
d = {'name':'付付','age':18}
print(d)
# 发送过去是个对象
return JsonResponse(d)
return render(request,'index.html')
#02 前端获取数据
<script>
// 绑定点击事件
$('#btn').click(function () {
$.ajax({
url: '', //不写默认往当前地址提交
type: 'post', //默认是get
{#data:{'username':'付付','password':123},#}
data:{'i1':$('#d1').val(),'i2':$('#d2').val()},
{#dataType:true,#}
success:function (args) {
// 接收后端的值 args 动态渲染到 第三个input框内
{#$('#d3').val(args)#}
$('#d3').val(args.name)
console.log(typeof args),
// 接收到是个对象 可以通过对象点点方式取值
console.log(args.name)
}})
})
</script>
- 图示
三. Ajax 传输数据
3.1 前后端传输数据的编码格式
- contentType
###我们主要研究post请求数据的编码格式
"""
get请求数据就是直接放在url后面的
url?username=jason&password=123
"""
#01 可以朝后端发送post请求的方式
1.form表单
2.ajax请求
#02 前后端传输数据的编码格式
1) urlencoded
2) formdata
3) json
#02 研究form表单
1) html文件
<h3 class="text-center">测试前后端传输的编码格式</h3>
<form action="" method="post" enctype="multipart/form-data">
<p>username:<input type="text" name="username" class="form-control"> </p>
<p>password:<input type="password" name="password" class="form-control"> </p>
<p>上传文件: <input type="file" name="file"></p>
<p><input type="submit" class="btn btn-success " value="点击提交"></p>
</form>
2)打印检查
def index(request):
if request.method == 'POST':
print(request.POST)
print(request.FILES)
return render(request,'index.html')
"""
默认的数据编码格式是urlencoded
数据格式:username=jason&password=123
django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
username=jason&password=123 >>> request.POST
如果你把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中而将文件解析到request.FILES中
form表单是没有办法发送json格式数据的
"""
#03 研究ajax
"""
默认的编码格式也是urlencoded
数据格式:username=jason&age=20
django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
username=jason&age=20 >>> request.POST
"""
3.2 发送json格式数据
#01 前端页面
<button class="btn btn-success btn-primary" id="d1">点我啊</button>
<script>
$('#d1').click(function () {
$.ajax({
url: '',
type: 'post',
data:JSON.stringify({'name':'fufu','age':18}), // 需要转为json 发送给后端
contentType:'application/json', // 指定编码格式
success:function () {
console.log(1)
}
})
})
</script>
#02 后端接收数据
import json
def ad_json(request):
if request.is_ajax(): #判断是否是 ajax请求
json_bytes = request.body #json数据存储在 request.body 内(二进制json数据 需要序列化)
json_str = json_bytes.decode('utf-8')
json_dict = json.loads(json_str)
# json.loads括号内如果传入了一个二进制格式的数据那么内部自动解码再反序列化
json_dict = json.loads(json_bytes)
return render(request,'ad_json.html')
#03 结论
"""
ajax发送json格式数据需要注意点
1.contentType参数指定成:application/json
2.数据是真正的json格式数据
3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
"""
3.3 发送文件数据
### ajax发送文件需要借助于js内置对象FormData
"""
总结:
1.需要利用内置对象FormData
// 2 添加普通的键值对
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
// 3 添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0])
2.需要指定两个关键性的参数
contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象
processData:false, // 告诉你的浏览器不要对你的数据进行任何处理
3.django后端能够直接识别到formdata对象并且能够将内部的普通键值自动解析并封装到request.POST中 文件数据自动解析并封装到request.FILES中
"""
#01 后端代码
def ab_file(request):
if request.is_ajax():
if request.method == 'POST':
print(request.FILES)
print(request.POST)
return render(request, 'ab_file.html')
#02 前端代码书写
<body>
<h2 class="text-center">发送文件测试</h2>
<p>username: <input type="text" id="d1" ></p>
<p>password: <input type="password" id="d2"> </p>
<p><input type="file" id="d3"></p>
<button class="btn btn-info" id="d4">点击</button>
<script>
// 点击按钮朝后端发送普通键值对和文件数据
$('#d4').on('click',function () {
// 1. 需要先利用FormData内置对象
let formdateObj = new FormData();
// 2.添加普通键值对
formdateObj.append('username',$('#d1').val());
formdateObj.append('password',$('#d2').val());
// 3.添加文件对象
formdateObj.append('fufu_myfile',$('#d3')[0].files[0])
// 4.将对象基于ajax发送到后端
$.ajax({
url:'',
type:'post' ,
data:formdateObj, //直接将对象放在data后面即可
contentType: false, // 不需使用任何编码 django后端能够自动识别formdata对象
processData: false, // 告诉你的浏览器不要对你的数据进行任何处理
success:function (args) {
}
})
})
</script>
</body>
3.4 django自带的序列化组件
- serializers drf做铺垫
#01 创建环境
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32,verbose_name='性别')
age = models.IntegerField(verbose_name='年龄')
gender_choices = (
(1,'male'),
(2,'female'),
(3,'others'),
)
gender = models.IntegerField(choices=gender_choices,verbose_name='性别')
#02 发送列表套字典jso数据到前端
from app01 import models
import json
from django.http import JsonResponse,HttpResponse
from django.core import serializers
def ab_ser(request):
user_queryset = models.User.objects.all()
#01 手动拼接方式
# user_list = []
# for user_obj in user_queryset:
# tmp = {
# 'pk': user_obj.pk,
# 'username': user_obj.username,
# 'age': user_obj.age,
# 'gender': user_obj.get_gender_display()
# }
# user_list.append(tmp)
# return JsonResponse(user_list,safe=False)
#02 使用模块封装
# 会自动帮你将数据变成json格式的字符串 并且内部非常的全面
res = serializers.serialize('json',user_queryset)
return HttpResponse(res)
#03 前端展示
<body>
{% for user_obj in user_queryset %}
{{ user_obj }}
{% endfor %}
</body>
3.5 ajax结合sweetalert 二次确认
- 实现删除用户时 进行二次确认
#01 引用 sweetalert.css (cdn方式引用)
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.js"></script>
注意如果不使用cdn则 要把对应文件下载到本地引用
"""
1. 设置暴漏到静态文件路径
settings.py
STATIC_URL = '/static/' # 接口前缀 令牌
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static')
]
2.引用静态配置文件
{% load static %}
<link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}">
<script src="{% static 'dist/sweetalert.min.js' %}"></script>
"""
#02 书写用户列表展示页面 (截取重要部分)
<script>
$('.del').on('click',function () {
// 先将当前标签对象存储起来
let currentBtn = $(this);
// 二次确认弹框
swal({
title: "你确定要删吗?",
text: "请务必考虑清楚,删除后数据不可恢复",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "是的,我确认",
cancelButtonText: "我再考虑下",
closeOnConfirm: false,
closeOnCancel: false,
showLoaderOnConfirm: true //等待删除后 恢复
},
// 如果用户选择的是确认 isConfirm = true
function(isConfirm) {
if (isConfirm) {
// 朝后端发送ajax请求删除数据之后 再弹下面的提示框
$.ajax({
{#url:'/delete/user/' + currentBtn.attr('delete_id'), // 1 传递主键值方式1#}
url:'/delete/user/', // 2 放在请求体里面
type:'post',
data:{'delete_id':currentBtn.attr('delete_id')},
success:function (args) { // args = {'code':'','msg':''}
// 判断响应状态码 然后做不同的处理
if(args.code === 1000){
swal("删除成功!", args.msg, "success");
// 1.lowb版本 直接刷新当前页面
{#window.location.reload()#}
// 2.利用DOM操作 动态刷新 当前标签--父标签--父标签( 当前标签-td-tr) 移除
currentBtn.parent().parent().remove()
}else{
swal('ERROR','出现未知错误','info')
}
}
})
}
else {
swal("取消成功", "你考虑的很全面", "info");
}
});
})
</script>
#03 后端代码
1)用户列表展示
def user_list(request):
user_queryset = models.User.objects.all()
return render(request,'user_list.html',locals())
2)time 模拟删除等待
import time
def user_delete(request):
if request.is_ajax():
if request.method == "POST":
# 生成响应字典 需要返回给前端数据
back_dic = {"code":1000,'msg':''}
delete_id = request.POST.get('delete_id')
time.sleep(3)
print(delete_id)
models.User.objects.filter(pk=delete_id).delete()
back_dic['msg'] = '删除数据成功'
return JsonResponse(back_dic)
- html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style>
div.sweet-alert h2 {
padding-top: 10px;
}
</style>
{% load static %}
<link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}">
<script src="{% static 'dist/sweetalert.min.js' %}"></script>
{# 关于静态文件查找 其实还有一个方法 代码发布再透露 #}
{# <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.css" rel="stylesheet">#}
{# <script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.js"></script>#}
</head>
<body>
<div class="container-fluid">
<h1 class="text-center">数据展示</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table-striped table table-hover">
<thead>
<tr>
<th>ID</th>
<th>username</th>
<th>age</th>
<th>gender</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{% for user_obj in user_queryset %}
<tr>
<td>{{ user_obj.pk }}</td>
<td>{{ user_obj.username }}</td>
<td>{{ user_obj.age }}</td>
<td>{{ user_obj.get_gender_display }}</td>
<td>
<button class="btn btn-primary btn-xs">编辑</button>
<button class="btn btn-danger btn-xs del" delete_id="{{ user_obj.pk }}">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
$('.del').on('click',function () {
// 先将当前标签对象存储起来
let currentBtn = $(this);
// 二次确认弹框
swal({
title: "你确定要删吗?",
text: "请务必考虑清楚,删除后数据不可恢复",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "是的,我确认",
cancelButtonText: "我再考虑下",
closeOnConfirm: false,
closeOnCancel: false,
showLoaderOnConfirm: true //等待删除后 恢复
},
function(isConfirm) {
if (isConfirm) {
// 朝后端发送ajax请求删除数据之后 再弹下面的提示框
$.ajax({
{#url:'/delete/user/' + currentBtn.attr('delete_id'), // 1 传递主键值方式1#}
url:'/delete/user/', // 2 放在请求体里面
type:'post',
data:{'delete_id':currentBtn.attr('delete_id')},
success:function (args) { // args = {'code':'','msg':''}
// 判断响应状态码 然后做不同的处理
if(args.code === 1000){
swal("删除成功!", args.msg, "success");
// 1.lowb版本 直接刷新当前页面
{#window.location.reload()#}
// 2.利用DOM操作 动态刷新 当前标签--父标签--父标签( 当前标签-td-tr) 移除
currentBtn.parent().parent().remove()
}else{
swal('ERROR','出现未知错误','info')
}
}
})
}
else {
swal("取消成功", "你考虑的很全面", "info");
}
});
})
</script>
</body>
</html>
- 图示
- 等待页面
- 删除成功
四. 自定义分页器
4.1 批量插入数据
#01 bulk_create 批量创建即可
def ab_pl(request):
# 获取所有数据展示给前端
book_queryset = models.Book.objects.all()
"""
先创建1000个数据对象,然后 使用orm给你提供的bulk_create 批量创建
"""
book_list = []
for i in range(1,1000):
book_obj = models.Book(title=f'第{i}本书')
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)
return render(request,'ab_pl.html',locals())
#02 前端展示
<body>
{% for book_obj in book_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
</body>
4.2 分页器前戏
"""
总数据100 每页展示10 需要10
总数据101 每页展示10 需要11
总数据99 每页展示10 需要10
如何通过代码动态的计算出到底需要多少页?
在制作页码个数的时候 一般情况下都是奇数个 符合中国人对称美的标准
"""
# 分页
book_list = models.Book.objects.all()
# 想访问哪一页
current_page = request.GET.get('page',1) # 如果获取不到当前页码 就展示第一页
# 数据类型转换
try:
current_page = int(current_page)
except Exception:
current_page = 1
# 每页展示多少条
per_page_num = 10
# 起始位置
start_page = (current_page - 1) * per_page_num
# 终止位置
end_page = current_page * per_page_num
# 计算出到底需要多少页
all_count = book_list.count()
page_count, more = divmod(all_count, per_page_num)
if more:
page_count += 1
page_html = ''
xxx = current_page
if current_page < 6:
current_page = 6
for i in range(current_page-5,current_page+6):
if xxx == i:
page_html += '<li class="active"><a href="?page=%s">%s</a></li>'%(i,i)
else:
page_html += '<li><a href="?page=%s">%s</a></li>'%(i,i)
book_queryset = book_list[start_page:end_page]
"""
django中有自带的分页器模块 但是书写起来很麻烦并且功能太简单
所以我们自己想法和设法的写自定义分页器
上述推导代码你无需掌握 只需要知道内部逻辑即可
我们基于上述的思路 已经封装好了我们自己的自定义分页器
之后需要使用直接拷贝即可
"""
- html
<body>
{% for book_obj in book_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{{ page_html |safe }}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</body>
4.4 分页器封装
"""
当我们需要使用到非django内置的第三方功能或者组件代码的时候
我们一般情况下会创建一个名为utils文件夹 在该文件夹内对模块进行功能性划分
utils可以在每个应用下创建 具体结合实际情况
我们到了后期封装代码的时候 不再局限于函数
还是尽量朝面向对象去封装
我们自定义的分页器是基于bootstrap样式来的 所以你需要提前导入bootstrap
bootstrap 版本 v3
jQuery 版本 v3
"""
#01 后端代码 手动创建 utils-->mypage 文件
from utils.mypage import Pagination
def ab_pl(request):
"""
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
#01 获取数据
book_queryset = models.Book.objects.all() #展示的所有数据
current_page = request.GET.get('page',1) #获取当前页(没有制定 则默认是1)
all_count = book_queryset.count() #所有数据量
#02 传值生成对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
#03 直接对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
return render(request,'ab_pl.html',locals())
#02 前端代码
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
<nav aria-label="Page navigation">
</nav>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}
- 分页器封装代码 utils->>mypage
#01 分页器封装成类
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
- 图示
五. Forms组件
5.1 前戏-判断用户名是否规范
"""
写一个注册功能
获取用户名和密码 利用form表单提交数据
在后端判断用户名和密码是否符合一定的条件
用户名中不能含有童话故事
密码不能少于三位
如何符合条件需要你将提示信息展示到前端页面
"""
#01 后端代码
def ab_form(request):
back_dic = {'username':'','password':''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if '童话故事' in username:
back_dic['username'] = '不符合社会主义核心价值观'
if len(password) < 3:
back_dic['password'] = '不能太短 不好!'
"""
无论是post请求还是get请求
页面都能够获取到字典 只不过get请求来的时候 字典值都是空的
而post请求来之后 字典可能有值
"""
return render(request,'ab_form.html',locals())
#03 前端代码
<form action="" method="post">
<p>username:
<input type="text" name="username">
<span style="color: red">{{ back_dic.username }}</span>
</p>
<p>password:
<input type="text" name="password">
<span style="color: red">{{ back_dic.password }}</span>
</p>
<input type="submit" class="btn btn-info">
</form>
##03 总结
"""
1.手动书写前端获取用户数据的html代码 渲染html代码
2.后端对用户数据进行校验 校验数据
3.对不符合要求的数据进行前端提示 展示提示信息
forms组件
能够完成的事情
1.渲染html代码
2.校验数据
3.展示提示信息
为什么数据校验非要去后端 不能在前端利用js直接完成呢?
数据校验前端可有可无
但是后端必须要有!!!
因为前端的校验是弱不禁风的 你可以直接修改
或者利用爬虫程序绕过前端页面直接朝后端提交数据
购物网站
选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验
实际是获取到用户选择的所有商品的主键值
然后在后端查询出所有商品的价格 再次计算一遍
如果跟前端一致 那么完成支付如果不一致直接拒绝
"""
5.2 导入使用
#01 创建类 设置字段规则
from django import forms
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8)
# email字段必须符合邮箱格式 [email protected]
email = forms.EmailField()
5.3 校验数据
#01 校验数据
"""
1.测试环境的准备 可以自己拷贝代码准备
2.其实在pycharm里面已经帮你准备一个测试环境
python console
"""
from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法 注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
False
# 3 查看所有校验通过的数据
form_obj.cleaned_data
{'username': 'jason', 'password': '123'}
# 4 查看所有不符合校验规则以及不符合的原因
form_obj.errors
{
'email': ['Enter a valid email address.']
}
# 5 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'[email protected]','hobby':'study'})
form_obj.is_valid()
True
# 6 校验数据 默认情况下 类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
False
"""
也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传
"""
5.3 渲染标签
"""
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
"""
#01 后端代码
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
# 2 直接将该空对象传递给html页面
return render(request,'index.html',locals())
#02 前端利用空对象做操作
<body>
<form action="" method="post">
<p>第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用</p>
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<hr>
<p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用</p>
<p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
<p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
<hr>
<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>
{% for form in form_obj %}
<p>{{ form.label }}: {{ form }}</p>
{% endfor %}
</body>
</form>
"""
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可
username = forms.CharField(min_length=3,max_length=8,label='用户名')
"""
5.4 展示提示信息
#01 后端代码
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
if request.method == 'POST':
# 获取用户数据并且校验
"""
1.数据获取繁琐
2.校验数据需要构造成字典的格式传入才行
ps:但是request.POST可以看成就是一个字典
"""
# 3.校验数据
form_obj = MyForm(request.POST)
# 4.判断数据是否合法
if form_obj.is_valid():
# 5.如果合法 操作数据库存储数据
return HttpResponse('OK')
# 5.不合法 有错误
# 2 直接将该空对象传递给html页面
return render(request,'index.html',locals())
#02 前端
<form action="" method="post" novalidate>
<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>
{% for form in form_obj %}
<p>
{{ form.label }}: {{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit" class="btn btn-info btn-xs">
注意:form.errors.0 错误信息是个列表 只取第一个提示信息
"""
1.必备的条件 get请求和post传给html页面对象变量名必须一样
2.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改
更加的人性化
novalidate 需要取消浏览器渲染,在form表单内设置
"""
- 浏览器渲染
5.5 自定义提示信息
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
'min_length':'用户名最少3位',
'max_length':'用户名最大8位',
'required':"用户名不能为空"
}
)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8,label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': "密码不能为空"
}
)
# email字段必须符合邮箱格式 [email protected]
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确',
'required': "邮箱不能为空"
}
)
5.6 钩子函数(HOOK)
"""
在特定的节点自动触发完成响应操作
钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
在forms组件中有两类钩子
1.局部钩子
当你需要给单个字段增加校验规则的时候可以使用
2.全局钩子
当你需要给多个字段增加校验规则的时候可以使用
##案例
1.校验用户名中不能含有fufu 只是校验username字段 局部钩子
2.校验密码和确认密码是否一致 password confirm两个字段 全局钩子
"""
#01 后端代码
from django import forms
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,
max_length=8,
label='用户名',
error_messages={
'min_length':'用户名最少3位',
'max_length':'用户名最大8位',
'required':'用户名不能为空'
})
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,
max_length=8,
label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': '密码不能为空'
})
confirm_password = forms.CharField(min_length=3,
max_length=8,
label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': '密码不能为空'
})
# email字段必须符合邮箱格式 [email protected]
email = forms.EmailField(label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空'
})
#01 局部钩子 判断用户名内 不能包含 fufu
def clean_username(self):
# 获取到用户名
username = self.cleaned_data.get('username')
if 'fufu' in username:
# 提示前端展示错误信息
self.add_error('username','不能出现付付的名字~')
# 将钩子函数钩去出来数据再放回去
return username
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not confirm_password == password:
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数钩出来数据再放回去
return self.cleaned_data
5.7 forms组件其他参数
#01 常用的属性
label 字段名
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
widget 修改字段属性
#02 关于input type如何修改呢
"""
1.字段没有样式
2.针对不同类型的input如何修改
text
password
date
radio
checkbox
...
"""
widget = forms.widgets.PasswordInput(attrs={'class': 'form-control','user_text':'zhangyuzhou'}))
#03 案例 多个属性值的话 直接空格隔开即可
username = forms.CharField(min_length=3,
max_length=8,
label='用户名', #字段名
error_messages={
'min_length':'用户名最少3位',
'max_length':'用户名最大8位',
'required':'用户名不能为空'
}, #自定义报错信息
initial='付付', #默认值
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})
# required=False, #是否必须填写 控制字段
)
password = forms.CharField(min_length=3,
max_length=8,
label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': '密码不能为空'
},
widget = forms.widgets.PasswordInput(attrs={'class': 'form-control','user_text':'zhangyuzhou'}))
#04 正则校验 第一道关卡里面还支持正则校验
from django.core.validators import RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
]
)
- validators
5.8 其他类型渲染
- radio 选择
- select 单选
- checkbox
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
5.9 forms组件源码
"""
切入点:
form_obj.is_valid()
"""
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
# 如果is_valid要返回True的话 那么self.is_bound要为True self.errors要为Flase
self.is_bound = data is not None or files is not None # 只要你传值了肯定为True
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
# forms组件所有的功能基本都出自于该方法
def full_clean(self):
self._clean_fields() # 校验字段 + 局部钩子
self._clean_form() # 全局钩子
self._post_clean()
六. Cookie与Session
6.1 cookie概述
"""
发展史
1.网站都没有保存用户功能的需求 所有用户访问返回的结果都是一样的
eg:新闻、博客、文章...
2.出现了一些需要保存用户信息的网站
eg:淘宝、支付宝、京东...
以登陆功能为例:如果不保存用户登陆状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码(你觉得这样的网站你还想用吗?)
当用户第一次登陆成功之后 将用户的用户名密码返回给用户浏览器 让用户浏览器保存在本地,之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证
早起这种方式具有非常大的安全隐患
优化:
当用户登陆成功之后,服务端产生一个随机字符串(在服务端保存数据,用kv键值对的形式),交由客户端浏览器保存
随机字符串1:用户1相关信息
随机字符串2:用户2相关信息
随机字符串3:用户3相关信息
之后访问服务端的时候,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息
但是如果你拿到了截获到了该随机字符串,那么你就可以冒充当前用户 其实还是有安全隐患的
你要知道在web领域没有绝对的安全也没有绝对的不安全
"""
cookie
服务端保存在客户端浏览器上的信息都可以称之为cookie
它的表现形式一般都是k:v键值对(可以有多个)
session
数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)
下述内容暂时了解即可 先给我搞明白最简单的cookie与session使用再说话!
token
session虽然数据是保存在服务端的 但是禁不住数据量大
服务端不再保存数据
登陆成功之后 将一段用户信息进行加密处理(加密算法之后你公司开发知道)
将加密之后的结果拼接在信息后面 整体返回给浏览器保存
浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
跟浏览器尾部的密文进行比对
jwt认证
三段信息
(后期会讲 结合django一起使用)
总结:
1.cookie就是保存在客户端浏览器上的信息
2.session就是保存在服务端上的信息
3.session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)
6.2 cookie操作
#01 视图函数的返回值 赋值给obj
return HttpResponse()
return render()
return redirect()
#02 利用这个变量去操作cookie
obj1 = HttpResponse()
obj2 = render()
obj3 = redirect()
return obj
#03 设置cookie
obj.set_cookie(key,value)
obj.set_cookie('username', 'fufu',max_age=3,expires=3)
在设置cookie的时候可以添加一个超时时间
max_age
expires
两者都是设置超时时间的 并且都是以秒为单位
需要注意的是 针对IE浏览器需要使用expires
#04 获取cookie
request.COOKIES.get(key)
#05 删除cookies
obj.delete_cookie('username')
-------------------项目案例-------------------------
"""
实现用户登入功能:
1. 用户需要登入才能访问 home index 功能页面
2. 用户登入后默认跳到首页 home.html
3. 如果用户从其它页面跳转过来,登入后 应该跳转到用户上次访问的页面
例如:用户访问index 由于未登入--跳转到登入页面 login, 登入完毕后 直接跳转到 index
4. 用户可以注销 退出登入
"""
#01 登入装饰器
def login_auth(func):
def inner(request, *args, **kwargs):
"""
1. 获取用户上一次想访问的url
如果用户本身直接访问的登入longin页面 那么这个值就是空的
2. 判断cookie 如果有值 则不做改变,返回函数体本身
如果没有值则返回登入页面+用户上次访问的url 方便登入成功后跳转
"""
target_url = request.get_full_path()
# 查询用户状态 cookie值
if request.COOKIES.get('username'):
return func(request, *args, **kwargs)
else:
return redirect(f'/login/?next={target_url}')
return inner
#02 登入功能
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'fufu' and password == '123':
# 获取用户上一次想要访问的url
target_url = request.GET.get('next') # 这个结果可能是None
if target_url:
obj = redirect(target_url)
else:
# 保存用户登陆状态
obj = redirect('/home/')
# 让浏览器记录cookie数据
obj.set_cookie('username', 'fufu123')
"""
浏览器不单单会帮你存
而且后面每次访问你的时候还会带着它过来
"""
# 跳转到一个需要用户登陆之后才能看的页面
return obj
return render(request,'login.html')
@login_auth
def home(request):
return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
@login_auth
def index(request):
return HttpResponse('恭喜你登入成功,这是个登入后才能查看的index页面')
#03 注销功能
@login_auth
def logout(request):
obj = redirect('/login/')
obj.delete_cookie('username')
return obj
6.3 session操作
#01 概述
"""
1.session数据是保存在服务端的(存?),给客户端返回的是一个随机字符串
数据格式
sessionid:随机字符串
2.在默认情况下操作session的时候需要django默认的一张django_session表
数据库迁移命令
django会自己创建很多表 django_session就是其中的一张
3.django默认session的过期时间是14天
但是你也可以人为的修改它
"""
#02 创建session
request.session['key'] = value
def set_session(request):
request.session['fufu'] = 'girl'
return HttpResponse('付付啊')
#03 查看session
request.session.get('key')
def get_session(request):
res = request.session.get('fufu')
return HttpResponse(f'这是在获取session的值:{res}')
#04 删除session
request.session.delete() # 只删服务端的 客户端的不删
request.session.flush() # 浏览器和服务端都清空(推荐使用)
def del_session(request):
request.session.flush()
return HttpResponse('删除了')
#05 设置过期时间
request.session.set_expiry()
括号内可以放四种类型的参数
1.整数 多少秒
2.日期对象 到指定日期就失效
3.0 一旦当前浏览器窗口关闭立刻失效
4.不写 失效时间就取决于django内部全局session默认的失效时间
def set_session(request):
request.session['fufu'] = 'girl'
request.session.set_expiry(3600) #3600秒
return HttpResponse('付付啊')
#06 保持数据方式
session是保存在服务端的 但是session的保存位置可以有多种选择
1.MySQL
2.文件
3.redis
4.memcache
"""
django_session表中的数据条数是取决于浏览器的
同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
(当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除 你也可以通过代码清除)
主要是为了节省服务端数据库资源
"""
#07 session内部原理
request.session['hobby'] = 'girl'
内部发送了那些事
1.django内部会自动帮你生成一个随机字符串
2.django内部自动将随机字符串和对应的数据存储到django_session表中
2.1先在内存中产生操作数据的缓存
2.2在响应结果django中间件的时候才真正的操作数据库
3.将产生的随机字符串返回给客户端浏览器保存
request.session.get('hobby')
内部发送了那些事
1.自动从浏览器请求中获取sessionid对应的随机字符串
2.拿着该随机字符串去django_session表中查找对应的数据
3.
如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中
如果比对不上 则request.session.get()返回的是None
七 CBV 装饰器
#01 cbv url
url(r'^login/',views.MyLogin.as_view()),
#02 后端代码
from django.views import View
from django.utils.decorators import method_decorator
"""
CBV中django不建议你直接给类的方法加装饰器
无论该装饰器能都正常给你 都不建议直接加
"""
# @method_decorator(login_auth,name='get') # 方式2(可以添加多个针对不同的方法加不同的装饰器)
# @method_decorator(login_auth,name='post')
class MyLogin(View):
@method_decorator(login_auth) # 方式3:它会直接作用于当前类里面的所有的方法
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
# @method_decorator(login_auth) # 方式1:指名道姓
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')
标签:username,obj,form,self,request,初步,Ajax,使用,page
From: https://www.cnblogs.com/saas-open/p/18095913