Django筆記三十四之分頁操作

XHunter發表於2023-04-30

本文首發於公眾號:Hunter後端

原文連結:Django筆記三十四之分頁操作

這一篇筆記介紹一下如何在 Django 使用分頁。

Django 自帶一個分頁的模組:

from django.core.paginator import Paginator

主要用途是列表資料的切割,比如說有 3000 條使用者資料,前端需要一個列表介面用於展示這些資料,但是一次性展現這麼多資料不合適,所以打算用分頁的方式來操作。

比如一頁20條資料,前端透過按鈕控制 page_num 和 size 引數用於後端返回資料。

以下是本篇筆記目錄:

  1. 直接分頁操作
  2. Paginator 分頁操作
  3. Paginator 其他函式
  4. Page 的其他操作

1、直接分頁操作

在介紹 Django 的分頁模組前,我們一般如果要分頁的話會如何操作呢,這裡我們定義 page_num 引數為 頁數,size 引數為一頁返回的資料量。

假設有這樣一個長度為 20 的列表:

data_list = list(range(20))

我們想要實現每頁三條資料,也就是 size = 3,我們根據 page_num 和 size 引數可以這樣操作:

target_list = data_list[(page_num - 1) * size: page_num * size]

因為頁數是從 1 開始的,而列表的下標是從 0 開始的,所以這裡是 page_num - 1。

以這個為例,我們接下來介紹一下如何使用 Django 的模組來操作分頁。

2、Paginator 分頁操作

Paginator 不僅可以用於 model 的 queryset 資料,也可以用於我們上面這種列表資料 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() 方法接收需要分頁的可迭代資料,可以是這裡的列表,也可以是 Django 裡的 QuerySet 型別,然後透過 .page() 函式指定 page_num 數就可以獲取指定頁數的資料。

另外,如果需要獲取總數,可以直接 .count 獲取接收的可迭代資料的總數。

分頁超出總頁數

比如前面我們根據 size 大小對資料進行了分頁,最多隻能分為 7 頁,但是後面我們的 page 數傳入的是 7,會怎麼辦呢?會報錯:

    raise EmptyPage(_('That page contains no results'))
django.core.paginator.EmptyPage: That page contains no results

如何規避這種情況呢,當然,前端在傳入的時候可以做一定的限制,但是後端也要有這樣的控制,可以在傳入 page_num 引數前就對資料做一個校驗,發現 page_num 超出總頁數則直接 raise 報錯返回前端,或者直接傳入 page_num,透過 try except 來控制,發現報錯的話,直接返回空列表,比如:

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 其他函式

get_page(number)

前面我們對於每頁資料的獲取有一個 try except 的操作:

try:
    target_page_data = paginator.page(page_num)
except:
    target_page_data = []

假設說我們的資料只能分 7 頁資料,那麼 paginator.page(page_num) 的 page_num 引數就只能在 1-7 之間,可以是 int,也可以是字串的 1-7,比如 "2",除此之外輸入的其他引數,比如 0, -1,或者其他非法字串都會引發報錯。

所以我們使用了一個 try except 操作來捕獲異常,當發生異常時,我們返回的是空列表。

get_page() 函式相當於是基於 page() 函式做了異常處理,當我們輸入的資料是非法整數時,比如頁數在 1-7 之間,我們輸入的是 0,或者 -1,或者 10,返回的則是最後一頁資料:

>>> paginator.get_page(99)
<Page 7 of 7>

如果我們輸入的是其他的非法資料的時候,返回的則是第一頁資料:

>>> paginator.get_page('a')
<Page 1 of 7>

count 屬性

前面介紹了,可以透過 paginator.count 的方式來拿到待分頁的資料的總數,這裡介紹一下 .count 實現的方式。

因為 Paginator 是既可以對列表型別資料進行分頁,也可以對 QuerySet 進行分頁,但是 QuerySet 有 .count() 函式,而列表資料是沒有這個操作的。

但是如果統一都用 len() 函式來對輸入的資料進行取長度,這又是不現實的,因為 len() 函式的操作流程會將 QuerySet 資料都載入然後取值,在 QuerySet 無比大的時候這又是不現實的,這一點在之前的 Django 查詢最佳化筆記中有記錄。

所以這裡的 count 背後的方法是先去檢視這個資料有沒有 count() 方法,有的話就執行,比如一個 QuerySet,沒有的話就執行 len() 函式,比如列表資料。

num_pages 屬性

返回總頁數,比如我們前面的示例返回的資料是 7:

paginator.num_pages
# 7

page_range 屬性

返回頁數範圍,是一個 range() 型別:

paginator.page_range

4、Page 的其他操作

這裡的 Page 指的是分頁後的一頁資料的 Page 型別,也就是前面我們定義的 target_page_data 資料:

target_page_data = paginator.page(page_num)

是否有前一頁

>>> target_page_data.has_previous()
# True

是否有後一頁

>>> target_page_data.has_next()
# True

獲取下一頁的頁數

>>> target_page_data.next_page_number()
# 2

獲取前一頁的頁數

target_page_data.next_page_number()

注意:如果當前頁在第一頁或者最後一頁,當我們使用獲取前一頁或者下一頁的頁數時會報錯。

當前頁的開始和結束索引

對於某頁資料,如果想獲取該頁資料在全部資料中的索引,比如說,對於一個長度為 20 的列表進行分頁,每頁數量為 4,獲取的是第 1 頁的資料,那麼這頁資料的開始和結束索引就在 1 和 4,因為這裡定義的索引是從 1 開始計算的。

>>> target_page_data = paginator.page(1)
>>> 
>>> target_page_data.start_index()
# 1
>>> target_page_data.end_index()
# 4

當前頁數

獲取當前頁數:

target_page_data.number

獲取當前頁資料列表

>>> target_page_data.object_list
[12, 13, 14]

如果想獲取更多後端相關文章,可掃碼關注閱讀:
image