這一篇筆記介紹一下如何在 Django 使用分頁。
Django 自帶一個分頁的模塊:
from django.core.paginator import Paginator
主要用途是列表數(shù)據(jù)的切割,比如說有 3000 條用戶數(shù)據(jù),前端需要一個列表接口用于展示這些數(shù)據(jù),但是一次性展現(xiàn)這么多數(shù)據(jù)不合適,所以打算用分頁的方式來操作。
比如一頁20條數(shù)據(jù),前端通過按鈕控制 page_num 和 size 參數(shù)用于后端返回數(shù)據(jù)。
以下是本篇筆記目錄:
- 直接分頁操作
- Paginator 分頁操作
- Paginator 其他函數(shù)
- Page 的其他操作
1、直接分頁操作
在介紹 Django 的分頁模塊前,我們一般如果要分頁的話會如何操作呢,這里我們定義 page_num 參數(shù)為 頁數(shù),size 參數(shù)為一頁返回的數(shù)據(jù)量。
假設有這樣一個長度為 20 的列表:
data_list = list(range(20))
我們想要實現(xiàn)每頁三條數(shù)據(jù),也就是 size = 3,我們根據(jù) page_num 和 size 參數(shù)可以這樣操作:
target_list = data_list[(page_num - 1) * size: page_num * size]
因為頁數(shù)是從 1 開始的,而列表的下標是從 0 開始的,所以這里是 page_num - 1。
以這個為例,我們接下來介紹一下如何使用 Django 的模塊來操作分頁。
2、Paginator 分頁操作
Paginator 不僅可以用于 model 的 queryset 數(shù)據(jù),也可以用于我們上面這種列表數(shù)據(jù) data_list,我們這里使用 data_list 作為示例。
以下是一個簡單的使用 Paginator 的示例:
from django.core.paginator import Paginator
data_list = list(range(20))
page_num = 1
size = 3
paginator = Paginator(data_list, size)
target_page_data = paginator.page(page_num)
# <Page 1 of 7>
for item in target_page_data:
print(item)
count = paginator.count
在上面的示例中,Paginator() 方法接收需要分頁的可迭代數(shù)據(jù),可以是這里的列表,也可以是 Django 里的 QuerySet 類型,然后通過 .page() 函數(shù)指定 page_num 數(shù)就可以獲取指定頁數(shù)的數(shù)據(jù)。
另外,如果需要獲取總數(shù),可以直接 .count 獲取接收的可迭代數(shù)據(jù)的總數(shù)。
分頁超出總頁數(shù)
比如前面我們根據(jù) size 大小對數(shù)據(jù)進行了分頁,最多只能分為 7 頁,但是后面我們的 page 數(shù)傳入的是 7,會怎么辦呢?會報錯:
raise EmptyPage(_('That page contains no results'))
django.core.paginator.EmptyPage: That page contains no results
如何規(guī)避這種情況呢,當然,前端在傳入的時候可以做一定的限制,但是后端也要有這樣的控制,可以在傳入 page_num 參數(shù)前就對數(shù)據(jù)做一個校驗,發(fā)現(xiàn) page_num 超出總頁數(shù)則直接 raise 報錯返回前端,或者直接傳入 page_num,通過 try except 來控制,發(fā)現(xiàn)報錯的話,直接返回空列表,比如:
data_list = list(range(20))
page_num = 10
size = 3
paginator = Paginator(data_list, size)
try:
target_page_data = paginator.page(page_num)
except:
target_page_data = []
count = paginator.count
3、Paginator 其他函數(shù)
get_page(number)
前面我們對于每頁數(shù)據(jù)的獲取有一個 try except 的操作:
try:
target_page_data = paginator.page(page_num)
except:
target_page_data = []
假設說我們的數(shù)據(jù)只能分 7 頁數(shù)據(jù),那么 paginator.page(page_num) 的 page_num 參數(shù)就只能在 1-7 之間,可以是 int,也可以是字符串的 1-7,比如 "2",除此之外輸入的其他參數(shù),比如 0, -1,或者其他非法字符串都會引發(fā)報錯。
所以我們使用了一個 try except 操作來捕獲異常,當發(fā)生異常時,我們返回的是空列表。
get_page() 函數(shù)相當于是基于 page() 函數(shù)做了異常處理,當我們輸入的數(shù)據(jù)是非法整數(shù)時,比如頁數(shù)在 1-7 之間,我們輸入的是 0,或者 -1,或者 10,返回的則是最后一頁數(shù)據(jù):
>>> paginator.get_page(99)
<Page 7 of 7>
如果我們輸入的是其他的非法數(shù)據(jù)的時候,返回的則是第一頁數(shù)據(jù):
>>> paginator.get_page('a')
<Page 1 of 7>
count 屬性
前面介紹了,可以通過 paginator.count 的方式來拿到待分頁的數(shù)據(jù)的總數(shù),這里介紹一下 .count 實現(xiàn)的方式。
因為 Paginator 是既可以對列表類型數(shù)據(jù)進行分頁,也可以對 QuerySet 進行分頁,但是 QuerySet 有 .count() 函數(shù),而列表數(shù)據(jù)是沒有這個操作的。
但是如果統(tǒng)一都用 len() 函數(shù)來對輸入的數(shù)據(jù)進行取長度,這又是不現(xiàn)實的,因為 len() 函數(shù)的操作流程會將 QuerySet 數(shù)據(jù)都加載然后取值,在 QuerySet 無比大的時候這又是不現(xiàn)實的,這一點在之前的 Django 查詢優(yōu)化筆記中有記錄。
所以這里的 count 背后的方法是先去查看這個數(shù)據(jù)有沒有 count() 方法,有的話就執(zhí)行,比如一個 QuerySet,沒有的話就執(zhí)行 len() 函數(shù),比如列表數(shù)據(jù)。
num_pages 屬性
返回總頁數(shù),比如我們前面的示例返回的數(shù)據(jù)是 7:
paginator.num_pages
# 7
page_range 屬性
返回頁數(shù)范圍,是一個 range() 類型:
paginator.page_range
4、Page 的其他操作
這里的 Page 指的是分頁后的一頁數(shù)據(jù)的 Page 類型,也就是前面我們定義的 target_page_data 數(shù)據(jù):
target_page_data = paginator.page(page_num)
是否有前一頁
>>> target_page_data.has_previous()
# True
是否有后一頁
>>> target_page_data.has_next()
# True
獲取下一頁的頁數(shù)
>>> target_page_data.next_page_number()
# 2
獲取前一頁的頁數(shù)
target_page_data.next_page_number()
注意:如果當前頁在第一頁或者最后一頁,當我們使用獲取前一頁或者下一頁的頁數(shù)時會報錯。
當前頁的開始和結束索引
對于某頁數(shù)據(jù),如果想獲取該頁數(shù)據(jù)在全部數(shù)據(jù)中的索引,比如說,對于一個長度為 20 的列表進行分頁,每頁數(shù)量為 4,獲取的是第 1 頁的數(shù)據(jù),那么這頁數(shù)據(jù)的開始和結束索引就在 1 和 4,因為這里定義的索引是從 1 開始計算的。
>>> target_page_data = paginator.page(1)
>>>
>>> target_page_data.start_index()
# 1
>>> target_page_data.end_index()
# 4
當前頁數(shù)
獲取當前頁數(shù):
target_page_data.number
獲取當前頁數(shù)據(jù)列表
>>> target_page_data.object_list
[12, 13, 14]
原文鏈接:Django筆記三十四之分頁操作