Django-从原理开始一步步制作自己的分页器

Django-分页

分页

实质:分批获取数据

获取数据语句:models.UserInfo.objects.all()

分批获取直接在后面切片:models.UserInfo.objects.all()[0:10]

Django自带的分页

首先创建出300条UserInfo表的数据:

for i in range(300):
    name = 'lxyker %d' % i
    models.UserInfo.objects.create(name=name)

在index页面显示所有用户,views.py如下:

def index(request):
    """
    分页
    :param request:
    :return:
    """
    user_list = models.UserInfo.objects.all()
    return render(request, 'index.html', {'user_list': user_list})

模板中使用for循环,将数据展示出来,index.html如下:

<h1>用户列表</h1>

<ul>
    {% for user in user_list %}
        <li>{{ user.name }}</li>
    {% endfor %}
</ul>

如此,会在页面上直接显示出全部数据:
在这里插入图片描述

我们的目标是:每次显示10条数据,数据下方有按钮可以点击,每次点击直接跳转到对应页数。

首先导入django自带的分页工具包:from django.core.paginator import Paginator, page

创建一个paginator对象,在pycharm中按快捷键CTRL+P,查看需要填入的参数:

在这里插入图片描述
这个对象可以点出一些功能,具体查看注释部分,views.py如下:

def index(request):
    """
    分页
    :param request:
    :return:
    """    
    user_list = models.UserInfo.objects.all()
    
    paginator = Paginator()   
    # .per_page:     每页显示条目数量
    # .count:        数据总个数
    # .num_pages:    总页数
    # .page_range:   总页数的索引范围,如: (1,10),(1,200)
    # .page:         page对象    
    return render(request, 'index.html', {'user_list': user_list})

其中.page可以用pg = paginator.page(1)得到一个新的对象pg,表示展示第1页的数据。

这个pg对象有如下方法:

    pg = paginator.page(1) # 表示当前显示第1页,这里的pg是另外一个对象,它的方法如下:
    # .has_next              是否有下一页
    # .next_page_number      下一页页码
    # .has_previous          是否有上一页
    # .previous_page_number  上一页页码
    # .object_list           分页之后的数据列表
    # .number                当前页
    # .paginator             paginator对象

假设我们从用户的GET请求中,获取到当前应该显示的页码数current_page:

current_page = request.GET.get('page')

将其作为参数放入pg对象中:pg = paginator.page(current_page)


pg.object_list就是分页后的数据列表,那么我们可以将pg直接交给模板,然后用模板语法点出object_list。完整views.py如下:

from django.shortcuts import render
from django.core.paginator import Paginator, Page
from App import models


def index(request):
    current_page = request.GET.get('page')
    user_list = models.UserInfo.objects.all()
    
    paginator = Paginator(user_list, 10)
    pg = paginator.page(current_page)

    return render(request, 'index.html', {'pg': pg})

index.html如下:

<h1>用户列表</h1>

<ul>
    {% for user in pg.object_list %}
        <li>{{ user.name }}</li>
    {% endfor %}
</ul>

如此,访问http://localhost:8000/app/index?page=2就可以看到第2页的用户数据:

然后,在模板中添加“上一页”和“下一页”两个按钮,index.html部分代码如下:

<div>
    {% if pg.has_previous %}
        <a href="/app/index/?page={{ pg.previous_page_number }}">上一页</a>
    {% endif %}
    {% if pg.has_next %}
        <a href="/app/index/?page={{ pg.next_page_number }}">下一页</a>
    {% endif %}
</div>

再然后,利用for循环,将pg.paginator.page_range将页码展示出来:

    {% for page in pg.paginator.page_range %}
        <a href="/app/index/?page={{ page }}">{{ page }}</a>
    {% endfor %}

效果如图:

在这里插入图片描述

可惜django只能帮我们到这里了。




自定义分页组件

1、每次显示10条数据

核心代码:models.UserInfo.objects.all()[start:end]

GET方式传递当前页码数current_page,确定好每页显示多少条数据per_page。

找到规律后直接渲染到custom.html中。视图函数代码如下:

def custom(request):

    # 表示用户当前想要访问的页面
    current_page = int(request.GET.get('page'))

    # 每页显示10条数据
    per_page = 10

    # 1: 0-10
    # 2: 10-20
    # 3: 20-30
    # ……

    start = (current_page - 1) * per_page
    stop = current_page * 10

    user_list = models.UserInfo.objects.all()[start:stop]
    return render(request, 'custom.html', {'user_list': user_list})

2、封装简化

做一个类,将current_page、per_page作为属性,并且当用户输入的当前页不为整数、或者小于1时,都直接定位1。start、stop作为方法:

class PageInfo(object):

    def __init__(self, current_page, per_page):
        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        if self.current_page < 1:
            self.current_page = 1
        self.per_page = per_page

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

    def stop(self):
        return self.current_page * self.per_page

于是简化custom函数:

def custom(request):
    page_info = PageInfo(request.GET.get('page'), 10)
    user_list = models.UserInfo.objects.all()[page_info.start():page_info.stop()]
    return render(request, 'custom.html', {'user_list': user_list})

3、数据下方显示所有页码

首先,我们在PageInfo类中再增加all_count属性,表示数据总个数,用all_pager表示一共有多少页;

新增pager方法,让它直接返回字符串"<a href='/app/custom/?page=1'>1</a>"

custom中直接将对象page_info传递到前端,然后在前端用{{ page_info.pager|safe }}pager中的字符串渲染成a标签。注意,这里的page_info.pager不需要加括号,模板中自动执行并将返回值拿出来。

views.py如下:

class PageInfo(object):

    def __init__(self, current_page, all_count, per_page):
        """

        :param current_page: 当前页码
        :param all_count: 数据总个数
        :param per_page: 每页显示的个数
        """

        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        if self.current_page < 1:
            self.current_page = 1

        self.per_page = per_page
        self.all_count = all_count
        self.all_pager = math.ceil(self.all_count / self.per_page)

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

    def stop(self):
        return self.current_page * self.per_page

    def pager(self):
        v = "<a href='/app/custom/?page=1'>1</a>"
        return v


def custom(request):
    all_count = models.UserInfo.objects.count()

    page_info = PageInfo(request.GET.get('page'), all_count, 10)
    user_list = models.UserInfo.objects.all()[page_info.start():page_info.stop()]

    return render(request, 'custom.html', {'user_list': user_list, 'page_info': page_info})

给a标签加样式,改善pager()方法:

def pager(self):
    s = str()
    for i in range(1, self.all_pager+1):
        if i == self.current_page:
            temp = "<a style='display:inline-block;padding:5px;margin:5px;background-color:lightsalmon;' href='/app/custom/?page=%d'>%d</a>" % (i, i)
        else:
            temp = "<a style='display:inline-block;padding:5px;margin:5px;' href='/app/custom/?page=%d'>%d</a>" % (i, i)
        s += temp
    return s

此时效果如下:
在这里插入图片描述

4、完善与优化

让页面显示当前页的前后5条数据:

half = int(self.show_page/2)
begin = self.current_page - half
end = self.current_page + half + 1

for i in range(begin, end):
    pass

其中,self.show_page是我们在__init__方法中新增的属性,表示我们期望展示11个页码。

此时,只需要修改for循环中的begin和end,就可以控制页面的起始页码和结尾页码。

我们根据current_page的边界不一样,来判断应该展示的页码状态。具体判断方式如下代码所示:

half = int(self.show_page/2)

# 如果总数据比较少,导致:self.all_pager < self.show_page
if self.all_pager <= self.show_page:
    begin = 1
    end = self.all_pager + 1
else:

    # 如果当前页比较靠前,比如self.current_page = 1之类
    if self.current_page <= half:
        begin = 1
        end = self.show_page + 1

    # 如果当前页比较靠后:
    elif self.current_page > self.all_pager - half:
        begin = self.all_pager - self.show_page + 1
        end = self.all_pager + 1

    # 中间情况:
    else:
        begin = self.current_page - half
        end = self.current_page + half + 1

for i in range(begin, end):
    pass

最后,在字符串s的前后,加上上一页和下一页的标签,注意要判断当前页是否有上一页或下一页:

prev_page = "<a style='display:inline-block;padding:5px;margin:5px;'href='/app/custom/?page=%d'>上一页</a>" % (self.current_page - 1 if self.current_page > 1 else 1)
s += prev_page

for i in range(begin, end):
    pass

next_page = "<a style='display:inline-block;padding:5px;margin:5px;' href='/app/custom/?page=%d'>下一页</a>" % (self.current_page + 1 if self.current_page < self.all_pager else self.all_pager)
s += next_page

以上,还可以将url作为一个参数,以此生成a标签时,用%s将url传递给a标签。

5、Bootstrap美化

bootstrap官网样例:

https://v3.bootcss.com/components/#pagination-default

我们只需要在a标签前增加li标签,再将官网样例的li标签删掉即可。

<nav aria-label="Page navigation">
    <ul class="pagination">
        {{ page_info.pager|safe }}
    </ul>
</nav>

将PageInfo类放入工具箱,其他地方需要使用时,直接导入即可:
在这里插入图片描述
在这里插入图片描述

pager.py代码如下:

import math


class PageInfo(object):

    def __init__(self, current_page, all_count, per_page, show_page=5):
        """

        :param current_page: 当前页码
        :param all_count: 数据总个数
        :param per_page: 每页显示的个数
        :param show_page: 规定显示多少个页码
        """

        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        if self.current_page < 1:
            self.current_page = 1

        self.per_page = per_page
        self.all_count = all_count
        self.all_pager = math.ceil(self.all_count / self.per_page)
        self.show_page = show_page

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

    def stop(self):
        return self.current_page * self.per_page

    def pager(self):
        s = str()

        half = int(self.show_page/2)

        # 如果总数据比较少,导致:self.all_pager < self.show_page
        if self.all_pager <= self.show_page:
            begin = 1
            end = self.all_pager + 1
        else:

            # 如果当前页比较靠前,比如self.current_page = 1之类
            if self.current_page <= half:
                begin = 1
                end = self.show_page + 1

            # 如果当前页比较靠后:
            elif self.current_page > self.all_pager - half:
                begin = self.all_pager - self.show_page + 1
                end = self.all_pager + 1

            # 中间情况:
            else:
                begin = self.current_page - half
                end = self.current_page + half + 1

        prev_page = "<li><a href='/app/custom/?page=%d'>上一页</a></li>" % (self.current_page - 1 if self.current_page > 1 else 1)
        s += prev_page

        for i in range(begin, end):
            if i == self.current_page:
                temp = "<li class='active'><a href='/app/custom/?page=%d'>%d</a></li>" % (i, i)
            else:
                temp = "<li><a href='/app/custom/?page=%d'>%d</a></li>" % (i, i)
            s += temp

        next_page = "<li><a href='/app/custom/?page=%d'>下一页</a></li>" % (
            self.current_page + 1 if self.current_page < self.all_pager else self.all_pager)

        s += next_page

        return s

最终效果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值