Django分页的实现

2019/7/27 posted in  python

这篇博客非常好的详述了django分页的自定义修改方法

发表于 2018-01-23 | 分类于 Django |

可以通过 Django 提供的 Paginator 类来实现分页功能。也可以自定义实现分页功能。

自定义实现分页

一、原理解析

1、初始数据:
total_data: 数据集合,这是一个list,存放所有的数据。
page_size: 每页显示多少条数据。
num_page: 分页处显示页码数量。

2、分页需要使用的数据
current_page: 当前页,当前的页码。
通过 request.GET 获得:

current_page = int(request.GET.get('p'))

total_num: 数据条目总数

total_num = len(total_data)

max_page: 最大页码数
计算方法: 数据总条数除以每页显示条数,若余数等于0,最大页码数为商,否则最大页码数为商+1

getmax_page = lambda x,y: divmod(x,y)[0] if divmod(x,y)[1] == 0 else divmod(x,y)[0]+1
max_page = getmax_page(total_num,page_size)

peer_data: 每页显示数据
计算方法: 当我们定义page_size=10的时候,每页显示10条数据
第一页:0:10
第二页:10:20
以此类推…….
第n页:(n-1)x10:(nx10)
就可以推出每页显示数据的计算方法:

start = (current_page-1)*page_size
end = current_page*page_size
peer_data = total_data[start:end]

prev_page: 上一页页码
当前页小于等于1的时候,上一页为1

if current_page <= 1:
    prev_page =1
else:
    prev_page = current_page - 1

next_page: 下一页页码
当前页大于等于最大页时,下一页为最大页

if current_page >= max_page:
    next_page = max_page
else:
    next_page = current_page + 1

num_page_range: 分页区域显示页码范围
此处如果全部显示页码的话不合适,页码数太多页码就超级难看了,因此可以显示一个固定数量的页码值。
前面定义的 num_page (分页区域显示的页码数量)就在这里用到了。
如果num_page=7的话就这样显示:
1 2 3 4 5 6 7
前半区 后半区
实现逻辑如下:
1、总页数 max_page 小于最多显示页数 num_page ,显示1到总页数 max_page,此时显示的页码数量小于最多显示页数 num_page
2、当前页 current_page 小于等于最多显示页数的一半 num_page/2,显示1到最多显示页数 num_page,当前页在前半区。
3、当前页 current_page 加上最多显示页数的一半 num_page/2 大于总页数 max_page,显示(总页数 max_page 减去最大显示页数 num_page)到总页数 max_page,当前页在后半区。
4、前后各显示最多显示页数一半,当前页在中间位置。

part = num_page/2
if max_page < num_page:
    num_page_range = [i for i in range(1,max_page + 1)]
elif current_page <= part:
    num_page_range = [i for i in range(1,num_page + 1)]
elif current_page + part > max_page:
    num_page_range = [i for i in range(max_page - max_page_num,max_page + 1)]
else:
    num_page_range = [i for i in range(current_page-part,current_page + part + 1)]

3、视图函数传给模板的变量汇总
current_page: 当前页码
peer_data: 当前页数据
prev_page: 上一页页码
next_page: 下一页页码
max_page: 最大页码数
num_page_range: 分页区域显示页码范围

4、前端模板逻辑分析
前端页面调用 Bootstrap 分页组件来显示:

<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="#">1</a></li>
    <li><a href="#">2</a></li>
    <li><a href="#">3</a></li>
    <li><a href="#">4</a></li>
    <li><a href="#">5</a></li>
    <li>
      <a href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
  </ul>
</nav>

img
上一页按钮
当前为第一页时,禁用左边的上一页按钮

<ul class="pagination">
{% if current_page == 1 %}
    <li class="disabled">
      <a href="#" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
{% else %}
    <li>
      <a href="?p={{ prev_page }}" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
{% endif %}

下一页按钮
当前为最后一页时,禁用右边的下一页按钮

{% if current_page == max_page %}
    <li class="disabled">
      <a href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
    {% else %}
    <li>
      <a href="?p={{ next_page }}" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
{% endif %}

显示页码范围
根据视图传过来的分页区域显示页码范围 num_page_range 来循环显示,并对当前页进行样式处理:

{% for p in num_page_range %}
    {% if p == current_page %}
        <li class="active"><a href="#">{{ p }}</a></li>
    {% else %}
        <li><a href="?p={{ p }}">{{ p }}</a></li>
    {% endif %}
{% endfor %}

二、将分页实现为Web框架公共组件

分页的使用其实非常广泛,前面分析了分页实现的原理,这里我们将代码实现为公共组件,以后使用时就可以直接调用。

1、在app下建立py文件并自定义构建类

pager.py

#coding:utf-8

class Pagination(object):
    def __init__(self, totalCount, currentPage, perPageItemNum=10, maxPageNum=7):
        # 数据条目总数
        self.total_count = totalCount
        # 当前页页码值
        self.current_page = currentPage
        # 每页显示数据条目数
        self.per_page_item_num = perPageItemNum
        # 页码区域最多显示页码数
        self.max_page_num = maxPageNum

    def start(self):
        return (self.current_page - 1)*self.per_page_item_num

    def end(self):
        return self.current_page*self.per_page_item_num

    @property
    def num_pages(self):
        '''
        装饰器@property将函数的方法的调用方式转换为属性调用方式
        求出总页数
        '''
        a,b = divmod(self.total_count, self.per_page_item_num)
        if a == 0:
            return a
        else:
            return a+1

    def pager_num_page(self):
        '''
        分页区域显示页码范围
        '''
        part = self.max_page_num/2
        if self.num_pages < self.max_page_num:
            return range(1,self.num_pages+1)
        elif self.current_page <= part:
            return range(1,self.max_page_num+1)
        elif self.current_page + part > self.num_pages:
            return range(self.num_pages-self.max_page_num,self.num_pages+1)
        else:
            return range(self.current_page-part,self.current_page+part+1)

    def page_str(self):
        '''
        html返回到templates,templates中需引入bootStrap的css样式
        '''
        page_list = []
        first = """
        <li><a href='?p=1'>首页</a></li>
        """
        page_list.append(first)

        if self.current_page == 1:
            prev_page = """
            <li class="disabled">
                <span>
                    <span aria-hidden="true">&laquo;</span>
                </span>
            </li>
            """
        else:
            prev_page = """
            <li>
                <a href="?p=%s" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            """ %(self.current_page - 1)
        page_list.append(prev_page)

        for i in self.pager_num_page():
            if i == self.current_page:
                temp = """
                <li class="active">

                    <li class="active"><a href="#">%s</a></li>
                </li>
                """ %i
            else:
                temp = """
                <li>
                    <a href="?p=%s">%s</a>
                </li>
                """ %(i,i)
            page_list.append(temp)

        if self.current_page == self.num_pages:
            next_page = """
            <li class="disabled">
                <span>
                    <span aria-hidden="true">&raquo;</span>
                </span>
            </li>
            """
        else:
            next_page = """
            <li>
                <a href="?p=%s" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
            """ %(self.current_page + 1)
        page_list.append(next_page)

        last = """
        <li><a href='?p=%s'>尾页</a></li>
        """ %self.num_pages
        page_list.append(last)

        return ''.join(page_list)

2、调用 Pagination 实现分页

视图方法逻辑: views.py

#coding:utf-8
from django.shortcuts import render
from app01.pager import Pagination

# 定义后台数据
mylist=[]
for i in range(1,1000):
    temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
    mylist.append(temp)


def mypager(request):
    try:
        current_page = int(request.GET.get('p'))
    except:
        current_page = 1

    page_obj = Pagination(len(mylist),current_page,15,5)
    data = mylist[page_obj.start():page_obj.end()]
    return render(request,'mypager.html',locals())

模板使用: mypager.html

<!DOCTYPE html>
<html lang="en">
{% load staticfiles %}
<head>
    <meta charset="UTF-8">
    <title>mypager</title>
    <link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
</head>
<body>
<div class="container">
    <div class="panel">
        <table class="table table-striped">
            <tr>
                <th>ID</th>
                <th>name</th>
                <th>age</th>
                <th>addr</th>
                <th>score</th>
            </tr>
            {% for item in data %}
                <tr>
                    <td>{{ item.id }}</td>
                    <td>{{ item.name }}</td>
                    <td>{{ item.age }}</td>
                    <td>{{ item.addr }}</td>
                    <td>{{ item.score }}</td>
                </tr>
            {% endfor %}
        </table>
    </div>
    <nav aria-label="Page navigation">
        <ul class="pagination">
            {{ page_obj.page_str | safe }}
        </ul>
    </nav>
</div>
</body>
</html>

完后会效果如下:
img

使用Django内置分页

Django提供了两个新的类(Paginator和Page)来帮助你管理分页数据,这两个类存放在 django/core/paginator.py .它可以接收列表、元组或其它可迭代的对象。

1、语法解析

Paginator类
基于分页对象
count 数据总个数
num_pages 总共可分页数
page_range 总页数索引范围

Page类
基于分页对象中的某一页
object_list 分页对象的元素列表
number 分页对象的当前页值
has_next 是否有下一页
next_page_number 下一页代码
has_previous 是否有上一页
previous_page_number 上一页代码
has_other_pages 是否有其他页
start_index 分页对象元素的开始索引
end_index 分页对象元素的结束索引

2、基本语法实例

import os
 
from django.core.paginator import Paginator
objects = ['john','paul','george','ringo','lucy','meiry','checy','wind','flow','rain']<br>
p = Paginator(objects,3)  # 3条数据为一页,实例化分页对象
print p.count  # 10 对象总共10个元素
print p.num_pages  # 4 对象可分4页
print p.page_range  # xrange(1, 5) 对象页的可迭代范围
 
page1 = p.page(1)  # 取对象的第一分页对象
print page1.object_list  # 第一分页对象的元素列表['john', 'paul', 'george']
print page1.number  # 第一分页对象的当前页值 1
 
page2 = p.page(2)  # 取对象的第二分页对象
print page2.object_list  # 第二分页对象的元素列表 ['ringo', 'lucy', 'meiry']
print page2.number  # 第二分页对象的当前页码值 2
 
print page1.has_previous()  # 第一分页对象是否有前一页 False
print page1.has_other_pages()  # 第一分页对象是否有其它页 True
 
print page2.has_previous()  # 第二分页对象是否有前一页 True
print page2.has_next()  # 第二分页对象是否有下一页 True
print page2.next_page_number()  # 第二分页对象下一页码的值 3
print page2.previous_page_number()  # 第二分页对象的上一页码值 1
print page2.start_index()  # 第二分页对象的元素开始索引 4
print page2.end_index()  # 第2分页对象的元素结束索引 6

3、内置分页使用实例

视图方法逻辑: views.py

#coding:utf-8
from django.shortcuts import render
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
# Create your views here.
mylist=[]
for i in range(1,1000):
    temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
    mylist.append(temp)

def myin(request):
    try:
        current_page = int(request.GET.get('p'))
    except:
        current_page = 1

    # 实例化分页对象,每页10条数据
    paginator = Paginator(mylist,10)
    try:
        # 取对象的当前页分页对象
        posts = paginator.page(current_page)
    # current_page非数字时取第一页
    except PageNotAnInteger:
        posts = paginator.page(1)
    # current_page空时取最后一页
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request,'inpage.html',{'posts':posts})

模板使用: inpage.html

<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>内置</title>
    <link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
</head>
<body>
<div class="container">
<div class="panel">
    <table class="table table-striped">
        <tr>
            <th>ID</th>
            <th>name</th>
            <th>age</th>
            <th>addr</th>
            <th>score</th>
        </tr>

        {% for item in posts.object_list %}
            <tr>
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.age }}</td>
                <td>{{ item.addr }}</td>
                <td>{{ item.score }}</td>
            </tr>
        {% endfor %}
    </table>
</div>
<nav aria-label="Page navigation">
  <ul class="pagination">
    <!--当前页分页对象如果有上一页-->
    {% if posts.has_previous %}
        <li>
            <a href="?p={{ posts.previous_page_number }}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>

    {% else %}
        <li class="disabled">
          <a href="#" aria-label="Previous">
            <span aria-hidden="true">&laquo;</span>
          </a>
        </li>
    {% endif %}
    <!--循环显示可迭代页码范围-->
    {% for p in posts.paginator.page_range %}
        <!--如果是当前页,高亮显示-->
        {% if p == posts.number %}
            <li class="active"><a href="#">{{ p }}</a></li>
        {% else %}
            <li><a href="?p={{ p }}">{{ p }}</a></li>
        {% endif %}
    {% endfor %}
  <!--当前页分页对象如果有下一页-->
      {% if posts.has_next %}
          <li>
              <a href="?p={{ posts.next_page_number }}" aria-label="Next">
                  <span aria-hidden="true">&raquo;</span>
              </a>
          </li>
      {% else %}
          <li class="disabled">
              <a href="#" aria-label="Next">
                  <span aria-hidden="true">&raquo;</span>
              </a>
          </li>
      {% endif %}
  </ul>
</nav>
</div>
</body>
</html>

使用Django内置Paginator可以实现分页,但分页区域显示的页码范围 page_range 默认是全部,如果页数很多,页码效果将会很差,下面介绍修改扩展Django内置分页,实现自定义分页区域显示的页码范围功能。

4、扩展Django内置分页

在视图函数中自定义一个新类,继承 django.core.paginator.Paginator 来进行扩展。

from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage

class CustomPaginator(Paginator):
    def __init__(self ,current_page, per_pager_num, *args, **kwargs):
        # 当前页
        self.current_page = int(current_page)
        # 页码最大显示范围
        self.per_pager_num = per_pager_num
        # 继承父类Paginator的其他属性方法
        Paginator.__init__(self, *args, **kwargs)

    def pager_num_range(self):
        if self.num_pages < self.per_pager_num:
            return range(1, self.num_pages + 1)
        part = int(self.per_pager_num / 2)
        if self.current_page <= part:
            return range(1, self.per_pager_num + 1)
        if (self.current_page + part) > self.num_pages:
            return range(self.num_pages - self.per_pager_num + 1, self.num_pages + 1)
        else:
            return range(self.current_page - part, self.current_page + part + 1)

## 调用
mylist=[]
for i in range(1,1000):
    temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
    mylist.append(temp)

def myin(request):
    try:
        current_page = int(request.GET.get('p'))
    except:
        current_page = 1

    # 实例化分页对象,每页10条数据
    paginator = CustomPaginator(current_page, 5, mylist, 10)
    try:
        # 取对象的当前页分页对象
        posts = paginator.page(current_page)
    # current_page非数字时取第一页
    except PageNotAnInteger:
        posts = paginator.page(1)
    # current_page空时取最后一页
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request,'inpage.html',{'posts':posts})

完美解决问题。