Django 教程第六部分:通用列表和詳情檢視
本教程將擴充套件我們的LocalLibrary網站,為書籍和作者新增列表和詳情頁面。在這裡,我們將學習通用類檢視,並展示它們如何減少常見用例所需的程式碼量。我們還將更詳細地探討 URL 處理,展示如何執行基本模式匹配。
| 預備知識 | 完成所有之前的教程主題,包括Django 教程第五部分:建立我們的主頁。 |
|---|---|
| 目標 | 瞭解何時以及如何使用通用類檢視,以及如何從 URL 中提取模式並將資訊傳遞給檢視。 |
概述
在本教程中,我們將透過為書籍和作者新增列表和詳情頁面來完成 LocalLibrary 網站的第一個版本(更確切地說,我們將向你展示如何實現圖書頁面,並讓你自己建立作者頁面!)
這個過程與建立索引頁類似,我們在上一個教程中展示了它。我們仍然需要建立 URL 對映、檢視和模板。主要區別在於,對於詳情頁面,我們將面臨一個額外的挑戰,即從 URL 中的模式中提取資訊並將其傳遞給檢視。對於這些頁面,我們將演示一種完全不同型別的檢視:通用類列表和詳情檢視。這些檢視可以顯著減少所需的檢視程式碼量,使其更易於編寫和維護。
本教程的最後一部分將演示在使用通用類列表檢視時如何對資料進行分頁。
圖書列表頁面
圖書列表頁面將顯示頁面中所有可用圖書記錄的列表,透過 URL:catalog/books/ 訪問。該頁面將顯示每條記錄的標題和作者,其中標題是關聯圖書詳情頁面的超連結。該頁面將具有與網站中所有其他頁面相同的結構和導航,因此我們可以擴充套件在上一教程中建立的基本模板(base_generic.html)。
URL 對映
開啟 /catalog/urls.py 並複製 'books/' 的路徑設定行,如下所示。與索引頁一樣,此 path() 函式定義了一個與 URL 匹配的模式('books/')、一個在 URL 匹配時將呼叫的檢視函式(views.BookListView.as_view())以及此特定對映的名稱。
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
]
正如上一個教程中討論的,URL 必須已經匹配 /catalog,因此檢視實際上將為 URL:/catalog/books/ 呼叫。
檢視函式的格式與之前不同——這是因為此檢視實際上將作為類實現。我們將繼承一個現有的通用檢視函式,該函式已經完成了我們希望此檢視函式執行的大部分操作,而不是從頭開始編寫我們自己的檢視函式。
對於 Django 類檢視,我們透過呼叫類方法 as_view() 來訪問相應的檢視函式。這完成了建立類例項的所有工作,並確保為傳入的 HTTP 請求呼叫正確的處理方法。
檢視(基於類)
我們可以很容易地將圖書列表檢視編寫為常規函式(就像我們之前的索引檢視一樣),它將查詢資料庫中的所有圖書,然後呼叫 render() 將列表傳遞給指定的模板。然而,我們將使用基於類的通用列表檢視 (ListView)——一個繼承自現有檢視的類。由於通用檢視已經實現了我們所需的大部分功能並遵循了 Django 的最佳實踐,我們將能夠以更少的程式碼、更少的重複和最終更少的維護來建立更健壯的列表檢視。
開啟 catalog/views.py,並將以下程式碼複製到檔案底部。
from django.views import generic
class BookListView(generic.ListView):
model = Book
就這樣!通用檢視將查詢資料庫以獲取指定模型 (Book) 的所有記錄,然後渲染位於 /django-locallibrary-tutorial/catalog/templates/catalog/book_list.html 的模板(我們將在下面建立)。在模板中,您可以使用名為 object_list 或 book_list 的模板變數訪問圖書列表(即,通常是 <模型名稱>_list)。
注意:模板位置的這個奇怪路徑不是打錯了——通用檢視會在應用程式的 /application_name/templates/ 目錄(本例中為 /catalog/templates/)中查詢 /application_name/the_model_name_list.html(本例中為 catalog/book_list.html)中的模板。
您可以新增屬性來改變上述預設行為。例如,如果您需要有多個檢視使用相同的模型,您可以指定另一個模板檔案,或者如果您覺得 book_list 對於您的特定模板用例不直觀,您可能希望使用不同的模板變數名。最可能有用的變體是改變/過濾返回結果的子集——因此,您可能不是列出所有書籍,而是列出其他使用者閱讀最多的前 5 本書。
class BookListView(generic.ListView):
model = Book
context_object_name = 'book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location
覆蓋基於類的檢視中的方法
雖然我們在這裡不需要這樣做,但您也可以覆蓋一些類方法。
例如,我們可以重寫 get_queryset() 方法來改變返回的記錄列表。這比僅僅設定 queryset 屬性更靈活,就像我們在前面程式碼片段中所做的那樣(儘管在這種情況下並沒有真正的益處)
class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
我們也可以重寫 get_context_data(),以便向模板傳遞額外的上下文變數(例如,預設情況下會傳遞圖書列表)。下面的片段展示瞭如何向上下文新增一個名為 some_data 的變數(它將作為模板變數可用)。
class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(BookListView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['some_data'] = 'This is just some data'
return context
這樣做時,遵循上述模式很重要:
- 首先,從我們的超類獲取現有上下文。
- 然後新增您的新上下文資訊。
- 然後返回新的(已更新的)上下文。
注意:檢視內建類通用檢視(Django 文件),瞭解更多可以實現的功能示例。
建立列表檢視模板
建立 HTML 檔案 /django-locallibrary-tutorial/catalog/templates/catalog/book_list.html 並複製以下文字。如上所述,這是通用類列表檢視(針對名為 catalog 的應用程式中名為 Book 的模型)預期的預設模板檔案。
通用檢視的模板與其他模板一樣(當然,傳遞給模板的上下文/資訊可能不同)。與我們的索引模板一樣,我們在第一行擴充套件了基本模板,然後替換了名為content的塊。
{% extends "base_generic.html" %}
{% block content %}
<h1>Book List</h1>
{% if book_list %}
<ul>
{% for book in book_list %}
<li>
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a>
({{book.author}})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books in the library.</p>
{% endif %}
{% endblock %}
檢視預設將上下文(圖書列表)作為 object_list 和 book_list 別名傳遞;兩者都可以使用。
條件執行
我們使用if、else 和 endif 模板標籤來檢查 book_list 是否已定義且不為空。如果 book_list 為空,則 else 子句顯示解釋沒有可列出的書籍的文字。如果 book_list 不為空,則我們遍歷書籍列表。
{% if book_list %}
<!-- code here to list the books -->
{% else %}
<p>There are no books in the library.</p>
{% endif %}
上述條件只檢查一種情況,但您可以使用 elif 模板標籤(例如 {% elif var2 %})測試其他條件。有關條件運算子的更多資訊,請參閱if、ifequal/ifnotequal 和 ifchanged(在 內建模板標籤和過濾器 (Django 文件) 中)。
For 迴圈
模板使用for和endfor模板標籤來迴圈遍歷圖書列表,如下所示。每次迭代都會用當前列表項的資訊填充book模板變數。
{% for book in book_list %}
<li><!-- code here get information from each book item --></li>
{% endfor %}
您也可以使用 {% empty %} 模板標籤來定義如果圖書列表為空時會發生什麼(儘管我們的模板選擇使用條件語句)。
<ul>
{% for book in book_list %}
<li><!-- code here get information from each book item --></li>
{% empty %}
<p>There are no books in the library.</p>
{% endfor %}
</ul>
雖然此處未使用,但在迴圈中 Django 還會建立其他變數,您可以使用它們來跟蹤迭代。例如,您可以測試 forloop.last 變數,以在迴圈最後一次執行時執行條件處理。
訪問變數
迴圈中的程式碼為每本書建立一個列表項,顯示標題(作為指向待建立的詳情檢視的連結)和作者。
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
我們使用“點表示法”訪問關聯圖書記錄的欄位(例如 book.title 和 book.author),其中 book 項後面的文字是欄位名稱(如模型中定義)。
我們還可以在模板中從模型內部呼叫函式——在本例中,我們呼叫 Book.get_absolute_url() 來獲取可用於顯示關聯詳細記錄的 URL。這適用於函式沒有任何引數的情況(無法傳遞引數!)
注意:在模板中呼叫函式時,我們必須小心“副作用”。這裡我們只是獲取一個 URL 來顯示,但一個函式可以做幾乎任何事情——我們不希望僅僅透過渲染模板就刪除我們的資料庫(例如)!
更新基本模板
開啟基本模板(/django-locallibrary-tutorial/catalog/templates/base_generic.html)並插入 {% url 'books' %} 到 所有書籍 的 URL 連結中,如下所示。這將啟用所有頁面中的連結(我們現在可以成功地將其放置到位,因為我們已經建立了“書籍”URL 對映器)。
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="">All authors</a></li>
它看起來怎麼樣?
您現在還無法構建圖書列表,因為我們仍然缺少一個依賴項——圖書詳情頁面的 URL 對映,它用於建立指向單個圖書的超連結。我們將在下一節之後同時顯示列表和詳情檢視。
圖書詳情頁面
圖書詳情頁面將顯示有關特定圖書的資訊,透過 URL catalog/book/<id> 訪問(其中 <id> 是圖書的主鍵)。除了 Book 模型中的欄位(作者、摘要、ISBN、語言和流派)之外,我們還將列出可用副本 (BookInstances) 的詳細資訊,包括狀態、預期歸還日期、版本說明和 ID。這將使我們的讀者不僅可以瞭解圖書,還可以確認圖書是否可用以及何時可用。
URL 對映
開啟 /catalog/urls.py 並新增名為“book-detail”的路徑,如下所示。此 path() 函式定義了一個模式、關聯的通用類詳情檢視以及一個名稱。
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
對於 book-detail 路徑,URL 模式使用特殊語法來捕獲我們想要檢視的特定圖書 ID。該語法非常簡單:尖括號定義了要捕獲的 URL 部分,幷包含檢視可用於訪問捕獲資料的變數名稱。例如,<something> 將捕獲標記的模式並將值作為變數“something”傳遞給檢視。您可以選擇在變數名稱前加上一個轉換器規範來定義資料型別(int、str、slug、uuid、path)。
在本例中,我們使用 ' 來捕獲圖書 ID,它必須是一個特殊格式的字串,並將其作為名為 pk(主鍵的縮寫)的引數傳遞給檢視。這是用於在資料庫中唯一儲存圖書的 ID,如圖書模型中定義。
注意:如前所述,我們匹配的 URL 實際上是 catalog/book/<digits>(因為我們在 catalog 應用程式中,所以假定為 /catalog/)。
警告:基於類的通用詳細檢視期望傳遞一個名為 pk 的引數。如果您正在編寫自己的函式檢視,您可以使用任何喜歡的引數名稱,或者確實可以在一個未命名的引數中傳遞資訊。
高階路徑匹配/正則表示式入門
注意:您不需要此部分來完成本教程!我們提供它是因為了解此選項很可能在您以 Django 為中心的未來中很有用。
path() 提供的模式匹配簡單而有用,適用於您只想捕獲任何字串或整數的(非常常見)情況。如果您需要更精細的過濾(例如,只過濾具有特定字元數的字串),那麼您可以使用re_path()方法。
此方法的用法與 path() 類似,但它允許您使用正則表示式指定模式。例如,之前的路徑可以寫成如下所示
re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
正則表示式是一種極其強大的模式對映工具。坦率地說,它們相當不直觀,可能會讓初學者望而生畏。以下是一個非常簡短的入門教程!
首先要知道的是,正則表示式通常應該使用原始字串字面量語法宣告(即,它們被括起來,如下所示:r'<你的正則表示式文字放在這裡>')。
您需要了解的用於宣告模式匹配的語法主要部分是
| 符號 | 含義 |
|---|---|
| ^ | 匹配文字的開頭 |
| $ | 匹配文字的結尾 |
| \d | 匹配一個數字(0、1、2、… 9) |
| \w | 匹配一個單詞字元,例如,字母表中的任何大寫或小寫字母、數字或下劃線字元 (_) |
| + | 匹配前一個字元的一個或多個。例如,要匹配一個或多個數字,您可以使用 \d+。要匹配一個或多個“a”字元,您可以使用 a+ |
| * | 匹配前面字元的零個或多個。例如,要匹配空字串或一個單詞,可以使用 \w* |
| ( ) | 捕獲括號內的模式部分。任何捕獲的值都將作為未命名引數傳遞給檢視(如果捕獲了多個模式,則關聯的引數將按照捕獲宣告的順序提供)。 |
| (?P<name>...) | 將模式(由...表示)捕獲為命名變數(在此例中為“name”)。捕獲的值將以指定名稱傳遞給檢視。因此,您的檢視必須宣告一個同名引數! |
| [ ] | 匹配集合中的一個字元。例如,[abc] 將匹配 'a' 或 'b' 或 'c'。[-\w] 將匹配 '-' 字元或任何單詞字元。 |
大多數其他字元可以按字面意義理解!
讓我們看幾個真實的模式示例
| 模式 | 描述 |
|---|---|
| r'^book/(?P<pk>\d+)$' |
這是我們 URL 對映器中使用的 RE。它匹配一個以 它還會捕獲所有數字 (?P<pk>\d+) 並將它們作為名為 'pk' 的引數傳遞給檢視。捕獲的值總是作為字串傳遞! 例如,這將匹配 |
| r'^book/(\d+)$' | 這匹配與前述情況相同的 URL。捕獲的資訊將作為未命名引數傳送到檢視。 |
| r'^book/(?P<stub>[-\w]+)$' |
這匹配一個以 這是“存根”的典型模式。存根是 URL 友好的基於單詞的資料主鍵。如果您希望您的圖書 URL 更具資訊性,您可以使用存根。例如 |
你可以在一次匹配中捕獲多個模式,從而在 URL 中編碼大量不同的資訊。
注意:作為一項挑戰,請考慮如何編碼一個 URL,以列出特定年份、月份、日期釋出的所有書籍,以及可用於匹配它的 RE。
在 URL 對映中傳遞額外的選項
我們在此處未使用但可能覺得有價值的一個功能是,您可以將包含額外選項的字典傳遞給檢視(使用 path() 函式的第三個未命名引數)。如果您想對多個資源使用相同的檢視,並在每種情況下傳遞資料以配置其行為,則此方法可能很有用。
例如,給定如下所示的路徑,對於 /my-url/halibut/ 的請求,Django 將呼叫 views.my_view(request, fish='halibut', my_template_name='some_path')。
path('my-url/<fish>', views.my_view, {'my_template_name': 'some_path'}, name='aurl'),
注意:命名捕獲模式和字典選項都作為命名引數傳遞給檢視。如果捕獲模式和字典鍵使用相同的名稱,則將使用字典選項。
檢視(基於類)
開啟 catalog/views.py,並將以下程式碼複製到檔案底部。
class BookDetailView(generic.DetailView):
model = Book
就這樣!現在您只需建立一個名為 /django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html 的模板,檢視就會將 URL 對映器提取的特定 Book 記錄的資料庫資訊傳遞給它。在模板中,您可以使用名為 object 或 book 的模板變數訪問圖書的詳細資訊(即,通常是 the_model_name)。
如果需要,您可以更改使用的模板以及在模板中引用書籍的上下文物件的名稱。您還可以覆蓋方法,例如,向上下文中新增額外資訊。
如果記錄不存在怎麼辦?
如果請求的記錄不存在,則通用類詳情檢視將自動為您引發 Http404 異常——在生產環境中,這將自動顯示適當的“資源未找到”頁面,您可以根據需要進行自定義。
為了讓您瞭解其工作原理,下面的程式碼片段演示瞭如果您不使用通用類詳情檢視,如何將基於類的檢視實現為函式。
def book_detail_view(request, primary_key):
try:
book = Book.objects.get(pk=primary_key)
except Book.DoesNotExist:
raise Http404('Book does not exist')
return render(request, 'catalog/book_detail.html', context={'book': book})
該檢視首先嚐試從模型中獲取特定的圖書記錄。如果失敗,檢視應引發 Http404 異常以指示圖書“未找到”。然後,像往常一樣,最後一步是使用模板名稱和 context 引數(作為字典)中的圖書資料呼叫 render()。
如果您不使用通用檢視,另一種方法是呼叫 get_object_or_404() 函式。這是在找不到記錄時引發 Http404 異常的快捷方式。
from django.shortcuts import get_object_or_404
def book_detail_view(request, primary_key):
book = get_object_or_404(Book, pk=primary_key)
return render(request, 'catalog/book_detail.html', context={'book': book})
建立詳情檢視模板
建立 HTML 檔案 /django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html 併為其提供以下內容。如上所述,這是通用類詳情檢視(針對名為 catalog 的應用程式中名為 Book 的模型)預期的預設模板檔名。
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ book.title }}</h1>
<p><strong>Author:</strong> <a href="">{{ book.author }}</a></p>
<!-- author detail link not yet defined -->
<p><strong>Summary:</strong> {{ book.summary }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>
{% for copy in book.bookinstance_set.all %}
<hr />
<p
class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">
{{ copy.get_status_display }}
</p>
{% if copy.status != 'a' %}
<p><strong>Due to be returned:</strong> {{ copy.due_back }}</p>
{% endif %}
<p><strong>Imprint:</strong> {{ copy.imprint }}</p>
<p class="text-muted"><strong>Id:</strong> {{ copy.id }}</p>
{% endfor %}
</div>
{% endblock %}
注意:上面模板中的作者連結有一個空的 URL,因為我們還沒有建立要連結的作者詳情頁面。一旦詳情頁面存在,我們可以透過以下兩種方法獲取其 URL:
-
使用
url模板標籤反轉“author-detail”URL(在 URL 對映器中定義),並向其傳遞圖書的作者例項django<a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a> -
呼叫作者模型的
get_absolute_url()方法(這執行相同的反向操作)django<a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a>
雖然這兩種方法實際上做的是同樣的事情,但更推薦使用 get_absolute_url(),因為它有助於您編寫更一致、更易於維護的程式碼(任何更改只需在一個地方完成:作者模型)。
儘管這個模板稍微大一點,但其中幾乎所有內容之前都已描述過
- 我們擴充套件了基本模板並覆蓋了“內容”塊。
- 我們使用條件處理來確定是否顯示特定內容。
- 我們使用
for迴圈來遍歷物件列表。 - 我們使用點表示法訪問上下文欄位(因為我們使用了詳細通用檢視,所以上下文名為
book;我們也可以使用object)
我們之前沒有看到的第一個有趣的事情是函式 book.bookinstance_set.all()。這個方法是 Django “自動”構建的,用於返回與特定 Book 關聯的 BookInstance 記錄集。
{% for copy in book.bookinstance_set.all %}
<!-- code to iterate across each copy/instance of a book -->
{% endfor %}
需要此方法是因為您只在關係中的“多”方(BookInstance)聲明瞭 ForeignKey(一對多)欄位。由於您沒有在另一方(“一”)模型中宣告關係,因此它(Book)沒有任何欄位來獲取關聯記錄集。為了解決這個問題,Django 構建了一個適當命名的“反向查詢”函式,您可以使用它。函式的名稱由宣告 ForeignKey 的模型名稱小寫形式後跟 _set 構成(即,在 Book 中建立的函式是 bookinstance_set())。
注意:這裡我們使用 all() 來獲取所有記錄(預設值)。雖然您可以在程式碼中使用 filter() 方法來獲取記錄的子集,但您不能直接在模板中這樣做,因為您無法為函式指定引數。
另請注意,如果您沒有定義排序(在基於類的檢視或模型上),您還會看到開發伺服器出現類似以下的錯誤
[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 /foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> allow_empty_first_page=allow_empty_first_page, **kwargs)
之所以會發生這種情況,是因為分頁器物件需要對底層資料庫執行某種 ORDER BY。沒有它,它無法確定返回的記錄是否真的按正確順序!
本教程尚未涵蓋分頁(暫時還沒有!),但是由於您不能使用 sort_by() 並傳遞引數(與上面描述的 filter() 相同),您將不得不從以下三種選擇中選擇:
- 在模型上的
class Meta宣告中新增ordering。 - 在自定義基於類的檢視中新增
queryset屬性,指定order_by()。 - 在自定義類檢視中新增
get_queryset方法,並指定order_by()。
如果你決定為 Author 模型使用 class Meta(可能不如定製基於類的檢視靈活,但足夠簡單),你最終會得到類似這樣的結果
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
def get_absolute_url(self):
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
return f'{self.last_name}, {self.first_name}'
class Meta:
ordering = ['last_name']
當然,欄位不一定是 last_name:它可以是任何其他欄位。
最後但同樣重要的是,您應該按資料庫中實際具有索引(唯一或不唯一)的屬性/列進行排序,以避免效能問題。當然,在這裡沒有必要(我們可能有太少的書籍和使用者,所以可能有點超前),但對於未來的專案來說,這是一個值得記住的事情。
模板中第二個有趣(且不明顯)的事情是我們顯示每本書例項狀態文字的地方(“可用”、“維護”等)。細心的讀者會注意到,我們用於獲取狀態文字的方法 BookInstance.get_status_display() 沒有出現在程式碼的其他地方。
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">
{{ copy.get_status_display }} </p>
此函式是自動建立的,因為 BookInstance.status 是一個選項欄位。Django 會為模型中的每個選項欄位 foo 自動建立一個方法 get_foo_display(),可用於獲取欄位的當前值。
它看起來怎麼樣?
此時,我們應該已經建立了顯示圖書列表和圖書詳情頁面所需的一切。執行伺服器(python3 manage.py runserver)並在瀏覽器中開啟 http://127.0.0.1:8000/。
警告:暫時不要點選任何作者或作者詳情連結——您將在挑戰中建立它們!
點選所有圖書連結,顯示圖書列表。

然後點選您的某本書的連結。如果一切設定正確,您應該會看到類似以下截圖的內容。

分頁
如果您只有少量記錄,我們的圖書列表頁面看起來會很好。然而,當您擁有數十或數百條記錄時,頁面載入時間將逐漸延長(並且內容太多,無法合理瀏覽)。解決此問題的方法是為列表檢視新增分頁,減少每頁顯示的專案數量。
Django 對分頁提供了出色的內建支援。更棒的是,它內置於通用類列表檢視中,因此您無需做太多工作即可啟用它!
檢視
開啟 catalog/views.py,並新增如下所示的 paginate_by 行。
class BookListView(generic.ListView):
model = Book
paginate_by = 10
新增此功能後,一旦您擁有超過 10 條記錄,檢視將開始對傳送到模板的資料進行分頁。不同的頁面透過 GET 引數訪問——要訪問第 2 頁,您可以使用 URL /catalog/books/?page=2。
模板
既然資料已分頁,我們需要在模板中新增支援以滾動瀏覽結果集。因為我們可能希望對所有列表檢視進行分頁,所以我們將此新增到基本模板中。
開啟 /django-locallibrary-tutorial/catalog/templates/base_generic.html 並找到“內容塊”(如下所示)。
{% block content %}{% endblock %}
緊隨 {% endblock %} 之後複製以下分頁塊。程式碼首先檢查當前頁面是否啟用了分頁。如果是,它會新增適當的下一頁和上一頁連結(以及當前頁碼)。
{% block pagination %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
{% endblock %}
page_obj 是一個Paginator物件,如果當前頁面正在使用分頁,它將存在。它允許您獲取有關當前頁面、上一頁、總頁數等所有資訊。
我們使用 {{ request.path }} 獲取當前頁面 URL 以建立分頁連結。這很有用,因為它與我們正在分頁的物件無關。
就這樣!
它看起來怎麼樣?
下面的截圖展示了分頁的外觀——如果您在資料庫中輸入的圖書標題少於 10 個,那麼您可以透過降低 catalog/views.py 檔案中 paginate_by 行中指定的數字來更輕鬆地測試它。為了獲得以下結果,我們將其更改為 paginate_by = 2。
分頁連結顯示在底部,根據您所在的頁面顯示“上一頁”/“下一頁”連結。

挑戰自我
本文的挑戰是建立完成專案所需的作者詳情和列表檢視。這些檢視應透過以下 URL 提供:
catalog/authors/— 所有作者的列表。catalog/author/<id>— 特定作者的詳情檢視,其中主鍵欄位名為<id>
URL 對映器和檢視所需的程式碼應與我們上面建立的 Book 列表和詳情檢視幾乎相同。模板將有所不同,但會具有類似的行為。
備註
-
建立作者列表頁面的 URL 對映器後,您還需要更新基本模板中的所有作者連結。遵循我們更新所有圖書連結時所用的相同過程。
-
一旦你建立了作者詳情頁面的 URL 對映器,你也應該更新圖書詳情檢視模板(/django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html),這樣作者連結就會指向你新的作者詳情頁面(而不是一個空的 URL)。推薦的做法是在作者模型上呼叫
get_absolute_url(),如下所示。django<p> <strong>Author:</strong> <a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a> </p>
完成後,您的頁面應該看起來像下面的螢幕截圖。


總結
恭喜,我們的基本圖書館功能現已完成!
在本文中,我們學習瞭如何使用通用的基於類的列表和詳情檢視,並使用它們建立了檢視書籍和作者的頁面。在此過程中,我們瞭解了使用正則表示式進行模式匹配,以及如何將資料從 URL 傳遞到檢視。我們還學習了一些使用模板的技巧。最後,我們展示瞭如何對列表檢視進行分頁,以便即使有許多記錄,我們的列表也易於管理。
在接下來的文章中,我們將擴充套件此庫以支援使用者賬戶,從而演示使用者認證、許可權、會話和表單。
另見
- 內建的基於類的通用檢視(Django 文件)
- 通用顯示檢視(Django 文件)
- 基於類的檢視簡介(Django 文件)
- 內建模板標籤和過濾器(Django 文件)
- 分頁(Django 文件)
- 進行查詢 > 相關物件 (Django 文件)