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 传递到视图。我们还学习了一些使用模板的技巧。最后,我们展示了如何对列表视图进行分页,以便即使在拥有大量记录时也能管理我们的列表。

在我们的下一篇文章中,我们将扩展此库以支持用户帐户,从而演示用户身份验证、权限、会话和表单。

另请参阅