Django 教程 第 6 部分:通用列表和詳情檢視

本教程擴充套件了我們之前建立的 LocalLibrary 網站,為書籍和作者新增列表和詳情頁面。在這裡,我們將學習基於類的通用檢視,並展示它們如何減少您為常見用例編寫程式碼的數量。我們還將更詳細地介紹 URL 處理,展示如何執行基本的模式匹配。

先決條件 完成所有之前的教程主題,包括 Django 教程第 5 部分:建立我們的主頁
目標 瞭解在哪裡以及如何使用基於類的通用檢視,以及如何從 URL 中提取模式並將資訊傳遞給檢視。

概述

在本教程中,我們將透過新增書籍和作者的列表和詳情頁面來完成 LocalLibrary 網站的第一個版本(更準確地說,我們將向您展示如何實現書籍頁面,並引導您自己建立作者頁面!)。

此過程類似於建立索引頁面,我們在之前的教程中展示過。我們仍然需要建立 URL 對映、檢視和模板。主要區別在於,對於詳情頁面,我們將面臨額外的挑戰,即從 URL 中的模式中提取資訊並將其傳遞給檢視。對於這些頁面,我們將演示一種完全不同型別的檢視:基於類的通用列表和詳情檢視。這些可以顯著減少所需的檢視程式碼量,使它們更易於編寫和維護。

本教程的最後一部分將演示如何在使用基於類的通用列表檢視時對資料進行分頁。

圖書列表頁

書籍列表頁面將顯示頁面中所有可用書籍記錄的列表,透過 URL 訪問:catalog/books/。頁面將顯示每條記錄的標題和作者,標題是到關聯書籍詳情頁面的超連結。頁面將具有與站點中所有其他頁面相同的結構和導航,因此我們可以擴充套件我們在上一教程中建立的基本模板(base_generic.html)。

URL 對映

開啟 /catalog/urls.py 並複製設定 'books/' 路徑的行,如下所示。就像索引頁面一樣,此 path() 函式定義了一個與 URL('books/')匹配的模式、一個在 URL 匹配時將被呼叫的檢視函式(views.BookListView.as_view())以及此特定對映的名稱。

python
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,並將以下程式碼複製到檔案的底部

python
from django.views import generic

class BookListView(generic.ListView):
    model = Book

就是這樣!通用檢視將查詢資料庫以獲取指定模型(Book)的所有記錄,然後渲染位於 /django-locallibrary-tutorial/catalog/templates/catalog/book_list.html(我們將在下面建立)的模板。在模板中,您可以使用名為 object_listbook_list 的模板變數訪問書籍列表(即通用地“<模型名稱>_list”)。

注意:模板位置的此尷尬路徑不是列印錯誤——通用檢視在應用程式的 /application_name/templates/ 目錄(在本例中為 /catalog/templates/)內的 /application_name/the_model_name_list.html(在本例中為 catalog/book_list.html)中查詢模板。

您可以新增屬性來更改上述預設行為。例如,如果您需要使用相同模型的多個檢視,則可以指定另一個模板檔案;或者,如果 book_list 對於您的特定模板用例不直觀,則可能需要使用不同的模板變數名稱。可能最有用的變化是更改/過濾返回的結果子集——因此,您可能不會列出所有書籍,而是列出其他使用者閱讀的前 5 本書籍。

python
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 屬性更靈活(儘管在這種情況下沒有真正的益處)。

python
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”的變數新增到上下文中(然後它將作為模板變數可用)。

python
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 並複製下面的文字。如上所述,這是基於類的通用列表檢視(對於名為 Book 且位於名為 catalog 的應用程式中的模型)所期望的預設模板檔案。

通用檢視的模板與任何其他模板一樣(當然,傳遞給模板的上下文/資訊可能有所不同)。與我們的 index 模板一樣,我們在第一行擴充套件了我們的基本模板,然後替換了名為 content 的塊。

django
{% 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_listbook_list 別名傳遞;兩者都可以使用。

條件執行

我們使用 ifelseendif 模板標籤來檢查 book_list 是否已定義且不為空。如果 book_list 為空,則 else 子句顯示文字,說明沒有要列出的書籍。如果 book_list 不為空,則我們遍歷書籍列表。

django
{% if book_list %}
  <!-- code here to list the books -->
{% else %}
  <p>There are no books in the library.</p>
{% endif %}

上面的條件只檢查了一種情況,但您可以使用 elif 模板標籤(例如 {% elif var2 %})測試其他條件。有關條件運算子的更多資訊,請參閱:ififequal/ifnotequalifchanged,位於 內建模板標籤和過濾器(Django 文件)中。

For 迴圈

模板使用 forendfor 模板標籤遍歷書籍列表,如下所示。每次迭代都使用當前列表項的資訊填充 book 模板變數。

django
{% for book in book_list %}
  <li><!-- code here get information from each book item --></li>
{% endfor %}

您也可以使用 {% empty %} 模板標籤來定義如果書籍列表為空時會發生什麼(儘管我們的模板選擇使用條件而不是此標籤)。

django
<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 變數以在最後一次執行迴圈時執行條件處理。

訪問變數

迴圈內的程式碼為每本書建立一個列表項,顯示標題(作為指向尚未建立的詳情檢視的連結)和作者。

django
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})

我們使用“點表示法”(例如 book.titlebook.author)訪問關聯書籍記錄的欄位,其中 book 項後面的文字是欄位名稱(如模型中定義的那樣)。

我們也可以在模板中呼叫模型中的函式——在本例中,我們呼叫 Book.get_absolute_url() 以獲取可用於顯示關聯詳情記錄的 URL。這在函式沒有任何引數(無法傳遞引數!)的情況下有效。

注意:在模板中呼叫函式時,我們必須稍微注意“副作用”。在這裡,我們只是獲取要顯示的 URL,但函式幾乎可以執行任何操作——我們不希望僅僅透過渲染模板就刪除我們的資料庫(例如)!

更新基本模板

開啟基本模板(/django-locallibrary-tutorial/catalog/templates/base_generic.html)並將 {% url 'books' %} 插入到所有書籍的 URL 連結中,如下所示。這將使連結在所有頁面中可用(在我們建立了“books” URL 對映器後,我們可以成功地將其置於此處)。

django
<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() 函式定義了一個模式、一個關聯的基於類的通用詳情檢視以及一個名稱。

python
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)。

在本例中,我們使用 '<int:pk>' 來捕獲書籍 ID,該 ID 必須是格式特殊的字串,並將其作為名為 pk(主鍵的縮寫)的引數傳遞給檢視。這是用於在資料庫中唯一儲存書籍的 ID,如 Book 模型中定義的那樣。

注意:如前所述,我們匹配的 URL 實際上是 catalog/book/<digits>(因為我們在 catalog 應用程式中,/catalog/ 被假定)。

警告:基於類的通用詳情檢視期望傳遞一個名為 pk 的引數。如果您正在編寫自己的函式檢視,您可以使用任何您喜歡的引數名稱,或者在未命名引數中傳遞資訊。

高階路徑匹配/正則表示式入門

注意:您無需本節即可完成本教程!我們提供它是因為了解此選項在您以 Django 為中心的未來可能會有用。

path()提供的模式匹配非常簡單,並且對於(非常常見)只需要捕獲任何字串或整數的情況非常有用。如果您需要更精細的過濾(例如,僅過濾具有特定字元數的字串),則可以使用re_path()方法。

此方法的使用方式與path()相同,只是它允許您使用正則表示式來指定模式。例如,前面的路徑可以按如下所示編寫

python
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。它匹配一個字串,該字串在行首(^book/)具有book/,然後具有一個或多個數字(\d+),然後結束(在行尾標記之前沒有非數字字元)。

它還捕獲所有數字(?P<pk>\d+)並將它們作為名為“pk”的引數傳遞給檢視。捕獲的值始終作為字串傳遞!

例如,這將匹配book/1234,並將變數pk='1234'傳送到檢視。

r'^book/(\d+)$' 這與前面情況匹配相同的 URL。捕獲的資訊將作為未命名引數傳送到檢視。
r'^book/(?P<stub>[-\w]+)$'

這匹配一個字串,該字串在行首(^book/)具有book/,然後具有一個或多個要麼是“-”要麼是單詞字元的字元([-\w]+),然後結束。它還捕獲這組字元並將它們作為名為“stub”的引數傳遞給檢視。

這對於“stub”來說是一個相當典型的模式。“stub”是資料的 URL 友好型基於單詞的主鍵。如果您希望您的圖書 URL 更具資訊性,則可以使用 stub。例如/catalog/book/the-secret-garden而不是/catalog/book/33

您可以在一個匹配中捕獲多個模式,從而在 URL 中編碼大量不同的資訊。

注意:作為挑戰,考慮如何對 URL 進行編碼以列出特定年份、月份、日期釋出的所有書籍,以及可以用來匹配它的 RE。

在 URL 對映中傳遞其他選項

我們在這裡沒有使用的一個功能,但您可能會發現它很有價值,那就是您可以將包含其他選項的字典傳遞給檢視(使用path()函式的第三個未命名引數)。如果您想對多種資源使用相同的檢視並將資料傳遞給每個案例以配置其行為,則此方法很有用。

例如,給定下面顯示的路徑,對於對/myurl/halibut/的請求,Django 將呼叫views.my_view(request, fish='halibut', my_template_name='some_path')

python
path('myurl/<fish>', views.my_view, {'my_template_name': 'some_path'}, name='aurl'),

注意:命名捕獲模式和字典選項都作為命名引數傳遞給檢視。如果您對捕獲模式和字典鍵都使用相同的名稱,則將使用字典選項。

檢視(基於類)

開啟 catalog/views.py,並將以下程式碼複製到檔案的底部

python
class BookDetailView(generic.DetailView):
    model = Book

就是這樣!您現在需要做的就是建立一個名為/django-locallibrary-tutorial/catalog/templates/catalog/book_detail.html的模板,檢視將為其傳遞 URL 對映器提取的特定Book記錄的資料庫資訊。在模板中,您可以使用名為objectbook的模板變數訪問書籍的詳細資訊(即,泛型“the_model_name”)。

如果需要,您可以更改使用的模板和用於在模板中引用書籍的上下文物件的名稱。您還可以覆蓋方法以例如向上下文中新增其他資訊。

如果記錄不存在會發生什麼?

如果請求的記錄不存在,則基於通用類的詳細資訊檢視將自動為您引發Http404異常——在生產環境中,這將自動顯示相應的“資源未找到”頁面,您可以根據需要自定義它。

只是為了讓您瞭解其工作原理,下面的程式碼片段演示瞭如果您使用基於通用類的詳細資訊檢視,將如何實現基於類的檢視作為函式。

python
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異常以指示書籍“未找到”。然後,最後一步照常呼叫render(),並使用模板名稱和context引數(作為字典)中的書籍資料。

如果您不使用通用檢視,另一種方法是呼叫get_object_or_404()函式。這是一個快捷方式,如果記錄未找到,則引發Http404異常。

python
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併為其提供以下內容。如上所述,這是基於通用類的詳細資訊檢視(對於名為Book的模型,位於名為catalog的應用程式中)期望的預設模板檔名。

django
{% 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()更受歡迎,因為它可以幫助您編寫更一致且更易於維護的程式碼(任何更改只需要在一個地方進行:作者模型)。

雖然有點大,但此模板中的幾乎所有內容都已在前面描述過

  • 我們擴充套件了我們的基本模板並覆蓋了“content”塊。
  • 我們使用條件處理來確定是否顯示特定內容。
  • 我們使用for迴圈來迴圈遍歷物件列表。
  • 我們使用點表示法訪問上下文欄位(因為我們使用了詳細資訊通用檢視,所以上下文名為book;我們也可以使用“object”)

我們之前從未見過的第一個有趣的事情是函式book.bookinstance_set.all()。Django 自動構建此方法以返回與特定Book關聯的BookInstance記錄集。

django
{% 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()也是如此),因此您必須在三種選擇之間進行選擇

  1. 在模型的class Meta宣告中新增ordering
  2. 在自定義基於類的檢視中新增queryset屬性,指定order_by()
  3. 向自定義基於類的檢視新增get_queryset方法,並指定order_by()

如果您決定使用Author模型的class Meta(可能不如自定義基於類的檢視靈活,但足夠簡單),您最終將得到如下內容

python
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()沒有出現在程式碼的其他地方。

django
 <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">
 {{ copy.get_status_display }} </p>

此函式是自動建立的,因為BookInstance.statuschoices 欄位。Django 自動為模型中每個 choices 欄位“Foo”建立方法get_FOO_display(),可用於獲取欄位的當前值。

它是什麼樣子的?

至此,我們應該已經建立了顯示書籍列表和書籍詳細資訊頁面所需的所有內容。執行伺服器(python3 manage.py runserver)並在瀏覽器中開啟http://127.0.0.1:8000/

警告:請勿點選任何作者或作者詳細資訊連結 - 您將在挑戰中建立這些連結!

點選所有書籍連結以顯示書籍列表。

Book List Page

然後點選其中一本您書籍的連結。如果一切設定正確,您應該會看到如下所示的螢幕截圖。

Book Detail Page

分頁

如果您只有少量記錄,我們的書籍列表頁面看起來會很好。但是,隨著記錄數量達到幾十或幾百條,頁面載入時間會逐漸變長(並且內容過多,難以瀏覽)。解決此問題的方法是為您的列表檢視新增分頁功能,減少每頁顯示的專案數量。

Django 具有出色的內建分頁支援。更好的是,這內置於基於類的通用列表檢視中,因此您無需執行太多操作即可啟用它!

檢視

開啟catalog/views.py,並新增如下所示的paginate_by行。

python
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並找到“內容塊”(如下所示)。

django
{% block content %}{% endblock %}

{% endblock %}之後立即複製以下分頁塊。程式碼首先檢查當前頁面是否啟用了分頁。如果是,則根據需要新增下一頁上一頁連結(以及當前頁碼)。

django
{% 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

分頁連結顯示在底部,根據您所在的頁面顯示下一頁/上一頁連結。

Book List Page - paginated

挑戰自己

本文中的挑戰是建立完成專案所需的作者詳細資訊和列表檢視。這些檢視應在以下 URL 中提供

  • catalog/authors/ - 所有作者的列表。
  • catalog/author/ - 具有名為的主鍵欄位的特定作者的詳細資訊檢視

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>
    

完成後,您的頁面應該如下面的螢幕截圖所示。

Author List Page

Author Detail Page

總結

恭喜,我們的基本圖書館功能現在已完成!

在本文中,我們學習瞭如何使用基於類的通用列表和詳細資訊檢視,並使用它們建立頁面來檢視我們的書籍和作者。在此過程中,我們學習了使用正則表示式的模式匹配,以及如何將資料從 URL 傳遞到檢視。我們還學習了一些使用模板的技巧。最後,我們展示瞭如何對列表檢視進行分頁,以便即使在擁有大量記錄時也能管理我們的列表。

在我們的下一篇文章中,我們將擴充套件此庫以支援使用者帳戶,從而演示使用者身份驗證、許可權、會話和表單。

另請參閱