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())以及此特定映射的名称。

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 并复制以下文本。如上所述,这是通用类列表视图(针对名为 catalog 的应用程序中名为 Book 的模型)预期的默认模板文件。

通用视图的模板与其他模板一样(当然,传递给模板的上下文/信息可能不同)。与我们的索引模板一样,我们在第一行扩展了基本模板,然后替换了名为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 链接中,如下所示。这将启用所有页面中的链接(我们现在可以成功地将其放置到位,因为我们已经创建了“书籍”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)。

在本例中,我们使用 '' 来捕获图书 ID,它必须是一个特殊格式的字符串,并将其作为名为 pk(主键的缩写)的参数传递给视图。这是用于在数据库中唯一存储图书的 ID,如图书模型中定义。

注意:如前所述,我们匹配的 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' 的参数传递给视图。

这是“存根”的典型模式。存根是 URL 友好的基于单词的数据主键。如果您希望您的图书 URL 更具信息性,您可以使用存根。例如 /catalog/book/the-secret-garden 而不是 /catalog/book/33

你可以在一次匹配中捕获多个模式,从而在 URL 中编码大量不同的信息。

注意:作为一项挑战,请考虑如何编码一个 URL,以列出特定年份、月份、日期发布的所有书籍,以及可用于匹配它的 RE。

在 URL 映射中传递额外的选项

我们在此处未使用但可能觉得有价值的一个功能是,您可以将包含额外选项的字典传递给视图(使用 path() 函数的第三个未命名参数)。如果您想对多个资源使用相同的视图,并在每种情况下传递数据以配置其行为,则此方法可能很有用。

例如,给定如下所示的路径,对于 /my-url/halibut/ 的请求,Django 将调用 views.my_view(request, fish='halibut', my_template_name='some_path')

python
path('my-url/<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 异常以指示图书“未找到”。然后,像往常一样,最后一步是使用模板名称和 context 参数(作为字典)中的图书数据调用 render()

如果您不使用通用视图,另一种方法是调用 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 并为其提供以下内容。如上所述,这是通用类详情视图(针对名为 catalog 的应用程序中名为 Book 的模型)预期的默认模板文件名。

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(),因为它有助于您编写更一致、更易于维护的代码(任何更改只需在一个地方完成:作者模型)。

尽管这个模板稍微大一点,但其中几乎所有内容之前都已描述过

  • 我们扩展了基本模板并覆盖了“内容”块。
  • 我们使用条件处理来确定是否显示特定内容。
  • 我们使用 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.status 是一个选项字段。Django 会为模型中的每个选项字段 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/<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>
    

完成后,您的页面应该看起来像下面的屏幕截图。

Author List Page

Author Detail Page

总结

恭喜,我们的基本图书馆功能现已完成!

在本文中,我们学习了如何使用通用的基于类的列表和详情视图,并使用它们创建了查看书籍和作者的页面。在此过程中,我们了解了使用正则表达式进行模式匹配,以及如何将数据从 URL 传递到视图。我们还学习了一些使用模板的技巧。最后,我们展示了如何对列表视图进行分页,以便即使有许多记录,我们的列表也易于管理。

在接下来的文章中,我们将扩展此库以支持用户账户,从而演示用户认证、权限、会话和表单。

另见