第十篇:Django之Ajax
第十篇:Django之Ajax
一、Ajax简介
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法【类比python中的登录认证装饰器】。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)。
Ajax最大的特点是:异步提交和局部刷新。
- 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
- 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
Ajax常见的应用场景
-
搜索引擎根据用户输入的关键字,自动提示检索关键字。【类比百度搜索,直接对当前输入数据进行检索】
-
网站注册时用户数据的查重【局部刷新】
其实这里就使用了AJAX技术!当文件框发生了输入变化时,使用AJAX技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后再把后端返回的结果展示出来。
整个过程中页面没有刷新,只是刷新页面中的局部位置而已。
当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应。
补充:
"""向服务器发送请求的方式"""
1.浏览器地址栏直接输入url回车 GET请求
2.a标签href属性 GET请求
3.form表单 GET请求/POST请求
4.ajax GET请求/POST请求
二、jQuery实现AJAX
Ajax我们只了解jQuery封装之后的版本(不学原生的,因为原生ajax的比较复杂,同时在实际项目中也一般不用),所以我们在前端页面使用ajax的时候需要确保导入了jQuery【CDN或者本地均可】。(并不只有jQuery能够实现ajax,其他的框架也可以,但是原理基本相同)
<!--CDN导入jQuery-->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
1、Ajax简单小案例
要求:页面中有三个input框,在前两个框中输入数字,点击按钮,朝后端发送ajax请求,后端计算出结果,再返回给前端动态展示的到第三个input框中。【注意:整个过程页面不准有刷新,也不能在前端计算】
我们先简单搭建一个前端页面。
那么该如何进行操作呢?
不急,我们先对ajax进行一个简单的测试,使用jQery方法给按钮添加绑定事件,具体代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<input type="text" id="d1"> +
<input type="text" id="d2"> =
<input type="text" id="d3">
<button id="btn">计算</button>
<script>
$('#btn').click(function () {
// 朝后端发送ajax请求
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url:'', //不写就是朝当前地址提交【与form表单的action参数相同】
// 2.请求方式
type:'post', // 不指定默认就是get,都是小写
// 3.数据
data:{'username':'yangyi', 'password': 123},
// 4.回调函数:当后端给你返回结果的时候会自动触发,args接受后端的返回结果
// 后端如论返回什么都只会被回调函数接收,而不影响这个浏览器页面
success:function (args) {
alert(args)
}
})
})
</script>
</body>
</html>
我们在服务器中先进行测试。
def ab_ajax(request):
if request.method == 'POST':
print(request.POST)
return render(request, 'ajaxtest.html')
好了,到了这里,我们便可以解决上面的案例了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<input type="text" id="d1"> +
<input type="text" id="d2"> =
<input type="text" id="d3">
<button id="btn">计算</button>
<script>
$('#btn').click(function () {
// 朝后端发送ajax请求
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url:'', //不写就是朝当前地址提交【与form表单的action参数相同】
// 2.请求方式
type:'post', // 不指定默认就是get,都是小写
// 3.数据
{#data:{'username':'yangyi', 'password': 123},#}
data:{'num1':$('#d1').val(), 'num2':$('#d2').val()},
// 4.回调函数:当后端给你返回结果的时候会自动触发,args接受后端的返回结果
success:function (args) {
{#alert(args)#}
$('#d3').val(args)
}
})
})
</script>
</body>
</html>
服务器中进行如下修改,即可返回计算后的值。
def ab_ajax(request):
if request.method == 'POST':
num1 = request.POST.get('num1')
num2 = request.POST.get('num2')
num = int(num1)+int(num2)
return HttpResponse(str(num))
return render(request, 'ajaxtest.html')
那么,我们在后端传递一个Json格式的数据,使用HttpResponse返回。代码如下。
"""views.py"""
import json
def ab_ajax(request):
if request.method == 'POST':
# 定义一个字典
dic = {
'name': 'yangyi',
'age': 18,
'gender': 'male'
}
# 对字典进行序列化,然后进行返回
return HttpResponse(json.dumps(dic))
return render(request, 'ajaxtest.html')
"""ajaxtest.html"""
success:function (args) { # 异步回调函数
console.log(args)
console.log(typeof args)
args = JSON.parse(args) # 反序列化
console.log(args)
console.log(typeof args)
}
"""
当后端是以HttpResponse返回的json格式的数据,默认是不会自动反序列化。
HttpResponse解决方式
1.自己在前端利用JSON.parse()
2.在ajax里面配置一个参数
dataType: 'Json'
结论:写ajax的时候,可以直接将dataType参数加上,以防万一,或者后端就用JsonResonse。
"""
那么我们使用JsonResponse对象来进行测试,看看传递过来的Json数据是都已经被反序列化?
"""views.py"""
from django.http import JsonResponse
def ab_ajax(request):
if request.method == 'POST':
# 定义一个字典
dic = {
'name': 'yangyi',
'age': 18,
'gender': 'male'
}
# 对字典进行序列化,然后进行返回
return JsonResponse(dic)
return render(request, 'ajaxtest.html')
"""ajaxtest.html"""
success:function (args) {
console.log(args)
console.log(typeof args)
}
我们发现,使用JsonResponse对象传递的Json格式数据已经被反序列化,不需要在前端进行再次反序列,可以说是十分方便。【真的厉害】
总结:我们可以使用HttpResponse返回数据,回调函数不会自动帮你反序列化;但是如果后端直接用的是JsonResponse返回的数据,回调函数会自动帮你反序列化。
三、前后端传输数据的编码格式(contentType)
我们主要研究post请求数据的编码格式。因为get请求就是直接放在url后面的,直接在后端就可以通过request.GET
得到。
url?username=yangyi&password=123
"""
补充: / 后面通过无名又名分组得到
"""
可以朝后端发送post请求的方式,总共有两种。
1.form表单
2.ajax请求
前后端传输数据的编码格式,我们先了解三种。
1、urlencoded
2、formdata
3、json
1、form表单
我们先来研究form表单的编码格式,我们先建立一个form表单,以post方式提交,然后再后端以如此方式进行接收。
# 研究数据编码格式
def index(request):
if request.method == 'POST':
print(request.POST) # <QueryDict: {'username': ['yangyi'], 'password': ['123']}>
return render(request, 'index.html')
django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中,username=ynagyi&password=123 >>> request.POST
。
我们将from的编码格式改为enctype="multipart/form-data"
,添加文件进行传输。
<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>file: <input type="file" name="form" class="form-control"></p>
<input type="submit" class="btn btn-danger" value="按钮">
</form>
后端数据的接收结果如下所示。
总结:如果把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中,而文件将解析到request.FILES中。(form表单无法发送json格式的数据。)
2、ajax请求
代码如下。
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:{'username':'yangyi', 'age':18},
success:function (args) {
}
})
})
</script>
总结
ajax默认的编码格式也是urlencoded,数据格式:username=yangyi&age=20。
django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中。
username=yangyi&age=18 >>> request.POST
四、ajax发送json格式数据
我们可以使用模板语法实现从后端往前端发送数据,并在前端渲染出来,这是在前后端并不分离的前提下。但是在大多数情况下,前后端是分离的,我们必须使用json格式的数据,进行前后端数据之间的交互。
只有ajax才能实现向后端发送json格式的数据。
注意:
前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的
django针对json格式的数据,不会做任何的处理。
例如:通过ajax发送到后端的json格式数据{"username":"yangyi","age":18},在request.POST里面肯定找不到。
"""
request对象方法补充
request.is_ajax():判断当前请求是否是ajax请求,返回布尔值
"""
我们书写这样的前端代码进行测试。
<button class="btn btn-danger" id="d1">点我</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:JSON.stringify({'username':'yangyi','age':18}), //转化成json格式的数据
contentType:'application/json', // 指定编码格式
success:function () {
}
})
})
</script>
我们在后端使用如下方式接收。
# 研究数据编码格式
def index(request):
# 如果是ajax请求
if request.is_ajax():
print(request.POST)
print(request.FILES)
print(request.body) # b'{"username":"yangyi","age":18}'
return render(request, 'index.html')
解码和反序列化方式如下。
"""第一种方式"""
import json
def index(request):
# 如果是ajax请求
if request.is_ajax():
json_bytes = request.body
json_str = json_bytes.decode('utf-8') # 解码
json_dict = json.loads(json_str) # 反序列化
print(json_dict, type(json_dict)) # {'username': 'yangyi', 'age': 18} <class 'dict'>
return render(request, 'index.html')
"""第二种方式"""
# 其实还有一种更简单的方法,json.loads括号内如果传入了一个二进制格式的数据那么内部自动解码再反序列化。
import json
def index(request):
# 如果是ajax请求
if request.is_ajax():
json_bytes = request.body
json_dict = json.loads(json_bytes) # 直接将二进制的json格式字符串进行解码和反序列化
print(json_dict, type(json_dict)) # {'username': 'yangyi', 'age': 18} <class 'dict'>
return render(request, 'index.html')
总结:
"""
ajax发送json格式数据需要注意点
1.contentType参数指定成:application/json
2.数据是真正的json格式数据
3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
"""
五、ajax发送文件数据
现在我们通过form来往后端发送文件数据,那么如何使用ajax发送文件呢?
ajax发送文件需要借助于js内置对象FormData。
"""前端代码如下""" # Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCxnB9TbvvXa0V8RA
<p>username:<input type="text" id="d1"></p>
<p>password:<input type="text" 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 添加文件对象 $('#d3')[0]拿到原生的DOM标签
formDateObj.append('myfile',$('#d3')[0].files[0])
// 4 将对象基于ajax发送给后端
$.ajax({
url:'',
type:'post',
data:formDateObj, // 直接将对象放在data后面即可
// ajax发送文件必须要指定的两个参数
contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象
processData:false, // 告诉你的浏览器不要对你的数据进行任何处理
success:function (args) {
}
})
})
</script>
后端代码如下。
"""对传过来的键值对和文件,也是以同样的方式获取即可"""
def ab_file(request):
if request.is_ajax():
print(request.POST) # <QueryDict: {'username': ['yangyi'], 'password': ['123']}>
print(request.FILES) # <MultiValueDict: {'myfile': [<InMemoryUploadedFile: 美女.jpg (image/jpeg)>]}>
return render(request, 'ab_file.html')
总结:
# 1、需要利用内置对象FormData
let formDateObj = new FormData();
# 1.1、添加普通的键值对
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
# 1.2、添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0])
# 2、需要指定两个关键性的参数
ontentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象
processData:false, // 告诉你的浏览器不要对你的数据进行任何处理
# 3.django后端能够直接识别到formdata对象并且能够将内部的普通键值自动解析并封装到request.POST中 文件数据自动解析并封装到request.FILES中
六、django自带的序列化组件(drf做铺垫)
我们先使用django自带的sqlite3简单创建一个数据库表,代码如下。
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='性别')
数据库效果如下。
要求,把数据库中的数据在后端进行序列化,然后将json格式的数据返回给前端,而且是列表套字典【因为前端拿到数据之后for循环之后就能拿到数据对象,然后...】。[{},{},{},{},{}]
"""第一种方法:笨方法""" # [{},{},{},{},{}]
from django.http import JsonResponse
def ab_ser(request):
# 拿到数据库中的用户数据
user_queryset = models.User.objects.all()
user_list = []
for user_obj in user_queryset:
user_dic = {
'id': user_obj.pk,
'username': user_obj.username,
'age': user_obj.get_gender_display(),
}
user_list.append(user_dic)
return JsonResponse(user_list, safe=False, json_dumps_params={'ensure_ascii':False})
"""
[
{"id": 1, "username": "杨毅", "age": "male"},
{"id": 2, "username": "雷超", "age": "female"},
{"id": 3, "username": "安安", "age": "male"},
{"id": 4, "username": "杨鑫", "age": "others"},
{"id": 5, "username": "权康", "age": 4},
{"id": 6, "username": "港川", "age": "female"}
]
"""
我们可以在浏览器中得到如下效果。
"""第二种方法:django自带的序列化组件"""
from django.core import serializers
def ab_ser(request):
# 拿到数据库中的用户数据
user_queryset = models.User.objects.all()
# 序列化
res = serializers.serialize('json', user_queryset)
"""会自动帮你将数据变成json格式的字符串 并且内部非常的全面"""
return HttpResponse(res)
"""
[
{
"model": "app01.user",
"pk": 1,
"fields": {"username": "\u6768\u6bc5", "age": 18, "gender": 1}
},
{
"model": "app01.user",
"pk": 2,
"fields": {"username": "\u96f7\u8d85", "age": 20, "gender": 2}
},
{
"model": "app01.user",
"pk": 3,
"fields": {"username": "\u5b89\u5b89", "age": 23, "gender": 1}
},
{
"model": "app01.user",
"pk": 4,
"fields": {"username": "\u6768\u946b", "age": 24, "gender": 3}
},
{
"model": "app01.user",
"pk": 5,
"fields": {"username": "\u6743\u5eb7", "age": 24, "gender": 4}
},
{
"model": "app01.user",
"pk": 6,
"fields": {"username": "\u6e2f\u5ddd", "age": 25, "gender": 2}
}
]
"""
"""
前后端分离的项目,作为后端开发者,只需要写代码将数据处理好,能够序列化返回给前端即可,再写一个接口文档,告诉前端每个字段代表的意思。
"""
七、ajax结合sweetalert实现删除按钮的二次确认
sweetalert可以对弹出框进行一个美化,官方网址为:sweetalert
使用sweetalert之前,我们需要在html文件中先进行导入。
{% load static %}
<link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}">
<script src="{% static 'dist/sweetalert.js' %}"></script>
现在我们快速创建一个如下页面,仅将数据展示在前端页面。代码如下。
"""urls.py"""
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 用户展示页面
url(r'^user/list/', views.user_list)
]
"""views.py"""
def user_list(request):
user_queryset = models.User.objects.all()
return render(request, 'user_list.html', locals())
"""user_list.html"""
<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>
# 使用ajax之后,我们不需要在使用a标签,使用简单的按钮就可以。
<button class="btn btn-primary btn-xs">编辑</button>
<button class="btn btn-danger btn-xs del">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
浏览器效果如下。
我们给删除按钮,绑定事件,触发sweetalert效果。
<script>
$('.del').on('click', function () {
swal({
title: "Are you sure?",
text: "You will not be able to recover this imaginary file!",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "Yes, delete it!",
cancelButtonText: "No, cancel plx!",
closeOnConfirm: false,
closeOnCancel: false
},
function(isConfirm) {
if (isConfirm) {
swal("Deleted!", "Your imaginary file has been deleted.", "success");
} else {
swal("Cancelled", "Your imaginary file is safe :)", "error");
}
});
})
</script>
好了,到了这里,我们需要对代码进行简单的修改,即可实现ajax结合sweetalert操作。但是我们发现有一个小缺点,文字被覆盖了。
我们使用如下方式解决到这个小问题。
<style>
div.sweet-alert h2 {
padding-top: 10px;
}
</style>
好了,准备工作到此为止,我们现在正式开始添加ajax相关代码。
# 删除数据的时候,需要往后端传递删除数据的主键,我们可以给button绑定一个自定义的属性
<button class="btn btn-danger btn-xs del" delete_id="{{ user_obj.pk }}">删除</button>
ajax代码如下。
<script>
$('.del').on('click', function () {
// 我们被点击的删除按钮
let currentBtn = $(this) // this是被点击的DOM对象
swal({
title: "你确定要删除吗?",
text: "你将彻底删除掉这条数据,它可能相当重要呦~",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "是,请删除",
cancelButtonText: "不,请取消!",
closeOnConfirm: false,
closeOnCancel: false
},
function(isConfirm) {
if (isConfirm) {
// 朝后端发送ajax请求删除数据之后 再弹下面的提示框
$.ajax({
// url:'/delete/user/' + currentBtn.attr('delete_id') // 直接拼接,后端使用无名分组
url: '/delete/user/', // 2、删除id放在请求体里
type: 'post',
data: {'delete_id': currentBtn.attr('delete_id')},
success:function (args) {
// 回调函数之后,页面不会立马改变,必须进行刷新
// 1.lowb版本 直接刷新当前页面
{#window.location.reload() //有缺陷,如果不在第一个页面,刷新后,会进行页面跳转#}
// 2.利用DOM操作 动态刷新
currentBtn.parent().parent().remove()
}
})
swal("已删除!", "这条数据已被彻底删除.", "success");
} else {
swal("已取消!", "这条数据没有被删除:)", "error");
}
});
})
</script>
后端代码如下。
# 删除数据
def delete_user(request):
if request.is_ajax():
if request.method == 'POST':
delete_id = request.POST.get('delete_id')
models.User.objects.filter(pk=delete_id).delete()
return HttpResponse('已删除!!!') # 返回的数据没有被使用,仅仅是一个返回标志。
到了这里,我们还可以在进行一点优化,同时实现一个等待效果。
"""后端代码"""
# 前后端在用ajax进行交互的时候,后端通常给ajax的回调函数返回一个字典格式的数据,因为前端可以直接拿到一个object对象,方便操作
def delete_user(request):
if request.is_ajax():
if request.method == 'POST':
# 自定义一个响应码
back_dic = {"code":1000,'msg':''}
time.sleep(3) # 模拟操作数据的延迟【配合 showCancelButton: true,使用】
delete_id = request.POST.get('delete_id')
models.User.objects.filter(pk=delete_id).delete()
back_dic['msg'] = '数据已经删除,赶紧跑路吧!'
# 我们需要告诉前端我们操作的结果【JsonResponse返回的直接会被前端反序列化成一个object对象】
return JsonResponse(back_dic)
"""前端代码"""
<script>
$('.del').on('click', function () {
// 我们被点击的删除按钮
let currentBtn = $(this) // this是被点击的DOM对象
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') // 直接拼接,后端使用无名分组
url: '/delete/user/', // 2、删除id放在请求体里
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操作 动态刷新
currentBtn.parent().parent().remove()
}else{
swal("有错误!", "出现了未知的错误", "info")
}
}
})
} else {
swal("已取消!", "这条数据没有被删除:)", "error");
}
});
})
</script>
八、批量插入数据
加入我们创建10000张表,使用平时的方式进行创建,会发现速度很慢,那么有没有更快的方法呢?
我们先简单创建一张book表。
class Book(models.Model):
title = models.CharField(max_length=32)
代码如下所示。
"""第一种方式"""
"""views.py"""
def ab_pl(request):
# 循环创建10000张表
for i in range(10000):
models.Book.objects.create(title='第{}张表'.format(i))
book_queryset = models.Book.objects.all()
return render(request, 'ab_pl.html', locals())
"""ab_pl.html"""
{% for book_obj in book_queryset %}
<p>
{{ book_obj.title }}
</p>
{% endfor %}
我们发现速度有点慢,浏览器一直刷新,持续5秒左右才显示。
那么我们换一种方式创建表,这次创建100000张表。
"""第二种方式"""
def ab_pl(request):
book_list = []
for i in range(100000):
# 创建数据对象
book_obj = models.Book(title='第{}本书'.format(i))
book_list.append(book_obj)
# 批量创建数据
models.Book.objects.bulk_create(book_list)
book_queryset = models.Book.objects.all()
return render(request, 'ab_pl.html', locals())
我们发现100000张表,并展示到前端页面的速度仍然很快,和以前的方法是数量级的差距。
当你想要批量插入数据的时候,使用orm给你提供的bulk_create能够大大的减少操作时间。
九、自定义分页器实现
我们需要实现一个分页器,如何通过代码动态的计算出到底需要多少页?同时进行页面的跳转,在制作页码个数的时候,一般情况下都是奇数个,比较符合审美。
我们先简单进行一下推导。
"""
per_page_num = 10
current_page start_page end_page
1 0 10
2 10 20
3 20 30
4 30 40
per_page_num = 5
current_page start_page end_page
1 0 5
2 5 10
3 10 15
4 15 20
start_page = (current_page - 1) * per_page_num
end_page = current_page * per_page_num
"""
我们可以通过代码实现效果如下所示。
代码如下:
"""ab_pl.html"""
{% for book_obj in book_queryset %}
<p>
{{ book_obj.title }}
</p>
{% endfor %}
<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>
"""views.py"""
def ab_pl(request):
# 提取数据库中所有的数据对象列表
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_num = (current_page - 1) * per_page_num
# 终止字段数
end_num = current_page * per_page_num
# 总共有多少条数据
all_count = book_list.count()
# 使用内置函数,进行分页计算
page_count, more = divmod(all_count, per_page_num)
if more:
page_count += 1
# 因为前端模板语法没有range功能,所以在后端书写前端代码。
# 前端代码不一定非要在前端书写,也可以在后端生成展示给前端。
page_html = ''
xxx = current_page
# 如果当前页小于6页,则不进行跳转。
if current_page < 6:
current_page = 6
# 如果当前页大于最后一页减6,则不进行跳转
if current_page > page_count - 6:
current_page = page_count - 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_num:end_num]
# 返回页面
return render(request, 'ab_pl.html', locals())
十、自定义分页器的拷贝及使用
当我们需要使用到非django内置的第三方功能或者组件代码的时候,我们一般情况下会创建一个名为utils文件夹,在该文件夹内对模块进行功能性划分,当然,utils可以在每个应用下创建,具体使用结合实际情况。
我们自定义的分页器是基于bootstrap样式来的,所以你需要提前导入bootstrap。
自定义分页器模板代码。【我们到了后期封装代码的时候,不再局限于函数,还是尽量朝面向对象去封装。】
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)
使用方式如下。我们在utils文件夹下面,建立一个mypage.py文件,将代码拷贝其中。具体使用方式如下。
"""后端"""
from utils.mypage import Pagination
def ab_pl(request):
# 拿到所有的书籍查询集
book_queryset = models.Book.objects.all()
# 拿到点击的页数
current_page = request.GET.get('page', 1)
# 总共有多少条数据
all_count = book_queryset.count()
# 1、传值生成对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2 直接对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 3 将page_queryset传递到页面 替换之前的book_queryset
return render(request, 'ab_pl.html', locals())
"""前端"""
{#每页显示的数据#}
{% for book_obj in page_queryset %}
<p>
{{ book_obj.title }}
</p>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}
最终效果显示如下。