Django框架—分页器paginator

一、Django内置paginator分页器

1.数据准备

models.py文件创建表类

from django.db import models
# Create your models here.
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()
    authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid")
    def __str__(self):
        return self.name
class AuthorDetail(models.Model):
​
    nid = models.AutoField(primary_key=True)
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)
​
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()
    def __str__(self):
        return self.name
​
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid")
    authors=models.ManyToManyField(to='Author',)
    def __str__(self):
        return self.title
数据模型建立

指定数据库同步指令:makemigrations和migrate

在views外使用Django环境创建数据

在应用文件app01下创建一个py文件,名字随意,如init_book_table.py

import os
​
if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day068_model_form_lesson.settings")  # 引入django的环境
    import django
    django.setup()  # 运行Django环境,之后可以使用django中的其他文件
    from app01 import models
    import random
​
    l1 = []
    for i in range(1, 81):  # 创建100条记录
        obj = models.Book(
            title='红楼梦第%s回'%i,
            price= random.randint(100,200),
            publishDate='1998-06-%s'%(random.randint(1,30)),
            publish_id=random.randint(1,3),
        )
        l1.append(obj)
    models.Book.objects.bulk_create(l1)

创建好后,直接右键运行即可,就可以用DJango环境执行这段代码,在数据库中创建100天记录。

2.paginator对象常用属性和方法

使用django内置的分页器需要导入分页器类

from django.core.paginator import Paginator,EmptyError  # EmptyError是一个没有取到页面的错误

通过给Paginator类传递所有数据的queryset对象,以及每页显示数量来实例化一个paginator对象。这个对象有所有对分页的处理方法。

paginator对象属性

paginator.count:返回所有记录的总数量

paginator.num_pages:返回分页后的总页数

paginator.page_range:返回分页后的页码范围,一个range对象

all_books = models.Book.objects.all()
paginator = Paginator(all_books,10)  # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量
"""paginator对象的属性"""
print("count",paginator.count)  # 显示数据总数量 count 83
print("num_pages",paginator.num_pages)  # 显示分页后总页数 num_pages 9
print("page_range",paginator.page_range)  # 显示页面的范围 page_range range(1, 10)

paginator对象的方法

  • page_obj = paginator.page(page):获取某一页所有数据的一个对象

    • 如果页码值不在页码范围内,抛出异常error:EmptyPage 超过页码范围

    • 如果页码值非法:抛出异常error:PageNotAnInteger 非法页码值

page_obj对象的方法

  • page_obj.has_next():判断当前页是否有下一页

  • page_obj.next_page_number():下一页的页码

  • page_obj.has_previous():判断当前页是否有上一页

  • page_obj.previous_page_number():上一页的页码

all_books = models.Book.objects.all()
paginator = Paginator(all_books,10)  # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量
"""paginator对象的属性"""
print("count",paginator.count)  # 显示数据总数量 count 83
print("num_pages",paginator.num_pages)  # 显示分页后总页数 num_pages 9
print("page_range",paginator.page_range)  # 显示页面的范围 page_range range(1, 10)

3.Django分页器实现分页

视图函数部分

from django.shortcuts import render, redirect, HttpResponse
from utils.modelFormLesson import BookForm
from app01 import models
from django.core.paginator import Paginator,EmptyPage
def show(request):
    if request.method == "GET":
        all_books = models.Book.objects.all()
        paginator = Paginator(all_books,10)  # 将所有的book对象传给Paginator生成分页器对象,10表示每一页展示的数据数量
"""paginator对象的属性"""
        print("count",paginator.count)  # 显示数据总数量 count 83
        print("num_pages",paginator.num_pages)  # 显示分页后总页数 num_pages 9
        print("page_range",paginator.page_range)  # 显示页面的范围 page_range range(1, 10)
# """paginator对象的方法"""
        # page1 = paginator.page(1)  # 获取第一页的所有数据
        # print(page1)
        # for i in page1:
        #     print(i)
        #
        # page_obj = paginator.page(2)
        # print("是否有下一页",page_obj.has_next())  # 是否有上一页 True
        # print("下一页的页码",page_obj.next_page_number())  # 下一页的页码 3
        # print("是否有上一页",page_obj.has_previous())  # 是否有下一页 True
        # print("上一页的页码",page_obj.previous_page_number())  # 上一页的页码 1
"""分页逻辑部分"""
        current_page_num = request.GET.get("page",1)  # 获取请求页面,也就是当前页
        current_page_num = int(current_page_num)
        all_pages = paginator.num_pages  # 分页后的所有页数
        try:
            current_books = paginator.page(current_page_num)  # 获取当前页的所有数据
        except EmptyPage:
            current_books = paginator.page(paginator.num_pages)
        print(">>>>>>>>>>",current_books)
​
        if current_page_num <= 3:
            page_range = range(1,6)
        elif 3 < current_page_num <= paginator.num_pages - 2:
            page_range = range(current_page_num-2,current_page_num+3)
        else:
            page_range = range(all_pages-4,all_pages+1)
        return render(request,"show.html",{"page_range":page_range,"current_books":current_books,"current_page_num":current_page_num})
视图函数show

模板文件show.html

在前端页面注意点:

在实现上一页和下一页的功能时,需要判断当前页是不是最后一页,也就是当前页有没有上一页或下一页。

如果是,那么在a标签中的href属性中不能再使用current_books.next_page_number或current_books.previous_page_number,不然会EmptyError错误。

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1 style="text-align: center">书籍展示页面</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-sm-8 col-sm-offset-2"><!--===============================数据展示部分==============================-->
            <div style="height: 450px">
                <table class="table table-hover table-striped">
                    <thead>
                    <tr>
                        <th>序号</th>
                        <th>书名</th>
                        <th>出版日期</th>
                        <th>价格</th>
                        <th>出版社</th>
                        <th>作者</th>
                        <th>菜单</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for book in current_books %}
                        <tr>
                            <td>{{ forloop.counter }}</td>
                            <td>{{ book.title }}</td>
                            <td>{{ book.publishDate|date:"Y-m-d" }}</td>
                            <td>{{ book.price }}</td>
                            <td>{{ book.publish }}</td>
                            <td>
                                {% for author in book.authors.all %}
                                    {{ author.name }}
                                    {% if forloop.last %}
​
                                    {% endif %}
                                {% endfor %}
                            </td>
                            <td>
                                <a href="" class="btn btn-warning btn-xs">编辑</a>
                                <button class="btn btn-danger btn-xs">删除</button>
                                <span class="hidden">{{ book.pk }}</span>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
            <!--===============================分页部分==================================-->
            <nav aria-label="Page navigation" class="pull-right">
                <ul class="pagination">
                <!--上一页设置-->
                    <!--判断是否有上一页,有的话可以点击左箭头跳上一页-->
                    {% if current_books.has_previous %}
                        <li>
                            <a href="{% url 'show' %}?page={{ current_books.previous_page_number }}"
                               aria-label="Previous">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>
                        <!--没有上一页,让按钮变为disable,不让点击-->
                    {% else %}
                        <li class="disabled">
                            <a aria-label="Previous">  <!--注意第一页没有上一页,所以这里a标签的href属性不能再写了,如果写了,那么current_books.previous_page_number方法会一直报错。-->
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>
                    {% endif %}
​
                <!--页面设置-->
                    {% for page in page_range %}
                        {% if page == current_page_num %}
                            <li class="active">
                                <a href="{% url 'show' %}?page={{ page }}" aria-label="Previous">
                                    <span aria-hidden="true">{{ page }}</span>
                                </a>
                            </li>
                        {% else %}
                            <li>
                                <a href="{% url 'show' %}?page={{ page }}" aria-label="Previous">
                                    <span aria-hidden="true">{{ page }}</span>
                                </a>
                            </li>
                        {% endif %}
                    {% endfor %}
​
                <!--下一页设置-->
                    <!--判断是否有下一页,有的话可以点击右箭头跳下一页-->
                    {% if current_books.has_next %}
                        <li>
                            <a href="{% url 'show' %}?page={{ current_books.next_page_number }}"
                               aria-label="Next">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                        <!--没有上一页,让按钮变为disable,不让点击-->
                    {% else %}
                        <li class="disabled">
                            <a aria-label="Next">  <!--注意最后一页没有下一页,所以这里a标签的href属性不能再写了,如果写了,那么current_books.next_page_number方法会一直报错。-->
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                    {% endif %}
​
​
                </ul>
            </nav>
        </div>
    </div>
</div><script src="{% static 'jquery-3.4.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
show.html

分页后的前端页面

二、自定义分页器CustomerPaginator

1.为什么使用自定义分页器

django的内置分页器可以实现简单的分页设置,但是如果我们需要涉及到更多细节的设置,更人性化的配置的时候,django内置分页器就无法满足我们的使用需求。

这种场景,我们需要自己定义分页器组件,通过后端传递数据,在分页器组件中,根据分页的业务做逻辑处理,然后拼接成分页器标签返回。拿到分页器标签后,直接render给前端页面,生成在页面中。

2.自定义分页器实例

这里的自定义分页器,仅仅博主本人写的一个简单的自定义分页器,能够解决常用的分页需求。如果无法满足具体需求,可以根据具体需求进行扩展。

自定义分页器组件

项目下新建utils文件夹,用来存放扩展的功能组件,在utils下新建customPaginator.py,分页器组件代码如下:

import copy
class Paginator:
    """自定义分页器"""
    def __init__(self, request, data_counts, per_counts=10, show_counts=5):
        """
        初始属性
        :param request: 请求的所有请求数据
        :param data_counts: 展示的总数据数量
        :param per_counts: 每页展示的数据数量
        :param show_counts: 在前端显示的页码数量
        """
        self.request = request
        self.data_counts = data_counts
        self.per_counts = per_counts
        self.show_counts = show_counts
        self.params = copy.deepcopy(request.GET)  # 获取请求的GET数据的深拷贝querydict可以使用
# 获取数据分页后总页数
        def page_counts():
            """
            获取总的页码数量
            :return:
            """
            page_counts, mod = divmod(self.data_counts, self.per_counts)
            if mod:
                page_counts += 1
            return page_counts
​
        # 获取总页数,并添加在self属性中
        self.page_counts = page_counts()
​
        # 获取当前页页码
        self.current_page = request.GET.get("page", "")
​
        try:  # 异常处理当前页码是否为数字
            self.current_page = int(self.current_page)
        except Exception:
            self.current_page = 1# 判断当前页码在不在合理范围内
        if self.current_page < 1:
            self.current_page = 1
        if self.current_page > self.page_counts:
            self.current_page = 1
​
    @property  # 装饰为普通属性
    def start(self):
        """
        返回某一页数据展示的开始位置
        :return:
        """
        return (self.current_page - 1) * self.per_counts
​
    @property  # 装饰为普通属性
    def end(self):
        """
        返回某一页数据展示的结束位置
        :return:
        """
        return (self.current_page) * self.per_counts
​
    def paginate(self):
        """
        根据数据数据量,每页数据,每页页码,拼接分页标签返回
        :return:返回拼接好的分页器标签
        """
        tag = ""
        page_counts = self.page_counts
​
        # 设置首页标签url
        self.params["page"] = 1
        params = self.params.urlencode()  # condition=name&q=qwerqw&page=1
        if self.current_page == 1:
            first = f"""
                <li class="active">
                    <a href="{self.request.path}?{params}" aria-label="Previous">
                        <span aria-hidden="true">首页</span>
                    </a>
                </li>
                """
        else:
            first = f"""
                <li>
                    <a href="{self.request.path}?{params}" aria-label="Previous">
                        <span aria-hidden="true">首页</span>
                    </a>
                </li>
                """# 设置上一页标签的url
        if self.current_page <= 1:
            # 如果当前页是第一页,上一页不可选
            previous = """
                <li disabled>
                    <a href="#" aria-label="Previous">
                        <span aria-hidden="true">&laquo;</span>
                    </a>
                </li>
                """
        else:
            # 不是第一页,拼接上一页的url路径
            self.params["page"] = self.current_page - 1
            params = self.params.urlencode()  # condition=name&q=qwerqw&page=num
            previous = f"""
                <li>
                    <a href="{self.request.path}?{params}" aria-label="Previous">
                        <span aria-hidden="true">&laquo;</span>
                    </a>
                </li>
                """# 获取要显示的页码范围
        half_num = self.show_counts // 2
        if self.page_counts < self.show_counts:
            # 判断总页数是不是小于要显示的页数
            range_start = 1
            range_end = self.page_counts
        else:
            # 总页数大于要显示的页数
            if self.current_page <= half_num + 1:
                range_start = 1
                range_end = self.show_counts
            elif half_num + 1 < self.current_page < page_counts - half_num:
                range_start = self.current_page - half_num
                range_end = self.current_page + half_num
            else:
                range_start = page_counts - self.show_counts + 1
                range_end = page_counts
​
        # 获取中间的页码标签
        middle = ""
        for num in range(range_start, range_end + 1):
            # 保留查询参数的路径
            self.params["page"] = num
            params = self.params.urlencode()  # condition=name&q=qwerqw&page=num
# 判断哪一页是当前页,是的话,添加active效果
if self.current_page == num:
                one_tag = f"""<li class="active"><a href="{self.request.path}?{params}">{num}</a></li>"""
            else:
                one_tag = f"""<li><a href="{self.request.path}?{params}">{num}</a></li>"""
            middle += one_tag
​
        # 设置下一页标签的url
        if self.current_page >= self.page_counts:
            # 如果当前页已经是最大页数,下一页禁用
            next = """
                <li disabled>
                    <a href="#" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                    </a>
                </li>
                """
        else:
            # 如果不是正常使用
            self.params["page"] = self.current_page + 1
            params = self.params.urlencode()  # condition=name&q=qwerqw&page=num
            next = f"""
                <li>
                    <a href="{self.request.path}?{params}" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                    </a>
                </li>
                """# 设置尾页标签url
        self.params["page"] = self.page_counts
        params = self.params.urlencode()  # condition=name&q=qwerqw&page=1
        if self.current_page == self.page_counts:
            last = f"""
                <li class="active">
                    <a href="{self.request.path}?{params}" aria-label="Previous">
                        <span aria-hidden="true">尾页</span>
                    </a>
                </li>
                """
        else:
            last = f"""
                <li>
                    <a href="{self.request.path}?{params}" aria-label="Previous">
                        <span aria-hidden="true">尾页</span>
                    </a>
                </li>
                """
​
        tag = f"""
            <nav aria-label="Page navigation">
                
                <ul class="pagination">
                    {first}
                    {previous}
                    {middle}
                    {next}
                    {last}
                    <li style="margin-right:10px"><span>共{self.page_counts}页</span></li>&nbsp
                </ul>
            </nav>
            """return tag
​
    def jump_page(self):
        """
        产生跳转页标签
        :return: 返回跳转页的标签
        """
        url = self.request.get_full_path()
        if not "/?" in url:
            url = url+"?page="
        else:
            if "page" in url:
                url = url.split("page")[0]+"page="
            else:
                url = url + "&page="
​
​
        jump_tag =f"""
        <div class="input-group input-group-sm">
            <span class="input-group-btn">跳转到</span>
            <input type="text" class="form-control" name="page" placeholder="跳转页码">
            <span class="input-group-btn">页</span>
            <span class="input-group-btn">
                <a id="jump_page" href="{url}" class="btn btn-info btn-flat">Go!</a>
            </span>
        </div>
        """
        return jump_tag
​
    def jump_js(self):
        """
        生成跳转页使用的js代码
        :return:
        """
        jump_js = """
            <script>
                $("#jump_page").click(function () {
                    var page = $("[name=page]").val();
                    var url = $(this).attr("href") + page;
                    $(this).attr("href", url);
                });
            </script>
            """
        return jump_js
自定义分页器组件代码

自定分页组件使用

1.分页器使用

分页器接受参数

  • request: 请求的所有请求数据

  • data_counts: 展示的总数据数量

  • per_counts: 每页展示的数据数量,默认是每页10条

  • show_counts: 在前端显示的页码数量,默认是5页

分页器方法

  • start():方法根据提供的当前所在页码,返回当前页数据的开始位置

  • end():方法根据提供的当前所在页码,返回当前页数据的结束位置

  • paginate():返回拼接好的分页标签的html文本,在前端使用

  • jump_page():返回拼接好的跳转页标签html文本,在前端使用

  • jump_js():返回跳转页使用对应的js代码,在前端使用

2.具体使用实例

后端views视图

from crmweb import models
from django import views
from utils.customPaginator import Paginator  # 导入自定义分页器类
​
​
# 展示客户数据的视图
class CommonData(views.View):
    @method_decorator(login_required)  # 验证客户登录状态的装饰器,这里可有可无
    def dispatch(self, request, *args, **kwargs):
        res = super().dispatch(request, *args, **kwargs)
        return res
​
    def get(self, request):
        # 查询要显示的所有数据
        all_customers = models.Customer.objects.filter(consultant__isnull=True, status="unregistered").order_by("-pk")
​
        # 开始分页展示
        data_counts = all_customers.count()  # 获取分页的总数据数量
# 生成一个分页对象
        paginator = Paginator(request, data_counts, 10)
​
        # 获取当前页展示数据的范围
        try:  # 异常是否查到了数据,查到了才切片,不然会报错
            all_customers = all_customers[paginator.start:paginator.end]
        except Exception:
            pass# 获取分页的标签
        paginator_tag = paginator.paginate()  # 调用定义好的分页方法
# 获取跳转页的标签
        jump_tag = paginator.jump_page()  # 调用定义好的跳转页方法获取跳转页标签
        jump_js = paginator.jump_js()  # 调用定义好的跳转页方法获取跳转页js代码
return render(request, "customers_common_list.html",
                      {"all_customers": all_customers, "paginator_tag": paginator_tag,
                       "jump_tag": jump_tag, "jump_js": jump_js})

前端使用分页器

<table id="example2" class="table table-bordered table-hover text-center">
    <thead>
    <tr>
        <th style="width: 5%">序号</th>
        <th>qq</th>
        <th>姓名</th>
        <th>电话</th>
        <th>来源</th>
        <th>咨询课程</th>
        <th>客户状态</th>
        <th>销售老师</th>
        <th>跟进信息</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody><tr>
        <td>{{ forloop.counter }}</td>
        <td>{{ customer.qq }}</td>
        <td>{{ customer.qq_name }}</td>
        <td>
            {{ customer.phone|default:"暂无" }}
        </td>
        <td>{{ customer.get_source_display|default:'暂无' }}</td>
        <td>{{ customer.get_course_display|default:"暂无" }}</td>
        <td>{{ customer.get_status_display }}</td>
        <td>{{ customer.consultant.username|default:'暂无' }}</td>
        <td><a href="{% url 'consult_record' customer.pk %}">详情</a></td>
        <td><a style="color: #00c3cc;" href="{% url 'consult_record_edit' customer.pk %}">
                <i class="fa fa-edit" aria-hidden="true"></i>
            </a>
            |
            <a style="color: #d9534f;" href="{% url 'consult_record_del' customer.pk %}">
                <i class="fa fa-trash-o"></i>
            </a>
        </td>
    </tr>
    </tbody>
</table><!--自定义分页器使用-->
<div class="pull-right" style="display:inline-block; width: 120px;margin: 22px 10px">
    {{ jump_tag|safe }}
</div>
​
​
<!--自定义跳转页使用-->
<div class="pull-right">
    {{ paginator_tag|safe }}
</div>
{{ jump_js|safe }}
前端customer.html

 效果图如下

 

posted @ 2019-06-07 18:34  ryxiong728  阅读(323)  评论(0编辑  收藏  举报