Django Pagination 完善分页

2017-06-0223766 阅读88 评论

Django Pagination 简单分页 中,我们实现了一个简单的分页导航效果。但效果有点差强人意,我们只能点上一页和下一页的按钮进行翻页。比较完善的分页效果应该像下面这样,但想实现这样一种效果,Django Pagination 内置的 API 已无能为力。本文将通过拓展 Django Pagination 来实现下图这样比较完善的分页效果。

高级分页效果图

分页效果概述

一个比较完善的分页效果应该具有以下特性,就像上图展示的那样,很多网站都采用了类似这种的分页导航方式。

  • 始终显示第一页和最后一页。
  • 当前页码高亮显示。
  • 显示当前页码前后几个连续的页码。
  • 如果两个页码号间还有其它页码,中间显示省略号以提示用户。

拓展 Pagination

在此之前,我们已将首页文章列表的视图函数转为了类视图,并且使用了类视图 ListView 中已经为我们写好的分页代码来达到分页的目的(详情请查看文章开头处给出的链接)。为了实现如下所展示的分页效果,接下来就需要在 ListView 的基础上进一步拓展分页的逻辑代码。

高级分页效果图

先来分析一下导航条的组成部分,可以看到整个分页导航条其实可以分成 七个部分:

  1. 第 1 页页码,这一页需要始终显示。
  2. 第 1 页页码后面的省略号部分。但要注意如果第 1 页的页码号后面紧跟着页码号 2,那么省略号就不应该显示。
  3. 当前页码的左边部分,比如这里的 3-6。
  4. 当前页码,比如这里的 7。
  5. 当前页码的右边部分,比如这里的 8-11。
  6. 最后一页页码前面的省略号部分。但要注意如果最后一页的页码号前面跟着的页码号是连续的,那么省略号就不应该显示。
  7. 最后一页的页码号。

因此我们的思路是,在视图里将以上七步中所需要的数据生成,然后传递给模板并在模板中渲染显示即可。整个视图的代码如下,由于代码比较长,所以代码实现的功能直接在代码块中注释,就不在文章中进一步说明了。推荐使用大屏幕阅读器获取更好的阅读体验。

blog/views.py

class IndexView(ListView):
    model = Post
    template_name = 'blog/index.html'
    context_object_name = 'post_list'
    paginate_by = 10

    def get_context_data(self, **kwargs):
        """
        在视图函数中将模板变量传递给模板是通过给 render 函数的 context 参数传递一个字典实现的,
        例如 render(request, 'blog/index.html', context={'post_list': post_list}),
        这里传递了一个 {'post_list': post_list} 字典给模板。
        在类视图中,这个需要传递的模板变量字典是通过 get_context_data 获得的,
        所以我们复写该方法,以便我们能够自己再插入一些我们自定义的模板变量进去。
        """

        # 首先获得父类生成的传递给模板的字典。
        context = super().get_context_data(**kwargs)

        # 父类生成的字典中已有 paginator、page_obj、is_paginated 这三个模板变量,
        # paginator 是 Paginator 的一个实例,
        # page_obj 是 Page 的一个实例,
        # is_paginated 是一个布尔变量,用于指示是否已分页。
        # 例如如果规定每页 10 个数据,而本身只有 5 个数据,其实就用不着分页,此时 is_paginated=False。
        # 关于什么是 Paginator,Page 类在 Django Pagination 简单分页:http://zmrenwu.com/post/34/ 中已有详细说明。
        # 由于 context 是一个字典,所以调用 get 方法从中取出某个键对应的值。
        paginator = context.get('paginator')
        page = context.get('page_obj')
        is_paginated = context.get('is_paginated')

        # 调用自己写的 pagination_data 方法获得显示分页导航条需要的数据,见下方。
        pagination_data = self.pagination_data(paginator, page, is_paginated)

        # 将分页导航条的模板变量更新到 context 中,注意 pagination_data 方法返回的也是一个字典。
        context.update(pagination_data)

        # 将更新后的 context 返回,以便 ListView 使用这个字典中的模板变量去渲染模板。
        # 注意此时 context 字典中已有了显示分页导航条所需的数据。
        return context

    def pagination_data(self, paginator, page, is_paginated):
        if not is_paginated:
            # 如果没有分页,则无需显示分页导航条,不用任何分页导航条的数据,因此返回一个空的字典
            return {}

        # 当前页左边连续的页码号,初始值为空
        left = []

        # 当前页右边连续的页码号,初始值为空
        right = []

        # 标示第 1 页页码后是否需要显示省略号
        left_has_more = False

        # 标示最后一页页码前是否需要显示省略号
        right_has_more = False

        # 标示是否需要显示第 1 页的页码号。
        # 因为如果当前页左边的连续页码号中已经含有第 1 页的页码号,此时就无需再显示第 1 页的页码号,
        # 其它情况下第一页的页码是始终需要显示的。
        # 初始值为 False
        first = False

        # 标示是否需要显示最后一页的页码号。
        # 需要此指示变量的理由和上面相同。
        last = False

        # 获得用户当前请求的页码号
        page_number = page.number

        # 获得分页后的总页数
        total_pages = paginator.num_pages

        # 获得整个分页页码列表,比如分了四页,那么就是 [1, 2, 3, 4]
        page_range = paginator.page_range

        if page_number == 1:
            # 如果用户请求的是第一页的数据,那么当前页左边的不需要数据,因此 left=[](已默认为空)。
            # 此时只要获取当前页右边的连续页码号,
            # 比如分页页码列表是 [1, 2, 3, 4],那么获取的就是 right = [2, 3]。
            # 注意这里只获取了当前页码后连续两个页码,你可以更改这个数字以获取更多页码。
            right = page_range[page_number:page_number + 2]

            # 如果最右边的页码号比最后一页的页码号减去 1 还要小,
            # 说明最右边的页码号和最后一页的页码号之间还有其它页码,因此需要显示省略号,通过 right_has_more 来指示。
            if right[-1] < total_pages - 1:
                right_has_more = True

            # 如果最右边的页码号比最后一页的页码号小,说明当前页右边的连续页码号中不包含最后一页的页码
            # 所以需要显示最后一页的页码号,通过 last 来指示
            if right[-1] < total_pages:
                last = True

        elif page_number == total_pages:
            # 如果用户请求的是最后一页的数据,那么当前页右边就不需要数据,因此 right=[](已默认为空),
            # 此时只要获取当前页左边的连续页码号。
            # 比如分页页码列表是 [1, 2, 3, 4],那么获取的就是 left = [2, 3]
            # 这里只获取了当前页码后连续两个页码,你可以更改这个数字以获取更多页码。
            left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]

            # 如果最左边的页码号比第 2 页页码号还大,
            # 说明最左边的页码号和第 1 页的页码号之间还有其它页码,因此需要显示省略号,通过 left_has_more 来指示。
            if left[0] > 2:
                left_has_more = True

            # 如果最左边的页码号比第 1 页的页码号大,说明当前页左边的连续页码号中不包含第一页的页码,
            # 所以需要显示第一页的页码号,通过 first 来指示
            if left[0] > 1:
                first = True
        else:
            # 用户请求的既不是最后一页,也不是第 1 页,则需要获取当前页左右两边的连续页码号,
            # 这里只获取了当前页码前后连续两个页码,你可以更改这个数字以获取更多页码。
            left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]
            right = page_range[page_number:page_number + 2]

            # 是否需要显示最后一页和最后一页前的省略号
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True

            # 是否需要显示第 1 页和第 1 页后的省略号
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True

        data = {
            'left': left,
            'right': right,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'first': first,
            'last': last,
        }

        return data

模板中设置分页导航

接下来便是在模板中设置分页导航了,将导航条的七个部分的数据一一展现即可,示例代码如下:

{% if is_paginated %}
<div class="pagination">
  {% if first %}
    <a href="?page=1">1</a>
  {% endif %}
  {% if left %}
    {% if left_has_more %}
        <span>...</span>
    {% endif %}
    {% for i in left %}
        <a href="?page={{ i }}">{{ i }}</a>
    {% endfor %}
  {% endif %}
  <a href="?page={{ page_obj.number }}" style="color: red">{{ page_obj.number }}</a>
  {% if right %}
    {% for i in right %}
        <a href="?page={{ i }}">{{ i }}</a>
    {% endfor %}
    {% if right_has_more %}
        <span>...</span>
    {% endif %}
  {% endif %}
  {% if last %}
    <a href="?page={{ paginator.num_pages }}">{{ paginator.num_pages }}</a>
  {% endif %}
</div>
{% endif %}

多添加几篇文章,在示例中就可以看到分页效果了。要使分页导航更加美观,通过设置其 CSS 样式即可:

Django 完善分页效果

总结

本章节的代码位于:Step20: complete pagination

如果遇到问题,请通过下面的方式寻求帮助。

-- EOF --

88 评论
登录后回复
只是场烟雨
2019-04-28 18:02:04

博主你好!

我想问下,我照着代码一路写下来,但是在index完善分页这里,不知道为什么,感觉left,right等等值并没有传入index中,导致无法显示分页排版,请问这是什么原因呢?

回复
只是场烟雨 只是场烟雨
2019-04-29 09:48:37

调试了下,出来了,谢谢

回复
sLee0709
2019-04-07 09:18:47

博主您好。

非常感谢您的教程。有个问题想咨询您一下,一开始我通过您的教程能够成功实现刷新页面分页,然后我通过jQuery的load功能加载对应页面的文章列表的div,实现了不刷新页面分页。但问题来了,我的分页菜单没有更新,导致分页菜单一直都是1,2,3,...,7,8这种形式,4-6页的内容无法通过点击分页菜单的页码加载。然后我试了下分页div刷新时,同时也将分页菜单的div替换成新页面中对应的分页菜单,但问题又来了,替换后的分页菜单只能点击一次,再次点击就没有相应了,也不清楚是什么问题。想请教一下博主有没有方法可供参考一下?我的view和html里面的代码跟您给的一样,jQuery代码如下:

<script> 

 $('#p_manu_num a').on('click', function () { //定义分页菜单中a元素的功能

 var art_url = "?page=" + $(this).attr('name') +" #blog_content_arts" //获取目标页面的url中的文章列表

 var manu_url = "?page=" + $(this).attr('name') +" #p_manu_num"  //获取目标页面的url中的分页菜单

 $('#blog_content_arts').load(art_url).transition('fade in')  //加载目标页面中的文章列表到对应div中

 $('#p_manu_num').load(manu_url)  //加载目标页面中的分页菜单到对应div中

 }) 

</script>


或者,django能否结合ajax实现异步分页功能呢?

望博主不吝赐教,十分感谢!

回复
sLee0709 sLee0709
2019-04-07 09:21:58

抱歉,我忘了,我html中的代码跟您略有不同,如下所示:

{% if is_paginated %}
    <div class="ui pagination menu" id="p_manu_num">
        {% if first %}
            <a class="item" name="1">1</a>
        {% endif %}
        {% if left %}
            {% if left_more %}
                <div class="disabled item">...</div>
            {% endif %}
            {% for i in left %}
                <a class="item" name="{{ i }}">{{ i }}</a>
            {% endfor %}
        {% endif %}
        <a class="item active" id="act" name="{{ page_obj.number }}"><b>{{ page_obj.number }}</b></a>
        {% if right %}
            {% for j in right %}
                <a class="item" name="{{ j }}">{{ j }}</a>
            {% endfor %}
            {% if right_more %}
                <div class="disabled item">...</div>
            {% endif %}
        {% endif %}
    {% if last %}
        <a class="item" name="{{ paginator.num_pages }}">{{ paginator.num_pages }}</a>
    {% endif %}
</div>
{% endif %}
回复
BigOrange128
2018-12-19 09:14:04

请问,为什么访问前三页分页没有问题,后面的就无法访问了。

TypeError at / 

'bool' object is not iterable 

Request Method:GET 

Request URL:http://localhost:8000/?page=4 

Django Version:2.1.3

 Exception Type:TypeError 

Exception Value:'bool' object is not iterable 

Exception Location:G:\Anaconda3\envs\web\lib\site-packages\django\template\defaulttags.py in render, line 165Python Executable:G:\Anaconda3\envs\web\python.exe 

Python Version:3.6.2 

Python Path:['D:\\PycharmProjects\\web\\blogproject', 'G:\\Anaconda3\\envs\\web\\python36.zip', 'G:\\Anaconda3\\envs\\web\\DLLs', 'G:\\Anaconda3\\envs\\web\\lib', 'G:\\Anaconda3\\envs\\web', 'G:\\Anaconda3\\envs\\web\\lib\\site-packages'] 

Server time:星期三, 19 十二月 2018 09:09:01 +0800 

Error during template rendering 

In template D:\PycharmProjects\web\blogproject\templates\base.html, error at line 0 

'bool' object is not iterable 

1<!DOCTYPE html>

2<!--模板标签(具有类似函数的功能)-->

3{% load staticfiles %}

4{% load blog_tags %}

5<html>

6<head>

7 <title>BigOrange &amp; Blog</title>

8 <style>

9 span.highlighted{

10 color: #00CCFF;

回复
BigOrange128 BigOrange128
2018-12-19 21:43:30

解决了,下面评论的简洁代码有bug。

回复
热心市民·沈先生 BigOrange128
2019-03-22 04:32:00

请问你怎么解决的哈,我遇到了跟你一样的问题。我用的是博主的代码,不是下面评论的代码。

回复
DFnum26
2018-08-24 11:51:42
提示下:楼上发的CSS样式需要放在base.html的<head></head>中,且用<style type="text/css"></style>包含其中CSS代码!
回复
SamK6517433923 DFnum26
2018-09-13 15:20:52

CSS可以了,感谢~

回复
Rich
2018-07-29 16:48:10

博主您好,请问如果我没有使用您的通用类视图,要如何写高级的分页功能呢? 请问能给个思路或案例嘛?非常感谢您~~~ :)

回复
Rich Rich
2018-07-29 17:26:42

哈哈 我搞定啦! 谢谢楼主啦~~  看到文章内那么多代码心里害怕了,但是其实仔细读一读是可以读懂的

回复
庄鑫王建
2018-06-26 14:25:48

楼主您好!请问分页的居中如何设置呢?我在div中使用 align="center"但是没有效果。谢谢!

回复
jizhongwei
2018-04-10 16:43:16

博主,还想没有实现当前页的页码颜色加深哦。。。。

回复
追梦人物 jizhongwei
2018-04-11 13:11:18

参考评论区的代码,自行添加 css 即可。

回复
花_易秋
2018-04-10 13:30:40
  •  是大法官
  • Image

测试样例

回复
Me_Before丨You
2018-03-24 20:14:33

sequence index must be integer, not ‘slice’ 

这是因为xrange对象不能进行slice操作,进入templatetags,将pagination_tags.py,paginate函数里的page_range = paginator.page_range改为 page_range = list(paginator.page_range)

回复
孤云飘飘zhao
2018-03-01 18:38:22

老师的思路非常好,我来顺便简化一下一些重复的代码.

def pagination_data(self, paginator, page, is_paginated):
    if not is_paginated:
        return {}
    left_has_more = False
    right_has_more = False
    first = False
    last = False
    page_number = page.number
    total_pages = paginator.num_pages
    page_range = paginator.page_range
    right = page_range[page_number:page_number + 2]
    left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]
    if right:
        if right[-1] < total_pages - 1:
            right_has_more = True
        if right[-1] < total_pages:
            last = True
    if left:
        if left[0] > 2:
            left_has_more = True
        if left[0] > 1:
            first = True
回复
郭效杨
2018-01-31 11:08:11

那这样看起来只有index主页有分页啊进入标签页,类别页会调用index view中的分页吗?

回复
双鱼幸福leon 郭效杨
2018-04-26 21:46:13

分类和归档用之前那的视图类继承

class ArchivesView(IndexView)

回复
ELI
2018-01-27 17:49:08

感谢楼主的教程~

贴一下IndexView里的paginator_data函数的比较简洁的写法:

def pagination_data(self, paginator, page, is_paginated):
    if not is_paginated:
        return {}
    left = []
    right = []
    left_has_more = False    
    right_has_more = False    
    first = False    
    last = False   

    page_number = page.number
    total_pages = paginator.num_pages
    page_range = paginator.page_range

    left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:
                      (page_number - 1) if (page_number - 1) > 0 else 0]
    right = page_range[page_number:page_number + 2]

    if right:
        if right[-1] < total_pages:
            last = True        
        if right[-1] < total_pages - 1:
            right_has_more = True    
    if left:
        if left[0] > 1:
            first = True        
        if left[0] > 2:
            left_has_more = True  

    data = {'left': left,
            'right': right,        
            'left_has_more': left_has_more,
            'right_has_more': right_has_more, 
            'first': first,       
            'last': last,}

    return data
回复
amou1758 ELI
2018-09-06 01:23:50

NICE  很好使

回复
Fossen Wang
2017-12-07 21:27:47

分享一段css代码用于美化分页,效果类似于开头的示例:

.pagination {margin-top: 25px; text-align: center;}
.pagination a {
  display: inline-block;
  line-height: 38px;
  padding: 0 15px;
  margin-right: 4px;
  text-align: center;
  background-color: #fff;
  user-select: none;
  cursor: pointer;
  font-size: 14px;
  border: 1px solid #d7dde4;
  border-radius: 4px;
  transition: all .2s ease-in-out;
}
.pagination a:hover {
  color: #A161BF;
  border: 1px solid #A161BF;
}
.pagination .current-page {color: white; background-color: #A161BF;}
.pagination .current-page:hover {
  color: white;
  border: 1px solid #A161BF;
}
.pagination span {
  display: inline-block;
  font-size: 20px;
  line-height: 38px;
  padding: 0 8px;
  margin-right: 4px;
}

还需要改写模板中的一行代码:

<a href="?page={{ page_obj.number }}" style="color: red">{{ page_obj.number }}</a>

改为:

<a class="current-page" href="?page={{ page_obj.number }}" >{{ page_obj.number }}</a>
回复
5岁月神偷5 Fossen Wang
2017-12-15 10:44:20

可以用,谢谢

回复
谢亚波 Fossen Wang
2018-03-19 22:22:11

大哥,这段css代码,贴在哪个位置,原谅小弟不才,问这么简单的问题。

回复
谢亚波 5岁月神偷5
2018-03-19 22:32:01

大哥,这段css代码,贴在哪个位置,原谅小弟不才,问这么简单的问题。

回复
Me_Before丨You 谢亚波
2018-03-24 20:13:13

自定义style

回复
zlh95 Fossen Wang
2018-04-08 15:27:36

骚气紫色高亮

回复
花_易秋 Fossen Wang
2018-04-10 13:44:53

感谢啊,这个颜色适合我,骚气紫!

回复
庄鑫王建 Fossen Wang
2018-06-26 14:24:55

您好!请问分页的居中如何设置呢?我在div中使用 align="center"但是没有效果。谢谢!

回复
Huangbo 庄鑫王建
2018-07-02 23:41:42

在<div class="pagination">外面加一层:<div class="text-center">

回复
xuyy2 谢亚波
2018-07-03 10:52:59

越俎代庖,回答一下;可以复制到custom.css的后面;浏览器要清除缓存,刷新,就能看到效果了

回复
Stallionshell
2017-11-21 20:07:17

博主,在调用父类的方法的时候,super是不是需要添加一下参数?

         context = super().get_context_data(**kwargs)

改为  context = super(IndexView,self).get_context_data(**kwargs)

不然的话 可能出现index界面无法读取文章的情况,不知道是不是python或者django环境版本的问题。这是我测试的时候出现的一个bug

回复
Stallionshell Stallionshell
2017-11-21 20:25:58

好像不是这个的问题,我又出现了这个bug  尴尬

回复
追梦人物 Stallionshell
2017-11-21 20:32:47

这是 python3 的写法,python2需要使用父类和self作为参数。

回复
point6013
2017-10-31 17:45:05

出现了一个问题,假如说是总共两页的时候,按照您的代码,浏览第二页的时候,页码1是不显示的,我觉得应该修改一下

elif page_number == total_pages:  
     left = page_range[(page_number - 3)if (page_number - 3) > 0 else 0:page_number - 1]  
     if left[0] > 2:     
         left_has_more = True  
     if left[0] > 1:      
         first = True

这个地方,如果是总共两页的话,left应该是left=page_range[0:1],此时page_range=[1,2],即left=[1],这个时候如果去比较left[0]与1的大小的话,first=False了,我觉得应该修改此处的代码。不知道我的逻辑对不对

回复
追梦人物 point6013
2017-11-01 16:33:28

可能吧,但我当时测试的时候好像没有发现问题,你测试一下应该就知道了。有问题的话修改一下逻辑即可。

回复
point6013 追梦人物
2017-11-01 16:55:50

谢谢博主,抽出宝贵的上班时间回复消息

回复
cuitongji
2017-10-20 16:01:39

博主你好,完善页码之后出现如下错误是为什么呢?

context = super().get_context_data(**kwargs)

回复
zhb1207 cuitongji
2017-10-31 16:07:53

你是Python2还是3?

回复
cuitongji zhb1207
2017-10-31 16:12:03

我该环境之后没问题了,谢谢博主!

回复
zhb1207 cuitongji
2017-10-31 16:32:38

不用客气,我不是博主^_^

回复
Faraday179
2017-09-21 09:36:15

博主好,  blog/views中添加了分页后,  执行127.0.0.1:8000显示以下错误;

TypeError at /'NoneType' object is not iterable


具体错误如下,  表中有13条数据 , 设置了paginate_by = 2, 不明白为什么pagination_data = None ? 谢谢指点 

/blogproject/blog/views.py in get_context_data

28.  context.update(pagination_data)

Local Vars

Variable         Value

__class__    <class 'blog.views.IndexView'> 

context        {'is_paginated': True, 'object_list': <QuerySet [<Post: r234223>, <Post: kjjadf>]>, 'page_obj': <Page 1 of 7>, 'paginator': <django.core.paginator.Paginator object at 0x1097d53c8>, 'post_list': <QuerySet [<Post: r234223>, <Post: kjjadf>]>, 'view': <blog.views.IndexView object at 0x1097d5358>} 

is_paginated  True 

kwargs   {} 

page    <Page 1 of 7> 

pagination_data  None 

paginator    <django.core.paginator.Paginator object at 0x1097d53c8> 

self           <blog.views.IndexView object at 0x1097d5358

回复
追梦人物
2017-09-02 22:42:25

这通常是你的 url 写错了,对比一下文章中的,仔细一点。

回复
usher
2017-09-02 21:55:16

Error during template rendering

In template D:\Django\blogproject\templates\base.html, error at line 15
Reverse for 'category' with arguments '('',)' not found. 1 pattern(s) tried: ['category/(?P[0-9]+)/$']
5
6 Black & White
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
博主报了这个错误,这是哪错了?

回复
追梦人物 usher
2017-09-02 23:33:50

这通常是你的 url 写错了,对比一下文章中的,仔细一点。

回复
usher 追梦人物
2017-09-03 20:48:03

把+改成*就对了==

回复
一条具有时间跨度的单身狗1
2017-08-28 17:33:38

首页类视图

class IndexView(ListView):
model = Article
template_name = 'blog/index.html'
context_object_name = 'art_list'
ordering = '-art_ctime'
paginate_by = 1

def get_context_data(self, **kwargs):
    # 获取父类传递的给模板的字典
    context = super().get_context_data(**kwargs)

    # 将分页导航条的模板变量更新到context 中
    context.update(pagination_data)

    # 将 context 返回,以便于调用新的 context 来渲染模板
    return context

def pagination_data(self, paginator, page, is_paginated):
    if not is_paginated:
        # 如果没有分页,就不需要导航条,返回空字典
        return {}

    # 当前页面左边的页码,默认空
    left = []

    # 当前页面右边的页码,默认空
    right = []

    # 标识第一页后面是否需要显示省略号
    left_has_more = False

    # 标识最后一页前是否需要省略号
    right_has_more = False

    # 标记是否显示第一页,如果左边连续的页码中包含第一页,则无需显示第一页
    first = False

    # 标记是否显示最后一页
    last = False

    # 获取当前访问的页数
    page_number = page.number

    # 获取分页后的总页数
    total_pages = paginator.num_pages

    # 获取整个分页页码表,比如分 3 页则是 [1, 2, 3]
    page_range = paginator.page_range

    # 切割页数
    cut_page = 2

    # 如果所在页面在第一页,则判断后面页数
    if page_number == 1:
        right = page_range[page_number:(page_number + cut_page)]

        if total_pages > (right[-1] + 2):
            right_has_more = True

        if total_pages > right[-1]:
            last = True
    # 如果在最后一页
    elif page_number == total_pages:
        left = page_range[(-(cut_page + 1)):(-1)]

        if left[0] > 2:
            left_has_more = True

        if left[0] > 1:
            first = True
    # 如果即不再第一页也不再最后一页
    else:
        # 判断左边
        left = page_range[(-((total_pages - page_number + 1) + cut_page)):(-(total_pages - page_number + 1))]
        if left[0] > 2:
            left_has_more = True

        if left[0] > 1:
            first = True

        # 判断右边
        right = page_range[page_number:(page_number + cut_page)]

        if total_pages > (right[-1] + 1):
            right_has_more = True

        if total_pages > (right[-1]):
            last = True

    data = {
        'left': left,
        'right': right,
        'left_has_more': left_has_more,
        'right_has_more': right_has_more,
        'first': first,
        'last': last,

   return data

这是我写的,有些地方和博主的不一样,是自定义的,测试如果有问题的可以参考。有些地方分开可能好理解一点。

回复
一条具有时间跨度的单身狗1 一条具有时间跨度的单身狗1
2017-08-28 17:34:43

data 字典那里复制的时候少了个 }

回复
-陈思煜
2017-08-28 01:03:37

我感觉作者写的代码逻辑有问题,但是运行却能完美运行,这是DJango里面自带的功能吗?比如
if page_number == 1:
right = page_range[page_number:page_number + 2]
当页面只有2页的话,最后right的值却是2和3 但是page_range是没有这个值的

回复
-陈思煜 -陈思煜
2017-08-28 01:05:50

还有比如:
else:
left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]
right = page_range[page_number:page_number + 2]
当请求到倒数第二页时,right的最大那个比倒数第一页的值还大1
但还是没报错

回复
-陈思煜 -陈思煜
2017-08-28 01:12:16

看来是我对切片理解不够深了,试了一下,超出范围的切片后,python止取出有效的数值

回复
皮皮欧呢
2017-08-27 22:29:47

博主,我现在遇到一个问题是:在admin中添加post文章时,在选择tag和category时他们给出的选项是tag object和category object,但是我想要的是tag或者category里边的(Java,Python,数据结构......)等,试了几个方法还是不行

回复
追梦人物 皮皮欧呢
2017-08-28 16:57:15

你需要为 model 定义 str 方法,详情见之前关于model 部分的教程。

回复
zengzhengrong
2017-07-26 19:03:17

博主,我没用类方法写视图,没有继承ListView,没有page_obj、is_paginated,这两个字典。

回复
追梦人物 zengzhengrong
2017-08-04 20:43:26

可以参考着上一篇的内容自己创建,明白了运行原理自己写也不难。

回复
LeBron-Raymond-Anson
2017-07-24 22:48:27

博主!我问个问题,我按照你的博文代码一步一步理解敲的,也和GitHub上的代码核对过了,并无出入,这么显示不了上一页和下一页的按钮呢

回复
追梦人物 LeBron-Raymond-Anson
2017-07-25 08:32:04

是不是测试地数据太少不足以分页呀?

回复
JustBreaking
2017-06-14 11:31:17

亲测我觉得博主的代码没问题
只是我用的py2.7 page_range返回的是一个迭代器,要用list转化一下就好。
至于right[page_num:page_num+2] 和left[]的切片,顶多为空(当前页是最后一页),但是即使超出列表的index也不会报错。这样写right/left列表的长度可能为0,可能为1,也可能为2。比较灵活。
感谢博主能提供这么好的教程!

回复
yeliang
2017-06-10 01:48:34

使用python2.7的同学super().get_context_data(kwargs)这里会报错,应该要改下super(IndexView, self).get_context_data(kwargs)。
使用2.7还需要改写的地方是paginator.page_range,会得到一个xrange的生成器,这个xrange是不能切片的,但是3.6版本没有xrange了,xrange已经变成range,所以可以切片

回复
追梦人物 yeliang
2017-06-10 10:52:17

嗯,对 py2 的兼容我没有考虑了。

回复
JustBreaking yeliang
2017-06-14 11:21:55

没切片,用list(page_range)就好勒

回复
Bivectorfoil 追梦人物
2017-06-14 22:55:01

首先感谢博主精彩的教程,目前我有一个问题,在将index视图函数用类改写以后,访问主页(index.html)却得到这样的错误:TemplateDoesNotExist: blogs/post_list.html,(blogs是我的app名称),虽然我使用的是py2,但我已经按上面其他同学给出的方法仔细地重写了有关super方法处和page_range处的代码,但仍然得到这个错误,模板文件的位置也已经做了检查,奇怪的是,用同样方法复写的Detail ,archives和category等视图函数均可以访问成功,在此请教博主和各位同学,希望能够得到解答,多谢!完整的报错信息如下:

Traceback (most recent call last):
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
response = get_response(request)
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 217, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 215, in _get_response
response = response.render()
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/template/response.py", line 107, in render
self.content = self.rendered_content
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/template/response.py", line 82, in rendered_content
template = self.resolve_template(self.template_name)
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/template/response.py", line 64, in resolve_template
return select_template(template, using=self.using)
File "/home/zvector/BlogLearn/venv/local/lib/python2.7/site-packages/django/template/loader.py", line 53, in select_template
raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
TemplateDoesNotExist: blogs/post_list.html

回复
追梦人物 Bivectorfoil
2017-06-15 09:36:05

把 index.html 改为 post_list.html

回复
Bivectorfoil 追梦人物
2017-06-15 11:34:39

感谢博主,目前访问主页成功,访问其他(归档,分类等)也成功了,但我有个疑问,这样修改的原理是什么?我仔细对照过博主github教程上的代码,主页的文件名称并不需要修改(为index.html),这是为何?这样的改动会不会太大,因为相应的其他地方需要用到index.html文件的地方也需要修改。再次感谢博主的解答。

回复
追梦人物 Bivectorfoil
2017-06-15 11:36:38

只是为了方便,所以各个视图函数都渲染了 index.html,如果你的模板不同的话,你可以创建不同的模板,不同视图渲染不同模板就可以了。

回复
Annihilater JustBreaking
2017-10-23 07:52:34

感谢,猜到了是2.7兼容的问题还没找出来,幡然醒悟

回复
qiqiming
2017-06-08 16:29:51

pagination_data 这个函数感觉博主写的逻辑很严谨,但是是不是有一个小小的问题, rigth并不会取到1, 所以如果当前页面为第一页时, 就没有页码1了, 感觉把right = page_range[page_number:page_number + 2],改为right = page_range[page_number - 1:page_number + 2]是不是好一些。

回复
qiqiming qiqiming
2017-06-08 16:30:26

以上说的是page_num == 1时

回复
追梦人物 qiqiming
2017-06-08 18:13:25

应该可以取到 1 吧?我博客用的这段代码,使用下来没发现 bug。你这样修改以后测试有问题么?

回复
qiqiming 追梦人物
2017-06-08 19:46:40

想了一下逻辑,page_num == 1时right = page_range[page_num: page_num + 2] 无论如何取不到1,因为page_range[0]为1, 而此时page_num=1, 取到的是2, first此时也一直为false取不到1。亲测也是如此。

回复
qiqiming qiqiming
2017-06-08 19:47:33

当然不排除我可能有bug。。

回复
追梦人物 qiqiming
2017-06-08 21:43:00

其实我现在搞不清我写的代码了,总之目前来看运行上没什么问题,也就懒得管了。当然我这个代码里可能有些重复,有可以优化的地方。

回复
yeliang qiqiming
2017-06-09 22:52:11

我可以给您解释下,page_num==1的时候 first本来就是要false的,您可能还没有明白这个参数的意思,frist的意义是“标示是否需要显示第一页的页码号”,您现在当前页面是第一页的情况,只要判断right和last以及right_has_more这些参数即可,你看看index.html模板下的代码就知道了

回复
yeliang 追梦人物
2017-06-09 22:54:56

这个代码逻辑性已经非常号了
if page_number == 1:
# 用户请求的是第一页的数据
# 页码的右边部分为[2,3],左边和右边的连续页码都是加2
right = page_range[1:3]
不过我觉的直接写[1:3]会更加简洁明了

回复
追梦人物 yeliang
2017-06-09 22:57:49

必须从 page range 里截取吧?因为有时候只有两页。

回复
追梦人物 yeliang
2017-06-09 22:59:36

嗯,我明白了,第一页时直接用数字代替就可以了。

回复
yeliang 追梦人物
2017-06-09 23:05:46

没事的就是超过这个范围也不会有异常的。我都测试过了

回复
qiqiming yeliang
2017-06-10 15:37:36

我知道first的意思, 我的意思是1不会显示, 因为right里面没有。。。。。

回复
qiqiming yeliang
2017-06-10 15:38:26

page_range[1] = 2

回复
yeliang qiqiming
2017-06-10 20:52:47

当你当前页码为1的时候,right肯定不能包含1啊,这位大哥,right的意思是你当前页码右边显示的两个页码数组,怎么可能有1啊??你当前页码是1啊

回复
追梦人物 qiqiming
2017-06-10 21:03:35

是的 1不会显示,在模板中显示的是 current page,而实际就是 1

回复
qiqiming yeliang
2017-06-11 15:00:16

我知道了,理解不同

回复
qiqiming qiqiming
2017-06-11 15:01:41

找出问题了,我已经修改了,感谢讨论

回复
Annihilater qiqiming
2017-10-23 07:56:49

楼上有人解答了,切片取到有效值为止

回复
Annihilater Annihilater
2017-10-23 07:59:20

好吧,说的不是一个问题我理解错了

回复
追梦人物
2017-06-03 12:59:35

类似的,这个分页方法是通用的,稍加修改可以用于任何需要分页的地方。

回复
corebym
2017-06-03 12:08:13

请问用户profile页面可以做吗? 用户follow,私信、动态等功能

回复
追梦人物 corebym
2017-06-03 13:00:01

这个分页方法是通用的,稍加修改可以用于任何需要分页的地方。

回复