Python关于Paginator分页函数详细解析 附实战代码

佟寒
2023-12-01

前言

讲解python的分页器之前,科普一下java的分页器
每门编程语言都有一个分页的工具,类似java有PageHelper分页插件
Mybatis章节中有具体阐述:Mybatis从入门到精通(全)
对于实战中的引入,也可看我这篇文章:米米商城项目实战(含项目源码)

而Paginator是Django的一个分页工具,通过分页工具进行分页,将其数据源获取之后,定义每页显示的个数拆分(其他编程语言的分页工具大同小异),了解其逻辑原理以及函数即可
这些函数主要位于django/core/paginator.py

官网说明文档:Paginator的说明文档
主要的函数源码:Source code for django.core.paginator

1. 函数讲解

熟知一个功能使用,了解其函数源码必不可少

1.1 Paginator类

Paginator的函数讲解:

class Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)

其参数说明:

  • object_list(必选项):一个列表、元组、QuerySet或其他具有count()或__len__()方法的可切片对象
  • per_page(必选项):页面上要包含的最大条目数
  • orphans(可选):默认为0,表示最后一页不会合并,可能只有一条数据。如果最后一页小于或者等于orphans,前一页的数据添加在最后一页成为最后一页(有23个数据,per_page=10,和orphans=3,第一页10个数据,第二页13个数据)
  • (可选):是否允许第一页为空。False且object_list为空,则会引发EmptyPage错误

其方法中有:

函数描述
Paginator.get_page返回Page具有给定从 1 开始的索引的对象,同时还处理超出范围和无效页码
Paginator.page(数字)返回Page具有给定从 1 开始的索引的对象

其属性有如下三个:

  • Paginator.count 数据总量
  • Paginator.num_pages 总页数
  • Paginator.page_range 页码迭代器

1.2 Page类

一般不会用page来构建对象,使用Paginator.page(下面的实战代码中也有讲到)

函数描述
Page.has_next ()有下一页,返回true
Page.has_previous ()有上一页,返回true
Page.has_other_pages ()有上下页,返回true
Page.next_page_number ()返回下个页号,如果下页不存在,引发InvalidPage异常
Page.previous_page_number ()返回上个页号,如果上页不存在,引发InvalidPage异常
Page.start_index()返回页面上第一个对象的从 1 开始的索引
Page.end_index()返回页面上最后一个对象的从 1 开始的索引

其属性有:

  • Page.object_list 此页面上的对象列表
  • Page.number 此页面的从 1 开始的页码
  • Page.paginator 关联的Paginator对象

处理分页时候的异常一般有如下:

异常信息描述
异常InvalidPage向分页器传递无效页码时引发的异常的基类
异常PageNotAnIntegerpage不是一个整数数值。可通过except InvalidPage
异常EmptyPagepage为有效数值,但该页面上不存在对象引发。可通过except InvalidPage

2. 实战讲解

对于函数中的**kwargs解释,可看我这篇文章:Python关于 *args 和 **kwargs 参数的详解(全)

结合form表单进行获取数据

@require_GET
@login_required
# 省略详细内容注解 @form_check
def AudioProject(request):
    page_num = request.form.cleaned_data['page']
    search_projname = request.form.cleaned_data.get('projname') or None
    start_time, end_time = None, None
    if date_range:
        start_time, end_time = request.form.cleaned_data.get('range')
        start_time = datetime.datetime.strptime(start_time, "%Y-%m-%d %H:%M")
        end_time = datetime.datetime.strptime(end_time, "%Y-%m-%d %H:%M")
    # 根据种类进行过滤查询
    page = models.get_projects_by_page(page=page_num, projname=search_projname, start_time=start_time, end_time=end_time)

核心函数方法:

# 全局变量的setting PAGE_SIZE设置为15页
def get_projects_by_page(page=1, size=settings.PAGE_SIZE, **kwargs):

	# 此处主要判断 value值为空,既将key也清空
	# python web中的应用 和此处的Paginator 无直接关系,主要阐明其意思
    for k, v in kwargs.items():
        if v is None:
            kwargs.pop(k)
    
    # 调用该函数,通过获取数据源,该数据源可以使列表、元祖以及结果集等,
    # 再代入size ,每页显示的个数
    project_paginator = Paginator(get_search_projects(**kwargs), size)

	# 优化显示分页,为了统一分页
    project_page = get_page(project_paginator, page)
    return project_page

统一的分页处理:

def get_page(paginator, page):
    """统一的分页管理."""
    try:
        page_datas = paginator.page(page)
    except PageNotAnInteger:
        page_datas = paginator.page(1)
    except EmptyPage:
    	# Paginator.num_pages 
        page_datas = paginator.page(paginator.num_pages)

    return page_datas

以下html 为 python template的模板
只讲解Paginator的核心代码

{% macro show_page(errors, page) %}
    {% if not errors %}
        <div class="btn-group">
<!--            页码的基于 1 的范围迭代器-->
            {% if page.paginator.page_range | length > 10 %}
<!--            如果有前一页则返回-->
                {% if page.has_previous() %}
<!--             value值返回上一个页码-->
                    <button type="button"
                            value="{{ page.previous_page_number() }}"
                            class="btn btn-white btn-page"><i
                            class="fa fa-chevron-left"></i></button>
                {% endif %}
<!--            page.number为从页面1开始的页码-->
                {% for page_idx in page.paginator.page_range[
                ((0, page.number - 5)|sort)[-1]: page.number + 4] %}
                    <button value="{{ page_idx }}"
                            class="btn btn-page btn-white {% if page_idx == page.number %}active{% endif %}">{{ page_idx }}</button>
                {% endfor %}

                <input type="text" class="form-control"
                       id="pageJumperInputID" style="width: 50px; display:
                       inline-block" value="{{ page.number }}">
                <button class="btn btn-primary btn-page-jumper"
                        id="pageJumperButtonID"
                        style="float: inherit">跳转
                    (共{{ page
                .paginator.num_pages }}页)
                </button>
                
                <!-- 如果有下一页-->
                {% if page.has_next() %}
                <!--                value值返回下一个页码-->
                    <button value="{{ page.next_page_number() }}" type="button"
                            class="btn btn-page btn-white"><i
                            class="fa fa-chevron-right"></i></button>
                {% endif %}
            {% else %}
            <!-- 如果有前一页则返回-->
                {% if page.has_previous() %}
            <!--                value值返回上一个页码-->
                    <button type="button"
                            value="{{ page.previous_page_number() }}"
                            class="btn btn-white btn-page"><i
                            class="fa fa-chevron-left"></i></button>
                {% endif %}
                {% for page_idx in page.paginator.page_range %}
                    <button value="{{ page_idx }}"
                            class="btn btn-page btn-white {% if page_idx == page.number %}active{% endif %}">{{ page_idx }}</button>
                {% endfor %}
                {% if page.has_next() %}
                    <button value="{{ page.next_page_number() }}" type="button"
                            class="btn btn-page btn-white"><i
                            class="fa fa-chevron-right"></i></button>
                {% endif %}
            {% endif %}
        </div>
    {% endif %}
{% endmacro %}

分页的js代码模块:

{% macro show_page_js() %}
    <script>
        $(document).ready(function () {
            $(".btn-page-jumper").bind("click", function (e) {
                var page = $("#pageJumperInputID").val();
                if (typeof (fliover) == "function") {
                    fliover(page);
                } else {
                    const matchReg = /[&|?]page=.*/g;
                    const search_prefix = window.location.search.replace(matchReg, "");
                    if(search_prefix !== ""){
                        window.location.assign(search_prefix + "&page=" + page);
                    }else{
                        window.location.assign("?page=" + page);
                    }
                }
            });
            $(".btn-page").bind("click", function (e) {
                var page = this.value;
                if (typeof (fliover) == "function") {
                    fliover(page);
                } else {
                    const matchReg = /[&|?]page=.*/g;
                    const search_prefix = window.location.search.replace(matchReg, '')
                    if(search_prefix !== ""){
                        window.location.assign(search_prefix + "&page=" + page);
                    }else{
                        window.location.assign("?page=" + page);
                    }
                }
            });
            var input = document.getElementById("pageJumperInputID");
            if (input != undefined) {
                input.addEventListener("keyup", function (event) {
                    // Cancel the default action, if needed
                    event.preventDefault();
                    // Number 13 is the "Enter" key on the keyboard
                    if (event.keyCode === 13) {
                        // Trigger the button element with a click
                        document.getElementById("pageJumperButtonID").click();
                    }
                });
            }
        });
    </script>
{% endmacro %}
 类似资料: