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 发送数据
<script>
// 绑定点击事件
$('#btn' ).click(function () {
$.ajax({
// 1. 指定朝哪个后端发送ajax请求
url: '' ,
// 2. 请求方式
type : 'post' ,
// 3. 数据
data:{'username' :'付付' ,'password' :123 },
// 4. 回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
alert(args)
}})
})
</script>
def ab_ajax (request ):
if request.method == "POST" :
return HttpResponse(1 )
return render(request,'index.html' )
2.2 实现计算器
"""
页面上有三个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:{'i1' :$('#d1' ).val(),'i2' :$('#d2' ).val()},
// 4. 回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
// 接收后端的值 args 动态渲染到 第三个input 框内
$('#d3' ).val(args)
}})
})
</script>
</body>
def ab_ajax (request ):
if request.method == "POST" :
i1 = request.POST.get('i1' )
i2 = request.POST.get('i2' )
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 后端返回字符串
from django.shortcuts import render,HttpResponse
import json
from django.http import JsonResponse
def ab_ajax (request ):
if request.method == "POST" :
d = {'name' :'付付' ,'age' :18 }
print (d)
return JsonResponse(d)
return render(request,'index.html' )
<script>
// 绑定点击事件
$('#btn' ).click(function () {
$.ajax({
url: '' , //不写默认往当前地址提交
type : 'post' , //默认是get
{
data:{'i1' :$('#d1' ).val(),'i2' :$('#d2' ).val()},
{
success:function (args) {
// 接收后端的值 args 动态渲染到 第三个input 框内
{
$('#d3' ).val(args.name)
console.log(typeof args),
// 接收到是个对象 可以通过对象点点方式取值
console.log(args.name)
}})
})
</script>
三. Ajax 传输数据
3.1 前后端传输数据的编码格式
"""
get请求数据就是直接放在url后面的
url?username=jason&password=123
"""
1. form表单
2. ajax请求
1 ) urlencoded
2 ) formdata
3 ) json
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格式数据的
"""
"""
默认的编码格式也是urlencoded
数据格式:username=jason&age=20
django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
username=jason&age=20 >>> request.POST
"""
3.2 发送json格式数据
<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>
import json
def ad_json (request ):
if request.is_ajax():
json_bytes = request.body
json_str = json_bytes.decode('utf-8' )
json_dict = json.loads(json_str)
json_dict = json.loads(json_bytes)
return render(request,'ad_json.html' )
"""
ajax发送json格式数据需要注意点
1.contentType参数指定成:application/json
2.数据是真正的json格式数据
3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
"""
3.3 发送文件数据
"""
总结:
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中
"""
def ab_file (request ):
if request.is_ajax():
if request.method == 'POST' :
print (request.FILES)
print (request.POST)
return render(request, 'ab_file.html' )
<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自带的序列化组件
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='性别' )
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 ()
res = serializers.serialize('json' ,user_queryset)
return HttpResponse(res)
<body>
{% for user_obj in user_queryset %}
{{ user_obj }}
{% endfor %}
</body>
3.5 ajax结合sweetalert 二次确认
<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>
"""
<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/' , // 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版本 直接刷新当前页面
{
// 2. 利用DOM操作 动态刷新 当前标签--父标签--父标签( 当前标签-td-tr) 移除
currentBtn.parent().parent().remove()
}else {
swal('ERROR' ,'出现未知错误' ,'info' )
}
}
})
}
else {
swal("取消成功" , "你考虑的很全面" , "info" );
}
});
})
</script>
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)
<!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 批量插入数据
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 ())
<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中有自带的分页器模块 但是书写起来很麻烦并且功能太简单
所以我们自己想法和设法的写自定义分页器
上述推导代码你无需掌握 只需要知道内部逻辑即可
我们基于上述的思路 已经封装好了我们自己的自定义分页器
之后需要使用直接拷贝即可
"""
<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
"""
from utils.mypage import Pagination
def ab_pl (request ):
"""
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
book_queryset = models.Book.objects.all ()
current_page = request.GET.get('page' ,1 )
all_count = book_queryset.count()
page_obj = Pagination(current_page=current_page,all_count=all_count)
page_queryset = book_queryset[page_obj.start:page_obj.end]
return render(request,'ab_pl.html' ,locals ())
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
<nav aria-label="Page navigation" >
</nav>
{% endfor %}
{
{{ page_obj.page_html|safe }}
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 ):
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
else :
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
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 = []
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)
5.1 前戏-判断用户名是否规范
"""
写一个注册功能
获取用户名和密码 利用form表单提交数据
在后端判断用户名和密码是否符合一定的条件
用户名中不能含有童话故事
密码不能少于三位
如何符合条件需要你将提示信息展示到前端页面
"""
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 ())
<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>
"""
1.手动书写前端获取用户数据的html代码 渲染html代码
2.后端对用户数据进行校验 校验数据
3.对不符合要求的数据进行前端提示 展示提示信息
forms组件
能够完成的事情
1.渲染html代码
2.校验数据
3.展示提示信息
为什么数据校验非要去后端 不能在前端利用js直接完成呢?
数据校验前端可有可无
但是后端必须要有!!!
因为前端的校验是弱不禁风的 你可以直接修改
或者利用爬虫程序绕过前端页面直接朝后端提交数据
购物网站
选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验
实际是获取到用户选择的所有商品的主键值
然后在后端查询出所有商品的价格 再次计算一遍
如果跟前端一致 那么完成支付如果不一致直接拒绝
"""
5.2 导入使用
from django import forms
class MyForm (forms.Form):
username = forms.CharField(min_length=3 ,max_length=8 )
password = forms.CharField(min_length=3 ,max_length=8 )
email = forms.EmailField()
5.3 校验数据
"""
1.测试环境的准备 可以自己拷贝代码准备
2.其实在pycharm里面已经帮你准备一个测试环境
python console
"""
from app01 import views
form_obj = views.MyForm({'username' :'jason' ,'password' :'123' ,'email' :'123' })
form_obj.is_valid()
False
form_obj.cleaned_data
{'username' : 'jason' , 'password' : '123' }
form_obj.errors
{
'email' : ['Enter a valid email address.' ]
}
form_obj = views.MyForm({'username' :'jason' ,'password' :'123' ,'email' :'123@qq.com' ,'hobby' :'study' })
form_obj.is_valid()
True
form_obj = views.MyForm({'username' :'jason' ,'password' :'123' })
form_obj.is_valid()
False
"""
也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传
"""
5.3 渲染标签
"""
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
"""
def index (request ):
form_obj = MyForm()
return render(request,'index.html' ,locals ())
<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 展示提示信息
def index (request ):
form_obj = MyForm()
if request.method == 'POST' :
"""
1.数据获取繁琐
2.校验数据需要构造成字典的格式传入才行
ps:但是request.POST可以看成就是一个字典
"""
form_obj = MyForm(request.POST)
if form_obj.is_valid():
return HttpResponse('OK' )
return render(request,'index.html' ,locals ())
<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 = forms.CharField(min_length=3 ,max_length=8 ,label='用户名' ,
error_messages={
'min_length' :'用户名最少3位' ,
'max_length' :'用户名最大8位' ,
'required' :"用户名不能为空"
}
)
password = forms.CharField(min_length=3 ,max_length=8 ,label='密码' ,
error_messages={
'min_length' : '密码最少3位' ,
'max_length' : '密码最大8位' ,
'required' : "密码不能为空"
}
)
email = forms.EmailField(label='邮箱' ,
error_messages={
'invalid' :'邮箱格式不正确' ,
'required' : "邮箱不能为空"
}
)
5.6 钩子函数(HOOK)
"""
在特定的节点自动触发完成响应操作
钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
在forms组件中有两类钩子
1.局部钩子
当你需要给单个字段增加校验规则的时候可以使用
2.全局钩子
当你需要给多个字段增加校验规则的时候可以使用
##案例
1.校验用户名中不能含有fufu 只是校验username字段 局部钩子
2.校验密码和确认密码是否一致 password confirm两个字段 全局钩子
"""
from django import forms
class MyForm (forms.Form):
username = forms.CharField(min_length=3 ,
max_length=8 ,
label='用户名' ,
error_messages={
'min_length' :'用户名最少3位' ,
'max_length' :'用户名最大8位' ,
'required' :'用户名不能为空'
})
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 = forms.EmailField(label='邮箱' ,
error_messages={
'invalid' : '邮箱格式不正确' ,
'required' : '邮箱不能为空'
})
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
label 字段名
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
widget 修改字段属性
"""
1.字段没有样式
2.针对不同类型的input如何修改
text
password
date
radio
checkbox
...
"""
widget = forms.widgets.PasswordInput(attrs={'class' : 'form-control' ,'user_text' :'zhangyuzhou' }))
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' })
)
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' }))
from django.core.validators import RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$' , '请输入数字' ),
RegexValidator(r'^159[0-9]+$' , '数字必须以159开头' )
]
)
5.8 其他类型渲染
radio 选择
select 单选
checkbox
gender = forms.ChoiceField(
choices=((1 , "男" ), (2 , "女" ), (3 , "保密" )),
label="性别" ,
initial=3 ,
widget=forms.widgets.RadioSelect()
)
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()
)
keep = forms.ChoiceField(
label="是否记住密码" ,
initial="checked" ,
widget=forms.widgets.CheckboxInput()
)
hobby2 = forms.MultipleChoiceField(
choices=((1 , "篮球" ), (2 , "足球" ), (3 , "双色球" ),),
label="爱好" ,
initial=[1 , 3 ],
widget=forms.widgets.CheckboxSelectMultiple()
)
"""
切入点:
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
self.is_bound = data is not None or files is not None
@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
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操作
return HttpResponse()
return render()
return redirect()
obj1 = HttpResponse()
obj2 = render()
obj3 = redirect()
return obj
obj.set_cookie(key,value)
obj.set_cookie('username' , 'fufu' ,max_age=3 ,expires=3 )
在设置cookie的时候可以添加一个超时时间
max_age
expires
两者都是设置超时时间的 并且都是以秒为单位
需要注意的是 针对IE浏览器需要使用expires
request.COOKIES.get(key)
obj.delete_cookie('username' )
-------------------项目案例-------------------------
"""
实现用户登入功能:
1. 用户需要登入才能访问 home index 功能页面
2. 用户登入后默认跳到首页 home.html
3. 如果用户从其它页面跳转过来,登入后 应该跳转到用户上次访问的页面
例如:用户访问index 由于未登入--跳转到登入页面 login, 登入完毕后 直接跳转到 index
4. 用户可以注销 退出登入
"""
def login_auth (func ):
def inner (request, *args, **kwargs ):
"""
1. 获取用户上一次想访问的url
如果用户本身直接访问的登入longin页面 那么这个值就是空的
2. 判断cookie 如果有值 则不做改变,返回函数体本身
如果没有值则返回登入页面+用户上次访问的url 方便登入成功后跳转
"""
target_url = request.get_full_path()
if request.COOKIES.get('username' ):
return func(request, *args, **kwargs)
else :
return redirect(f'/login/?next={target_url} ' )
return inner
def login (request ):
if request.method == 'POST' :
username = request.POST.get('username' )
password = request.POST.get('password' )
if username == 'fufu' and password == '123' :
target_url = request.GET.get('next' )
if target_url:
obj = redirect(target_url)
else :
obj = redirect('/home/' )
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页面' )
@login_auth
def logout (request ):
obj = redirect('/login/' )
obj.delete_cookie('username' )
return obj
6.3 session操作
"""
1.session数据是保存在服务端的(存?),给客户端返回的是一个随机字符串
数据格式
sessionid:随机字符串
2.在默认情况下操作session的时候需要django默认的一张django_session表
数据库迁移命令
django会自己创建很多表 django_session就是其中的一张
3.django默认session的过期时间是14天
但是你也可以人为的修改它
"""
request.session['key' ] = value
def set_session (request ):
request.session['fufu' ] = 'girl'
return HttpResponse('付付啊' )
request.session.get('key' )
def get_session (request ):
res = request.session.get('fufu' )
return HttpResponse(f'这是在获取session的值:{res} ' )
request.session.delete()
request.session.flush()
def del_session (request ):
request.session.flush()
return HttpResponse('删除了' )
request.session.set_expiry()
括号内可以放四种类型的参数
1. 整数 多少秒
2. 日期对象 到指定日期就失效
3.0 一旦当前浏览器窗口关闭立刻失效
4. 不写 失效时间就取决于django内部全局session默认的失效时间
def set_session (request ):
request.session['fufu' ] = 'girl'
request.session.set_expiry(3600 )
return HttpResponse('付付啊' )
session是保存在服务端的 但是session的保存位置可以有多种选择
1. MySQL
2. 文件
3. redis
4. memcache
"""
django_session表中的数据条数是取决于浏览器的
同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
(当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 装饰器
url(r'^login/' ,views.MyLogin.as_view()),
from django.views import View
from django.utils.decorators import method_decorator
"""
CBV中django不建议你直接给类的方法加装饰器
无论该装饰器能都正常给你 都不建议直接加
"""
class MyLogin (View ):
@method_decorator(login_auth )
def dispatch (self, request, *args, **kwargs ):
return super ().dispatch(request,*args,**kwargs)
def get (self,request ):
return HttpResponse("get请求" )
def post (self,request ):
return HttpResponse('post请求' )
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~