Django分页器实现分页和源码分析

在博客文章多了情况下,如果全部显示在一页,给人的体验感相当不好,所以就需要分页处理

主要涉及到两个类

一、 Paginatorpage使用

①后端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_notes(request, type_, page_number):
"""
获取Python中type类型的文章,需要分页
:param type_:文章类型
:param request:Request请求对象
:return:对应序号的文章内容
"""
# user = request.session.get('user', default='游客')
user = request.user.username if request.user.is_authenticated else '游客'
try:
number = page_number # 获取前端请求的页数
except Exception as e:
number = 1
data = {
'user': user,
'type': type_,
}
notes = notes_models.Note.note_.filter(type=type_, status='Published')
try:
# 初始化分页器列表,每页5篇,当第一页没有文章的时候,要求其自定义处理异常
paginator = Paginator(notes, 5, allow_empty_first_page=False)
# 通过page()来创建Page对象,前端的模板语言也可以会调用函数
notes = paginator.page(number=number)
data['notes'] = notes
return render(request, 'notes.html', data)
except EmptyPage:
data['error'] = '当前类别没有笔记,如果您想要加盟云博的话,为此页增添技术文章,共同学习进步的话,请注册成为本站的vip,一起记录学习的心得'
return render(request, 'notes.html', data)

说明:

① 通过匹配路由,获取url地址中的参数,首先查找已经发布的所有笔记,然后对这些笔记进行分页处理,生成每页5篇笔记的分页器对象(我喜欢这么叫它),其中allow_empty_first_page是允许第一个也是否为空,默认为True,即没有文章的时候,也不会报EmptyPage的异常。主要用来捕捉异常,实现个性化(自定义)提示用户此页尚没有文章。

② 产生了分页其对象后,我们需要调用page函数去产生Page对象,所以不需要手动实例化。详细过程源码分析请看下方。之后通过render将参数直接渲染到页面上。

注: 每次页面跳转都会重新生成分页器,进行数据切片渲染到前端页面

②前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<ul class="layui-timeline notes_list">
{% if error %}
{{error}}
{% endif %}
{% for note in notes.object_list %}
{% if forloop.first %}
<blockquote class="layui-elem-quote layui-quote-nm quote">最近更新笔记--------文章简介</blockquote>
{% endif %}
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis icon layui-icon-note "></i>
<div class="layui-timeline-content">
<a href=" /notes/user_articles_list/{{note.slug}} " class="layui-timeline-title notes_title">{{note.title}}</a>
<div class="notes_content">
<!--escape进行html转义不过不大管用-->
<div class="shorthands">
<span style="font-weight:bolder">简介:</span>{{note.shorthand}}
<!--修改成关键字-->
</div>
</div>
<div class="note_detail">
<span>作者:{{note.note_author.username}}</span>
<span>关键词:<span style="color:#1a0202">{{note.key_word}}</span></span>
<span>文章类型:{{note.type}}</span>
<span>阅读量:{{note.read_counts}}</span>
<span>发布日期:{{note.publish_date}}</span>
</div>
</div>
</li>
{% endfor %}
{% if notes.has_previous %}
<a href="/notes/user_note/note/{{type}}/page_number={{ notes.previous_page_number }}">上一页</a>
{% endif %}
{% for note in notes.paginator.page_range %}
<a href="/notes/user_note/note/{{type}}/page_number={{ note }}">{{ note }}</a>
{% endfor %}
{% if notes.has_next %}
<a href="/notes/user_note/note/{{type}}/page_number={{ notes.next_page_number }}">下一页</a>
{% endif %}
</ul>

说明:

主要通过模板语言调用函数,模板语言能够结合python函数,进行调用,模板语言中的参数都可以看做对象,毕竟Python崇尚的是万物皆对象嘛~

具体的函数下面将会进行源码分析:

二、Paginator和page源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
class Paginator:

def __init__(self, object_list, per_page, orphans=0,
allow_empty_first_page=True):
self.object_list = object_list # 对象列表
self._check_object_list_is_ordered()
self.per_page = int(per_page)
self.orphans = int(orphans) # 孤儿
self.allow_empty_first_page = allow_empty_first_page

def validate_number(self, number):
"""Validate the given 1-based page number."""
try:
if isinstance(number, float) and not number.is_integer():
raise ValueError
number = int(number)
except (TypeError, ValueError):
raise PageNotAnInteger(_('That page number is not an integer'))
if number < 1:
raise EmptyPage(_('That page number is less than 1'))
if number > self.num_pages:
if number == 1 and self.allow_empty_first_page:
pass
else:
raise EmptyPage(_('That page contains no results'))
return number

def get_page(self, number):
"""
Return a valid page, even if the page argument isn't a number or isn't
in range.
"""
# 其实加了页号的验证
try:
number = self.validate_number(number)
except PageNotAnInteger:
number = 1
except EmptyPage:
number = self.num_pages
return self.page(number)

def page(self, number):
# 对相应的页号进行查询集的切片,最后调用_get_page方法,用于产生Page实例
"""Return a Page object for the given 1-based page number."""
number = self.validate_number(number)
bottom = (number - 1) * self.per_page
top = bottom + self.per_page
if top + self.orphans >= self.count:
top = self.count
return self._get_page(self.object_list[bottom:top], number, self)

def _get_page(self, *args, **kwargs):
# 产生Page实例,传入相应的参数,分别为切片后的列表,页号,分页器对象
"""
Return an instance of a single page.

This hook can be used by subclasses to use an alternative to the
standard :cls:`Page` object.
"""
return Page(*args, **kwargs)

# 只读特性缓存
@cached_property
def count(self):
"""Return the total number of objects, across all pages."""
c = getattr(self.object_list, 'count', None)
if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c):
return c()
return len(self.object_list)
# 获取最大页
@cached_property
def num_pages(self):
"""Return the total number of pages."""
if self.count == 0 and not self.allow_empty_first_page:
return 0
hits = max(1, self.count - self.orphans)
return ceil(hits / self.per_page)

@property
def page_range(self):
"""
Return a 1-based range of pages for iterating through within
a template for loop.
"""
return range(1, self.num_pages + 1)

def _check_object_list_is_ordered(self):
"""
Warn if self.object_list is unordered (typically a QuerySet).
"""
ordered = getattr(self.object_list, 'ordered', None)
if ordered is not None and not ordered:
obj_list_repr = (
'{} {}'.format(self.object_list.model, self.object_list.__class__.__name__)
if hasattr(self.object_list, 'model')
else '{!r}'.format(self.object_list)
)
warnings.warn(
'Pagination may yield inconsistent results with an unordered '
'object_list: {}.'.format(obj_list_repr),
UnorderedObjectListWarning,
stacklevel=3
)

说明:
@cached_property的主要源码如下:

说明几点:

① 拥有__get__方法的类是只能其实例属于类属性的时候生效,这个类中name为类属性,因此self.func(instance)实际上是调用的Paginator实例的num_pages函数,产生的结果放入Paginator实例的缓存,而不会调用cached_property类中的__dict__。

1
2
3
4
5
6
7
8
9
10
def __get__(self, instance, cls=None):
"""
Call the function and put the return value in instance.__dict__ so that
subsequent attribute access on the instance returns the cached value
instead of calling cached_property.__get__().
"""
if instance is None:
return self
res = instance.__dict__[self.name] = self.func(instance)
return res

self.func = func # 打赏猴子补丁,将num_pages传给self.func

Page类源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Page(collections.abc.Sequence):
# 由_get_page传过来的实例
def __init__(self, object_list, number, paginator):
self.object_list = object_list
self.number = number
self.paginator = paginator

def __repr__(self):
return '<Page %s of %s>' % (self.number, self.paginator.num_pages)

def __len__(self):
return len(self.object_list)

def __getitem__(self, index):
if not isinstance(index, (int, slice)):
raise TypeError
# The object_list is converted to a list so that if it was a QuerySet
# it won't be a database hit per __getitem__.
if not isinstance(self.object_list, list):
self.object_list = list(self.object_list)
return self.object_list[index]
# 判断是否有下一页
def has_next(self):
return self.number < self.paginator.num_pages
# 判断是否有前一页
def has_previous(self):
return self.number > 1
# 判断是否有前一页或下一页
def has_other_pages(self):
return self.has_previous() or self.has_next()
# 获取下一页页号
def next_page_number(self):
return self.paginator.validate_number(self.number + 1)
# 获取上一页页号
def previous_page_number(self):
return self.paginator.validate_number(self.number - 1)
# 获取开始的当前页第一条数据在queryset中的索引值
def start_index(self):
"""
Return the 1-based index of the first object on this page,
relative to total objects in the paginator.
"""
# Special case, return zero if no items.
if self.paginator.count == 0:
return 0
return (self.paginator.per_page * (self.number - 1)) + 1
# 获取当前页的最后一条数据的在queryset中的索引值
def end_index(self):
"""
Return the 1-based index of the last object on this page,
relative to total objects found (hits).
"""
# Special case for the last page because there can be orphans.
if self.number == self.paginator.num_pages:
return self.paginator.count
return self.number * self.paginator.per_page