55. django之Ajax

1. Ajax介绍

1.1 概念

AJAX(Asynchronous Javascript And XML)  异步的Javascript和XML,是一种用于创建动态网页的编程技术。

它通过在网页上进行异步数据传输,实现了在不重新加载整个页面的情况下更新部分页面内容的能力
Ajax的核心技术包括使用JavaScript、XML、HttpRequest对象与服务器进行数据交互,以及利用DOM(Document Object Model)来动态地更新页面。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)

虽然Ajax最初是指Asynchronous JavaScript and XML,但如今已经不仅限于使用XML作为数据传输的格式,而是可以使用各种格式,如JSON(JavaScript Object Notation)。
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
  同步交互:
    客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
  异步交互:
    客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

1.2 核心作用

异步提交 局部刷新

页面不刷新的前提下可以与后端进行数据交互

eg:某些网站的注册页面,动态获取用户名,实时与后端确认并将结果展示给前端,防止重复

1.3 补充

Ajax的本质是js代码,使用Ajax的前提是必须引入jQuery文件

Ajax提交数据页面无需刷新,输入框的数据还在,处理数据的过程不影响页面其它操作

form表单提交数据页面需要刷新,输入框的数据清空,处理数据的过程无法进行其它操作

1.4 向后端发送请求的方式

浏览器地址直接url回车
  GET请求
a标签的 href 属性
  GET请求
form表单
  GET请求/POST请求
Ajax
  GET请求/POST请求

2. Ajax案例

2.1 计算器

[1] 需求

页面上有3个input框和1个提交按钮,前两个框输入数字

点击按钮,朝后端发送Ajax请求,在后端计算出结果,返回给第3个input框动态展示

效果:整个过程页面不会刷新,不会在前端计算

[2] 代码

准备工作:

settings.py中配置数据库、注释csrf、配置静态文件

init.py中给数据库打猴子补丁

项目-static-jQuery-jQuery.js

功能实现:

app01的views.py中定义视图函数

from django.shortcuts import render

# Create your views here.

def add(request):
    return render(request ,'add.html')

app01的urls.py中定义路由

from app01 import views
from django.urls import path

urlpatterns = [
    path('add/', views.add),
]

项目的urls.py中定义路由

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/', include('app01.urls')),
]

app01的templates目录定义add.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="{% static 'jquery/jquery.js' %}"></script>
</head>
<body>
<input type="text" id="d1">+<input type="text" id="d2">=<input type="text" id="d3">
<button id="btn1">计算</button>
<script>
    // 给计算按钮绑定点击事件
    $('btn1').click(function (){
        let d1Val = $('#d1').val();  // 获取输入框的数据
        let d2Val = $('#d2').val();
        // 发送Ajax请求,传输数据
        $.ajax({
            url: '',  // Ajax提交数据的路径,不写则为当前页面的路径
            type: 'post',  // Ajax请求的方式
            data: {'d1': d1Val, 'd2': d2Val},  // 请求提交给后端的数据
            // 异步回调函数,后端有返回值自动触发,args是后端返回的数据
            success: function (args){
                // 将返回的数据赋值给id为d3元素的value属性
                $('#d3').val(args)
            }
        })
    })
</script>
</body>
</html>

点击计算按钮时,第3个输入框得到结果,而页面不会刷新

image

2.2 注册验证

app01的templates的add.html

<body>
<p>用户名:<input type="text" id="d1"></p>
<script>
    $('#d1').on('input', function (){
        $.ajax({
            url: '',  // Ajax提交数据的路径,不写则为当前页面的路径
            type: 'get',  // Ajax请求的方式
            data: {'d1': $(this).val()},  // 请求提交给后端的数据
            // 异步回调函数,后端有返回值自动触发,args是后端返回的数据
            success: function (args){
                alert(args)
            }
        })
    })
</script>
</body>

app01的views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def add(request):
    print(request.is_ajax())  # 判断是否为Ajax请求
    if request.is_ajax():
        if request.method == "GET":
            name = request.GET.get("d1")
            if name == 'avril':
                return HttpResponse('用户名avril已存在')
            elif name == 'kylian':
                return HttpResponse('用户名kylian已存在')
            elif name == 'haaland':
                return HttpResponse('用户名haaland已存在')
    return render(request ,'add.html')

image

2. 前后端传输数据编码格式

前后端传输数据的编码格式:urlencoded、formdata、json

请求体查看数据传输的编码方式  Content-Type

[1] form表单传输文本数据

app01的templates的add.html

<form action="" method="post">
    <input type="text" name="用户名">
    <input type="text" name="密码">
    <input type="submit">
</form>
</body>

浏览器点击提交发起post请求时

image

数据格式

image

后端视图函数中获取数据   request.POST

def add(request):
    if request.method == 'POST':
        print(request.POST)
    return render(request, 'add.html')

image

form表单默认的编码格式:urlencoded
传输数据格式:username=666666&password=66

[2] form表单传输文件

app01的templates的add.html

<body>
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file1">
    <input type="submit">
</form>
</body>

传输文件的编码格式

image

传输文件无法在浏览器查看数据格式

后端视图函数中可以查看数据  request.FILES

Django后端针对 urlencoded 编码格式的数据会自动帮我们解析封装到 request.POST 中

如果编码格式改为 formdata ,那么针对普通的键值对还是解析到 request.POST 中,而其他文件格式的数据解析到 request.FILES 中

[3] ajax传输数据

ajax的编码格式Content-Type以及后端查看数据方式与form表单一致

3. Ajax发送JSON、文件数据

3.1 JSON知识储备

[1] JSON介绍

JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
JSON 是轻量级的文本数据交换格式

JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。
JSON 解析器和 JSON 库支持许多不同的编程语言。

合格的json数据(json只识别双引的字符串格式):
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ] 

不合格的json数据:
{ name: "张三", 'age': 32 } // 属性名必须使用双引号
[32, 64, 128, 0xFFF] // 不能使用十六进制值
{ "name": "张三", "age": undefined } // 不能使用undefined
{ "name": "张三",
"birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
"getName": function() {return this.name;} // 不能使用函数和日期对象
}

[2] JavaScript序列化JSON数据方法

(1)JSON.parse()
用于将一个 JSON 字符串转换为 JavaScript 对象(json只识别双引号的字符串格式)
JSON.parse('{"name":"Howker"}');
JSON.parse('{name:"Stack"}') ; // 错误
JSON.parse('[18,undefined]') ; // 错误

(2)JSON.stringify()
用于将 JavaScript 值转换为 JSON 字符串。
JSON.stringify({"name":"Tonny"})

3.2 Ajax发送JSON数据

[1] 前提

form表单无法发送json数据

[2] 不指定contentType

app01的templates的add.html

<body>
<button id="d1">发送json格式数据</button>
<script>
    $('#d1').click(function (){
        $.ajax({
            url: '',
            type: 'post',
            data: JSON.stringify({'name':'avril', 'pwd':123}),
            success:function (args){

            }
        })
    })
</script>
</body>

app01的views.py

def add(request):
    if request.method == 'POST':
        print(request.POST)
    return render(request, 'add.html')

在前端点击按钮时,整个数据变为字典的键

image

image

总结:不指定contentType时,默认编码格式是urlencoded,发送的数据格式是JSON 即data: JSON.stringify({'name':'avril', 'pwd':123})

Ajax发送的数据类型一定要和数据的编码格式一致

[3] 指定contentType为JSON

app01的add.html

<body>
<button id="d1">发送json格式数据</button>
<script>
    $('#d1').click(function (){
        $.ajax({
            url: '',
            type: 'post',
            contentType: 'application/json',  // 不写默认是urlencoded
            data: JSON.stringify({'name':'avril', 'pwd':123}),
            success:function (args){

            }
        })
    })
</script>
</body>

前端点击按钮时,后端接收到的内容为空而非json数据

image

后端针对json格式的数据不会作任何处理,存放在request.body内

def add(request):
    if request.method == 'POST':
        print(request.POST)
        print(request.body)  # 返回的是二进制数据
    return render(request, 'add.html')

image

json数据需要手动处理

def add(request):
    if request.method == 'POST':
        print(request.POST)
        print(request.body)
        json_bytes = request.body
        json_data = json.loads(json_bytes)
        print(json_data, type(json_data))

    return render(request, 'add.html')

image

  # (1)方式一:先解码 再转换数据格式
    json_str = json_bytes.decode('utf-8')
    json_dict = json.loads(json_str)
    print(json_dict, type(json_dict))  # {'username': 'dream', 'password': 521521} <class 'dict'>

    # (2)方式二:json.loads(二进制数据) 内部可以自动解码再反序列化
    json_dict_loads = json.loads(json_bytes)
    print(json_dict_loads, type(json_dict_loads))  # {'username': 'dream', 'password': 521521} <class 'dict'>

3.3 Ajax发送文件数据

Ajax发送文件数据需要借助js内置对象formdata

app01的add.html

<body>
<input type="text" id="d1" name="name">
<input type="text" id="d2" name="pwd">
<input type="file" id="d3" name="abc">
<button id="d5">发送Ajax请求</button>
<script>
    $('#d5').click(function (){
        let fdObj = new FormData  // 生成内置对象
        // 对象添加键值对
        fdObj.append('name', $('#d1').val())
        fdObj.append('pwd', $('#d2').val())
        // 对象添加文件数据
        fdObj.append('abc', $('#d3')[0].files[0])
        // 发送Ajax请求
        $.ajax({
            url: '',
            type: 'post',
            data: fdObj,

            // Ajax发送文件必须添加的两个参数
            contentType:false,  // 不需要任何编码,Django后端能自动识别 formdata 对象
            processData:false,  // 给浏览器发送信号:无需对数据进行任何处理
            success:function (args){

            }
        })
    })
</script>
</body>

app01的views.py

def add(request):
    if request.method == 'POST':
        print(request.POST)  # 键值对数据
        print(request.FILES)  # 文件数据

    return render(request, 'add.html')

image

image

总结:

(1)Ajax发送文件必须添加的两个参数
// 不需要使用任何编码 - Django后端能自动识别 formdata 对象
contentType: false,
// 给浏览器发送信号,不要对数据进行任何处理
processData: false,
(2)后端解析数据
Django后端能直接自动识别到FormData对象
将内部的普通键值对自动解析并封装到request.POST中
将内部的文件数据自动解析并封装到request.FILES中

4. django自带序列化组件serializers

4.1 需求

在前端获取后端用户表里所有的数据,格式是列表套字典,不能是queryset

4.2 基于JsonResponse序列化数据

app01的models.py中定义模型表

class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    data = ((1, 'run'), (2, 'rap'), (3, 'play'))
    hobby = models.IntegerField(choices=data)
    addr = models.CharField(max_length=32)

运行迁移任务,添加基础数据

image

项目urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/', include('app01.urls')),
]

app01的urls.py

from app01 import views
from django.urls import path

urlpatterns = [
    path('serialtool/', views.serialtool),
]

app01的views.py

from django.http import JsonResponse
from app01 import models

# Create your views here.

def serialtool(request):
    data_list = []  # 期望的数据格式: [{}, {}, {}]
    user_queryset = models.User.objects.all()
    for user_obj in user_queryset:
        data_list.append({
            'pk': user_obj.pk,
            'name': user_obj.name,
            'age': user_obj.age,
            'hobby': user_obj.hobby,
            'hobby_real': user_obj.get_hobby_display(),  # 获取实际数据而非主键值
            'addr': user_obj.addr
        })
    return JsonResponse(data_list, safe=False)

django的orm产生的queryset无法直接被前端识别,需要json格式数据

image

4.3 基于Django自带的serializers组件序列化数据

serializers 模块会将数据全部变成JSON格式的字符串,并且内部非常的全面

app01的views.py

from django.shortcuts import render, HttpResponse
from app01 import models
from django.core import serializers

# Create your views here.

def serialtool(request):
    user_queryset = models.User.objects.all()
    data = serializers.serialize('json', user_queryset)  # 第一个参数的含义是以哪种格式序列化数据
    return HttpResponse(data)  # 无需使用JsonResponse,使用HttpResponse即可

image

5. 批量操作数据

5.1 需求与准备

项目需求:
浏览器中访问django后端某一条url(如:127.0.0.1:8080/index/)
实时朝数据库中生成一千条数据,并将生成的数据查询出来展示到前端页面

app01的models.py中,定义图书模型表,迁移至数据库

class Book(models.Model):
    title = models.CharField(max_length=32)

app01的urls.py

from app01 import views
from django.urls import path

urlpatterns = [
    path('addbook/', views.addbook),
]

 app01的addbook.html

{% for book_obj in book_queryset %}
    <p>{{ book_obj.title }}</p>
{% endfor %}

5.2 单条插入数据

app01的views.py

def addbook(request):
    # (1)先给Book表插入一千条数据
    for i in range(1000):
        models.Book.objects.create(title=f'第{i}本书')
    # (2)将所有数据查询到并展示给前端页面
    book_queryset = models.Book.objects.all()
    # (3)将查询出来的数据传递给html页面
    return render(request, 'addbook.html', locals())

启动django后端,浏览器访问,会发现浏览器会有一个明显的卡顿等待时间

这不是浏览器问题也不是网速问题
而是后端再不停的操作数据库,耗时较长,大概需要等待一段时间之后才能正常看到插入的1000条数据
很明显这样操作数据库的效率太低

5.3 批量插入数据

当需要向数据库批量插入数据的时候,使用ORM提供的bulk_create方法能够大大的减少操作的时间

app01的views.py

def bulkdata(request):
    book_list = []
    for i in range(1000):
        book_obj = models.Book(title=f'第{i}本书')  # 先用类产生一个对象,而不是直接向数据库插入数据
        book_list.append(book_obj)  # 将对象添加到列表中
    models.Book.objects.bulk_create(book_list)  # 批量插入数据
    book_queryset = models.Book.objects.all()
    return render(request, 'bulkdata.html', locals())

6. 分页器推导

6.1 引入

浏览器的一页展示的数据量过多的情况下,查看不方便,需要进行分页处理

6.2 代码实现

[1] 理论

get请求可以携带参数,在往后端发送查看数据请求的同时,可以发送一个参数给后端想要查看第几页的数据
queryset对象是支持索引取值和切片操作的,但是不支持负数索引

[2] 后端分析

# 获取用户想访问的页码  
current_page = request.GET.get("page")  

# 由于后端接受到的前端数据是字符串类型,所以需要做类型转换处理加异常捕获(# 如果没有到页码默认展示第一页)
try:  
  current_page = int(current_page)
except Exception as e:
  current_page = 1
  
# 需要定义页面展示多少条数据
per_page_count= 10  # 一页展示10条数据
 
# 需要对总数据进行切片操作,以确定第n页的起始计数与结束计数
start_page = ? 
end_page = ?

分析第n页、每页数据条数、每页起始计数、每页结束计数四个参数之间的数据关系

    每页展示10条  per_page_count = 10
    query_page  start_count  stop_count     索引取值顾头不顾尾
        1           0           10
        2           10          20
        3           20          30
        
    每页展示5条  per_page_count = 5
    query_page  start_count  stop_count     索引取值顾头不顾尾
        1           0           5
        2           5           10
        3           10          15

规律:

    start_count = (query_page-1)*per_page_count
    stop_count = query_page*per_page_count

[3] 前后端代码

app01的urls.py与项目urls.py

from app01 import views
from django.urls import path

urlpatterns = [
    path('bulkdata/', views.bulkdata),
]
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/', include('app01.urls')),
]

app01的views.py

def bulkdata(request):
    book_list = []
    for i in range(1000):
        book_obj = models.Book(title=f'第{i}本书')  # 先用类产生一个对象,而不是直接向数据库插入数据
        book_list.append(book_obj)  # 将对象添加到列表中
    models.Book.objects.bulk_create(book_list)  # 批量插入数据

    query_page = request.GET.get('page')  # 1.获取用户想要查看的页码
    try:  # 2.由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
        query_page = int(query_page)
    except Exception as e:  # 3.如果没有获取到页码,则展示第一页
        query_page = 1
    per_page_count = 10  # 4.每页展示几条数据
    start_count = (query_page - 1) * per_page_count  # 5.第n页的起始与结束计数
    stop_count = query_page * per_page_count
    book_queryset = models.Book.objects.all()[start_count:stop_count]  # 5.查询第n页的开始与结束计数

    return render(request, 'bulkdata.html', locals())

app01的models.py

class Book(models.Model):
    title = models.CharField(max_length=32)

app01的bulkdata.html

<body>
    {% for book_obj in book_queryset %}
        <p>{{ book_obj.title }}</p>
    {% endfor %}

</body>

浏览器访问

通过在 url 后面携带参数,完成分页操作  http://localhost:8000/app01/bulkdata/?page=11

image

image

这种方式没有对前端页面进行优化,仅仅只是后端部分完成了分页

6.3 分页器组件

使用bootstrap的默认分页(Default pagination)对前端进行优化

app01的bulkdata.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">&laquo;</span>
            </a>
        </li>
        <li><a href="?page=1">1</a></li>  // 只需要修改这部分
        <li><a href="?page=2">2</a></li>
        <li><a href="?page=3">3</a></li>
        <li><a href="?page=4">4</a></li>
        <li><a href="?page=5">5</a></li>
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

</body>

image

image

效果:

仍然可以使用url指定页码进行分页

也可以使用底部的分页器进行分页,但是页码范围与前端代码的a标签所写的范围保持一致

6.4 动态计算页数 

[1] 分析

问题1:总数据有100条,每页展示10条,总共需要几页?
答案:10条
问题2:总数据有101条,每页展示10条,总共需要几页?
答案:11条
问题3:总数据有99条,每页展示10条,总共需要几页?
答案:10条

如何动态的计算出需要多少页?

[2] 内置函数之divmod

divmod(x, y)
用于运行整数除法和取模运算,并返回一个包含商和余数的元组。
参数x和y是两个数字
  x 是被除数
  y 是除数

result = divmod(9, 2)
print(result)  # 输出 (4, 1)

result = divmod(14, 3)
print(result)  # 输出 (4, 2)

[3] 前后端代码

app01的views.py

def bulkdata(request):
    book_list = []
    for i in range(1000):
        book_obj = models.Book(title=f'第{i}本书')  # 先用类产生一个对象,而不是直接向数据库插入数据
        book_list.append(book_obj)  # 将对象添加到列表中
    models.Book.objects.bulk_create(book_list)  # 批量插入数据

    query_page = request.GET.get('page')  # 1.获取用户想要查看的页码
    try:  # 2.由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
        query_page = int(query_page)
    except Exception as e:  # 3.如果没有获取到页码,则展示第一页
        query_page = 1
    per_page_count = 10  # 4.每页展示几条数据
    start_count = (query_page - 1) * per_page_count  # 5.第n页的起始与结束计数
    stop_count = query_page * per_page_count
    data_queryset = models.Book.objects.all()  # 6.获取图书数据集
    book_queryset = data_queryset[start_count:stop_count]  # 7.查询第n页的开始与结束计数
    book_count = data_queryset.count()  # 8.计算所有图书数量
    page_count, more = divmod(book_count, per_page_count)  # 9.more用来接收计算结果元组的第二个元素(余数)
    if more:
        page_count += 1
    # 10.前端动态页面展示,后端写好标签传给前端
    url_num = ''
    for i in range(1, page_count + 1):  # 页码从1开始
        url_num += f'<li><a href="?page={i}">{i}</a></li>'
    return render(request, 'bulkdata.html', locals())

app01的bulkdata.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">&laquo;</span>
            </a>
        </li>
        {% comment %}必须使用safe进行转义,也可以在后端进行转义{% endcomment %}
        {{ url_num|safe }}
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>
</body>

image

image

完成了简易版的分页操作,问题是前端页面展示的页码过多

6.5 分页器美化

需求:以当前页为中心,左右两边各展示5个页码

app01的views.py

def bulkdata(request):
    book_list = []
    for i in range(1000):
        book_obj = models.Book(title=f'第{i}本书')  # 先用类产生一个对象,而不是直接向数据库插入数据
        book_list.append(book_obj)  # 将对象添加到列表中
    models.Book.objects.bulk_create(book_list)  # 批量插入数据

    query_page = request.GET.get('page')  # 1.获取用户想要查看的页码
    try:  # 2.由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
        query_page = int(query_page)
    except Exception as e:  # 3.如果没有获取到页码,则展示第一页
        query_page = 1
    per_page_count = 10  # 4.每页展示几条数据
    start_count = (query_page - 1) * per_page_count  # 5.第n页的起始与结束计数
    stop_count = query_page * per_page_count
    data_queryset = models.Book.objects.all()  # 6.获取图书数据集
    book_queryset = data_queryset[start_count:stop_count]  # 7.查询第n页的开始与结束计数
    book_count = data_queryset.count()  # 8.计算所有图书数量
    page_count, more = divmod(book_count, per_page_count)  # 9.more用来接收计算结果元组的第二个元素(余数)
    if more:
        page_count += 1

    # 12.防止当前页小于6时,左侧出现负数页的问题
    query_page_real = query_page
    if query_page < 6:
        query_page = 6

    # 10.前端动态页面展示,后端写好标签传给前端
    url_num = ''
    # 11.以当前页为中心,左右两边各展示5个页码
    for i in range(query_page - 5, query_page + 6):
        if i == query_page_real:
            url_num += f'<li class="active"><a href="?page={i}">{i}</a></li>'  # 对当前页进行高亮展示
        else:
            url_num += f'<li><a href="?page={i}">{i}</a></li>'
    return render(request, 'bulkdata.html', locals())

image

image

6.6 封装分页器

[1] 自定义分页器封装

utils目录是django的工具集仓库,用于存放通用、低耦合、跨模块复用的辅助函数、类与常量。

项目里新建utils目录,目录里新建分页器文件paging.py,文件里定义类

将分页器的代码从前端封装到了后端

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)

[2] 使用封装分页器

app01的views.py

from django.shortcuts import render, HttpResponse
from app01 import models
from utils import paging

# Create your views here.

def bulkdata(request):
    book_list = []
    for i in range(1000):
        book_obj = models.Book(title=f'第{i}本书')  # 先用类产生一个对象,而不是直接向数据库插入数据
        book_list.append(book_obj)  # 将对象添加到列表中
    models.Book.objects.bulk_create(book_list)  # 批量插入数据

    data_queryset = models.Book.objects.all()  # 1.获取图书数据集
    query_page = request.GET.get('page')  # 2.获取用户想要查看的页码
    paginator = paging.Pagination(current_page=query_page, all_count=data_queryset.count())  # 3.产生分页器对象
    book_queryset = data_queryset[paginator.start:paginator.end]  # 4.使用分页器对图书数据集进行分页
    return render(request, 'bulkdata.html', locals())

app01的bulkdata.html

<body>
{% for book_obj in book_queryset %}
    <p>{{ book_obj.title }}</p>
{% endfor %}
{{ paginator.page_html|safe}}   {% comment %}转义{% endcomment %}
</body>

增加了部分功能

image

image

 

posted @ 2026-04-16 20:24  pythondjango  阅读(14)  评论(0)    收藏  举报