【Django 025】分页Pagination和导航条详解

如果有多条数据要传递给客户端的时候,例如查询某用户的所有博客,一次性全部发送过去不仅传递耗时,在客户端加载出来也并不美观。这个时候就需要用到分页的帮助了,将全部数据分解为多块,根据用户的请求只发送指定页码的小块数据。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

环境准备

先总结下我的操作环境:

  • Centos 7
  • Python 3.7
  • Pycharm 2019.3
  • Django 2.2

因为Django长期支持版本2.2LTS和前一个长期支持版本1.11LTS有许多地方不一样,需要小心区分。

分页逻辑

创建应用Page,注册在配置中,创建并在项目中注册urls.py。然后创建如下model

class People(models.Model):
    p_name = models.CharField(max_length=16)
    p_age = models.IntegerField(default=18)

迁移到数据库中待用。

新建如下路由和view函数用来往数据库中添加99条记录

path('addpeople/', views.add_people, name='add_people'),
def add_people(request):
    for i in range(1,100):
        people = People()
        people.p_name = 'xiaofu{}'.format(str(i))
        people.p_age = i
        people.save()
    return HttpResponse('Add 100 people done')

访问http://127.0.0.1:8000/page/addpeople/即可成功添加数据。

有了数据,下面开始今天的真题。

未分页时

下面模拟用户请求所有的数据,创建如下路由和view函数

path('getpeople/', views.get_people, name='get_people'),
def get_people(request):
    people_info = People.objects.all()
    context = {
        'people_info': people_info
    }
    return render(request, 'people_info.html', context=context)

其中h5文件people_info.html如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>People Info</title>
</head>
<body>
<ul>
    {% for people in people_info %}
        <li>{{ people.p_name }}: {{ people.p_age }}</li>
    {% endfor %}
</ul>

</body>
</html>

之后访问的效果如下
在这里插入图片描述

可以说非常不美观。

数据切片

理想的效果,是每次显示少量信息,然后分多页显示。

前面在学MTV中的models时候,我们了解到查询出来的数据是一个QuerySet,其可以做类似于list的切片操作,如下

In [1]: a = [1,2,3,4,5]                                                                                                                                                      

In [2]: b = a[0:2]                                                                                                                                                           

In [3]: b                                                                                                                                                                    
Out[3]: [1, 2]

这样我们将99条数据切为10页,切片时从整体的[0,10]一直到[90,100]即可。具体显示第几页由用户的url中的查询参数传递进来。为了更灵活,也可以让用户再传递一个每页显示的记录数量代替这个默认的10。

分页效果

创建如下路由和view函数

path('get_people_with_page/', views.get_people_with_page, name='get_people_with_page'),
def get_people_with_page(request):
    page_num = int(request.GET.get('page_num', 1))
    page_record = int(request.GET.get('page_record', 10))
    people_info = People.objects.all()[page_record * (page_num - 1):page_record * page_num]
    context = {
        'people_info': people_info
    }
    return render(request, 'people_info.html', context=context)

这里还是用的跟上面同样的h5文件,不过传递进去的是已经切片的数据了。默认每页显示10条数据,显示第一页,如下
2-default-page.png
可以传递单个参数,另一个参数用默认值,如下显示第4页
3-one-parameter.png
也可以传递两个参数,同时指定每页显示数目和显示的页码,如下
4-two-parameters.png

这就是分页显示最基本的逻辑。

但是很明显光有最基本的功能是不够的,例如不能通过点击来切换页面,也不知道一共有多少页。为了使得分页变得更简便功能更丰富,Django也直接集成了自己的一个分页模块,就是下面要说的Paginator类。

Paginator类

Paginator类的作用就是将一个QuerySet变为多个Page实例,可以参考其官方文档

初始化及常用方法和属性

初始化的时候Paginator一共接受两个参数,第一个参数可以传递一个list,tuple或者QuerySet等任何可以切片的对象。第二个参数是每一页显示的条目个数。

这里以list为例进行演示,通过python manage.py shell进入交互环境。

>>> from django.core.paginator import Paginator
>>> P = Paginator(['xiaofu1','xiaofu2','xiaofu3','xiaofu4'],2)

显示所有条目

>>> P.object_list
['xiaofu1', 'xiaofu2', 'xiaofu3', 'xiaofu4']

显示总条目个数,总页数和每页条目数

>>> P.count
4
>>> P.num_pages
2
>>> P.per_page
2

其中每一页是个Page对象,下面再单独写Page对象

>>> P.page_range
range(1, 3)
>>> P.page(1)
<Page 1 of 2>

Page对象

获取其中一个Page对象,注意这里是从1开始的

>>> page1=P.page(1)

查看本页面的内容和页码

>>> page1.number
1
>>> page1.object_list
['xiaofu1', 'xiaofu2']

本页面内容在所有内容中的index

>>> page1.start_index()
1
>>> page1.end_index()
2

查看本页面在所有页面中的位置关系,因为已经是最前面一页,所以再查看前面一页会报错

>>> page1.has_previous()
False
>>> page1.has_next()
True
>>> page1.has_other_pages()
True
>>> page1.previous_page_number()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/fuhx/python_projects/django/lib/python3.7/site-packages/django/core/paginator.py", line 174, in previous_page_number
    return self.paginator.validate_number(self.number - 1)
  File "/home/fuhx/python_projects/django/lib/python3.7/site-packages/django/core/paginator.py", line 47, in validate_number
    raise EmptyPage(_('That page number is less than 1'))
django.core.paginator.EmptyPage: That page number is less than 1
>>> page1.next_page_number()
2

还有一些就不一一展示了,下面通过Paginator类结合Bootstrap来对刚才的页面展示进行一下优化

结合Bootstrap效果

少废话,先来看看效果
6-bootstrap-finished.gif

写过前端的朋友应该都对Bootstrap很熟悉,其自带很多组件及对应的css,通过js展示出一些比较炫酷美观的效果。例如上面的导航条的css就是自带的,我们要完成三个目的:

  • 点击导航条数字调到对应页码
  • 点击previous和next进行前后页码跳转
  • 当不能前后跳转是previous或者next要不能被点击

下面正式开始。

新建一个模板文件,在head里面加上Bootstrap所需的css和js。

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
      integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
        integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
        crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
        integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
        crossorigin="anonymous"></script>

然后直接去Bootstrap的Pagination组件页面复制框架代码进来

<nav aria-label="Page navigation example">
  <ul class="pagination">
    <li class="page-item"><a class="page-link" href="#">Previous</a></li>
    <li class="page-item"><a class="page-link" href="#">1</a></li>
    <li class="page-item"><a class="page-link" href="#">2</a></li>
    <li class="page-item"><a class="page-link" href="#">3</a></li>
    <li class="page-item"><a class="page-link" href="#">Next</a></li>
  </ul>
</nav>

之后如果请求该页面会出现一个如下所示的分页主键,css效果出来了,但是没有实现功能
5-bootstrap.png

先修改下view函数,将people_info变为包含所有结果的QuerySet对象,并做为参数给Pagination构造函数。传递给h5页面两个对象,分别是Paginator实例和Page对象

def get_people_with_page(request):
    page_num = int(request.GET.get('page_num', 1))
    page_record = int(request.GET.get('page_record', 10))
    # people_info = People.objects.all()[page_record * (page_num - 1):page_record * page_num]
    people_info = People.objects.all()
    P = Paginator(people_info,page_record)
    page = P.page(page_num)
    context = {
        'paginator':P,
        'page': page
    }
    return render(request, 'people_info_with_page.html', context=context)

之后重点看下h5页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>People Info</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
            integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
            crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
            integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
            integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
            crossorigin="anonymous"></script>
</head>
<body>
<ul>
    {% for item in page.object_list %}
        <li>{{ item.p_name }}</li>
    {% endfor %}
</ul>
<nav aria-label="Page navigation example">
    <ul class="pagination">
        {% if page.has_previous == False %}
            <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
        {% else %}
            <li class="page-item"><a class="page-link"
                                     href="{% url 'page:get_people_with_page' %}?page_num={{ page.previous_page_number }}&page_record={{ paginator.per_page }}">Previous</a>
            </li>

        {% endif %}
        {% for num in paginator.page_range %}
            <li class="page-item"><a class="page-link"
                                     href="{% url 'page:get_people_with_page' %}?page_num={{ num }}&page_record={{ paginator.per_page }}">{{ num }}</a>
            </li>
        {% endfor %}
        {% if page.has_next == False %}
            <li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
        {% else %}
            <li class="page-item"><a class="page-link"
                                     href="{% url 'page:get_people_with_page' %}?page_num={{ page.next_page_number }}&page_record={{ paginator.per_page }}">Next</a>
            </li>
        {% endif %}
    </ul>
</nav>
</body>
</html>

这里的body分两大块来看:

  • 显示条目
  • 导航

其中导航又分为三块:

  • previous
  • 数字
  • next

先看显示条目部分

<ul>
    {% for item in page.object_list %}
        <li>{{ item.p_name }}</li>
    {% endfor %}
</ul>

应该比较好理解,page.object_list是当前页面所有条目的一个list,循环获取单个元素的内容。

然后是导航的数字部分

{% for num in paginator.page_range %}
    <li class="page-item"><a class="page-link"
                             href="{% url 'page:get_people_with_page' %}?page_num={{ num }}&page_record={{ paginator.per_page }}">{{ num }}</a>
    </li>
{% endfor %}

这里直接修改Bootstrap框架代码的href部分,做一个反向解析,后面加上两个查询参数。

重点是导航的previous部分

{% if page.has_previous == False %}
    <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
{% else %}
    <li class="page-item"><a class="page-link"
                             href="{% url 'page:get_people_with_page' %}?page_num={{ page.previous_page_number }}&page_record={{ paginator.per_page }}">Previous</a>
    </li>

{% endif %}

首先判断本页码是否还有前页码,如果没有,在Bootstrap自带的代码基础上加一个disabled类名。如果有前页码,修改href为前一个页码的url,注意查询参数中是previous_page.number

导航的next部分 也同理,就不再重复了。

总结

这篇文章我们一起学习了分页的基本逻辑和导航条的实现。数据展示的逻辑还是不难的,但是导航条的展示却可以千变万化,有带省略号的,有等分显示页码的,有前密后疏的排列的。Bootstrap只是一种最基本的实现,更多有意思的导航条等着我们去慢慢观察和发现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值