codecamp

Django Tutorial Part 6: Generic list and detail views

先决条件: 完成之前的所有教学主题,包括 Django教程第5部分:创建我们的主页
目的: 了解在何处以及如何使用基于类的基本视图,以及如何从URL中提取模式并将信息传递给视图。

概述

在本教程中,我们将完成第一个版本的 LocalLibrary >通过添加图书和作者的列表和详细信息页面(或更准确地说,我们将向您介绍如何实现图书页面,并让您自己创建作者页面)。

该过程类似于创建索引页,我们在上一个教程中显示。 我们仍然需要创建URL地图,视图和模板。 主要的区别是,对于详细页面,我们将有额外的挑战,从URL中的模式提取信息并将其传递给视图。 对于这些页面,我们将演示一个完全不同类型的视图:基于类的列表和详细视图。 这些可以显着减少所需的视图代码量,使其更易于编写和维护。

本教程的最后一部分将演示如何在使用基于类的列表视图时对数据进行分页。

图书列表页

图书列表页面将显示页面中所有可用图书记录的列表,使用以下网址访问: catalog / books / 该页面将显示每个记录的标题和作者,标题是相关联的图书详细信息页面的超链接。 该网页与网站中的所有其他网页具有相同的结构和导航,因此可以扩展我们在上一教程中创建的基本模板( base_generic.html )。

URL映射

打开 /catalog/urls.py ,然后复制下面粗体显示的行。 与我们的索引映射非常类似,这个 url()函数定义了一个正则表达式(RE)来匹配URL( r\'^ books / $\' 如果URL匹配( views.BookListView.as_view())和此特定映射的名称,则将调用此函数。

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^books/$', views.BookListView.as_view(), name='books'),
]

这里的正则表达式匹配等于 books / ( ^ 是字符串开始标记, $ 是字符串标记的结尾)的URL。 如前面的教程中所讨论的,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 ),然后渲染位于 /locallibrary/catalog/templates/catalog/book_list.html (我们将在下面创建)。 在模板中,您可以使用名为 object_list book_list 的模板变量访问图书列表(即通常为" the_model_name _list 代码>")。

注意:模板位置的尴尬路径不是错误印记 - 通用视图会在 / application_name / the_model_name 应用程序的 / application_name / templates / 目录()中的 / list.html ( catalog / book_list.html > / catalog / templates /)。

您可以添加属性以更改上述默认行为。 例如,如果您需要具有使用此相同模型的多个视图,则可以指定另一个模板文件,或者如果 book_list 对您的特定模板使用不直观,则可能需要使用不同的模板变量名称 -案件。 可能最有用的变化是更改/过滤返回的结果子集 - 因此,不是列出所有图书,而是列出其他用户阅读的前5个图书。

class BookListView(generic.ListView):
    model = Book
    context_object_name = 'my_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

Overriding methods in class-based views

虽然我们不需要这样做,你也可以覆盖一些类方法。

例如,我们可以覆盖 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 a context
        context = super(BookListView, self).get_context_data(**kwargs)
        # Get the blog from id and add it to the context
        context['some_data'] = 'This is just some data'
        return context

当这样做时,重要的是遵循上面使用的模式:

  • First get the existing context from our superclass.
  • Then add your new context information.
  • Then return the new (updated) context.

注意:查看内置 - 在基于类的通用视图(Django docs)中有更多可以做的事例。

创建列表视图模板

创建HTML文件 /locallibrary/catalog/templates/catalog/book_list.html ,然后在下面的文本中复制。 如上所述,这是基于通用类的列表视图(对于名为目录的应用程序中名为 Book 的模型)所期望的默认模板文件。

通用视图的模板就像任何其他模板(虽然传递给模板的上下文/信息当然可能不同)。 与我们的 index 模板一样,我们在第一行中扩展我们的基本模板,然后替换名为 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 (别名;将起作用)。

Conditional execution

我们使用 如果 else endif 模板标签来检查 book_list 是否已定义并且不为空。 它是空的( else 子句),然后我们显示文本,说明没有图书要显示。 如果它不为空,那么我们遍历书的列表。

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

上述条件仅检查一种情况,但您可以使用 elif 模板标记(例如 {%elif var2%} )对其他条件进行测试。 有关条件运算符的详情,请参阅:如果 href ="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifequal-and-ifnotequal"class ="external"> ifequal / ifnotequal https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifchanged"class ="external"> ifchanged en / 1.10 / ref / templates / builtins"class ="external">内置模板标签和过滤器(Django Docs)。

For loops

该模板使用 endfor 代码>模板标记循环访问图书列表,如下所示。 每次迭代用当前列表项的信息填充 book 模板变量。

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

虽然这里不使用,但在循环内Django还将创建其他变量,您可以使用它来跟踪迭代。 例如,您可以测试 forloop.last 变量,以便在上次运行循环时执行条件处理。

Accessing variables

循环中的代码为每本书创建一个列表项,该列表项显示标题(作为尚未创建的详细视图的链接)和作者。

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

我们使用"点符号"(例如 book.title book.author )访问关联的图书记录的字段 book 项下是字段名(如模型中定义)。

我们还可以在模板中从模板中调用函数 - 在这种情况下,我们调用 Book.get_absolute_url()获取一个URL, 。 这个工作提供的函数没有任何参数(没有办法传递参数!)

注意:在模板中调用函数时,我们必须小心处理"副作用"。 这里我们只是得到一个显示的URL,但是一个函数可以做任何事情 - 我们不想删除我们的数据库(例如)只是通过渲染我们的模板!

Update the base template

打开基本模板( / locallibrary / catalog / templates / base_generic.html ),然后在网址中插入 {%url\'books\'%} 所有图书的链接,如下所示。 这将启用所有页面的链接(我们可以成功地把它放在位,现在我们已经创建了"书籍"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地图,需要创建指向各个图书的超链接。 在下一节后,我们将显示列表和详细信息视图。

书详细信息页

书详细信息页将显示关于使用网址 catalog / book / < id> (其中 < id> 是图书的主键)。 除了 Book 模型(作者,摘要,ISBN,语言和流派)中的字段,我们还会列出可用副本的详细信息( BookInstances ), 状态,预期的返回日期,印记和ID,这将使我们的读者不仅了解这本书,而且确认是否/何时可用。

URL映射

打开 /catalog/urls.py 并添加" book-detail "URL映射器,如下面的粗体所示。 这个 url()函数定义一个模式,关联的基于类的详细视图和一个名称。

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^books/$', views.BookListView.as_view(), name='books'),
    url(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
]

与我们以前的映射器不同,在这种情况下,我们使用正则表达式(RE)匹配一个真正的"模式",而不只是一个字符串。 这个特定的RE所做的是与以 book / 开头的任何网址匹配,后跟在行标记结束之前的一个或多个数字(数字)。 在执行匹配时,它"捕获"数字,并将它们作为名为 pk 的参数传递给视图函数。

注意:如前所述,我们匹配的网址实际上是 catalog / book /< digits> (因为我们位于目录应用程序 代码> / catalog / )。

重要:通用基于类的详细视图期望传递名为pk的参数。 如果你正在编写我们自己的函数视图,你可以使用任何你喜欢的参数名,或者确实在一个未命名的参数传递信息。

A lightning regular expression primer

正则表达式是一个令人难以置信的强大的模式映射工具。 我们之前已经避免了很多关于它们的事情,因为我们在URL中匹配硬编码的字符串(而不是模式),并且坦率地说,它们对初学者来说是非常直观和可怕的。

注意:不要惊慌! 我们将匹配的模式很简单,在很多情况下都有很好的文档!

首先要知道的是,正则表达式通常应该使用原始字符串文字语法来声明(即它们如下所示: r\')。

声明模式匹配所需的语法的主要部分是:

符号 含义
^ 匹配文本的开头
$ 匹配文本的结尾
\d 匹配数字(0,1,2,... 9)
\w 匹配字词字符,例如 字母表中的任何大写或小写字符,数字或下划线字符(_)
+ Match one or more of the preceding character. For example, to match one or more digits you would use \d+. To match one or more "a" characters, you could use a+
* Match zero or more of the preceding character. For example, to match nothing or a word you could use \w*
() 捕获括号内的模式部分。 任何捕获的值都将作为未命名参数传递到视图(如果捕获多个模式,则相关参数将按声明捕获的顺序提供)。
(?P<name>...) 捕获模式(由...指示)作为命名变量(在本例中为"名称")。 捕获的值将传递到具有指定名称的视图。 因此,您的视图必须声明具有相同名称的参数!
[] 与集合中的一个字符匹配。 例如,[abc]将匹配\'a\'或\'b\'或\'c\'。 [ - \\ w]将匹配" - "字符或任何字字符。

大多数其他字符可以采取字面意思!

让我们考虑几个真实的模式例子:

模式 描述
r'^book/(?P<pk>\d+)$'

这是我们的url映射器中使用的RE。 它与在行开头有 book / ( ^ book / )的字符串匹配,然后具有一个或多个数字( \\ d + ),然后结束(在行标记结束之前没有非数字字符)。

它还捕获所有数字(?P \\ d +),并将它们传递到名为"pk"的参数中的视图。 捕获的值始终以字符串形式传递!

例如,这将匹配 book / 1234 ,并向视图发送一个变量 pk =\'1234\'

r'^book/(\d+)$' 此匹配与上述情况相同的网址。 捕获的信息将作为未命名的参数发送到视图。
r'^book/(?P<stub>[-\w]+)$'

此操作与在行开头有 book / ( ^ book / )的字符串匹配,然后有一个或多个字符 a" - "或字字符( [ - \\ w] + ),然后结束。 它还捕获此组字符,并将它们传递到名为"stub"的参数中的视图。

这是一个相当典型的模式为"存根"。 存根是用于数据的基于URL的基于字的主键。 如果您希望图书网址更具信息性,则可以使用存根。 例如 / catalog / book / the-secret-garden 而不是 / catalog / book / 33

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

注意:作为一个挑战,请考虑如何对网址进行编码,以列出在特定年份,月份,日期和RE中发布的所有可用于匹配的图书。

Passing additional options in your URL maps

我们在这里没有使用,但您可能会发现有价值的一个功能是,您可以声明并传递 views-extra-options"class ="external">其他选项。 选项声明为一个字典,作为第三个未命名的参数传递给 url()函数。 如果要对多个资源使用相同的视图,并传递数据以配置其在每种情况下的行为(在每种情况下我们提供不同的模板),此方法可能很有用。

url(r'^/url/$', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'),
url(r'^/anotherurl/$', views.my_reused_view, {'my_template_name': 'another_path'}, name='anotherurl'),

注意:将两个额外选项和命名捕获的模式作为命名的参数传递给视图。 如果对捕获的模式和额外选项使用同名,则只会将捕获的模式值发送到视图(在附加选项中指定的值将被删除)。

视图(基于类)

打开 catalog / views.py ,然后将以下代码复制到文件底部:

class BookDetailView(generic.DetailView):
    model = Book

而已! 您现在需要做的是创建一个名为 /locallibrary/catalog/templates/catalog/book_detail.html 的模板,该视图将为其传递特定 Book 的数据库信息 >记录由URL映射器提取。 在模板中,您可以使用名为 object book 的模板变量访问书籍列表(即通常为" the_model_name >")。

如果需要,可以更改模板中使用的模板以及用于引用该书的上下文对象的名称。 您还可以覆盖方法,例如,向上下文添加附加信息。

What happens if the record doesn't exist?

如果一个请求的记录不存在,那么通用的基于类的细节视图将自动引发一个Http404异常 - 在生产中,这将自动显示一个适当的"找不到资源"页面,如果需要可以自定义。

只是为了让你了解这是如何工作的,下面的代码片段演示了如何实现基于类的视图作为一个函数,如果你使用通用基于类的细节视图。

def book_detail_view(request,pk):
    try:
        book_id=Book.objects.get(pk=pk)
    except Book.DoesNotExist:
        raise Http404("Book does not exist")

    #book_id=get_object_or_404(Book, pk=pk)
    
    return render(
        request,
        'catalog/book_detail.html',
        context={'book':book_id,}
    )

视图首先尝试从模型中获取特定的书记录。 如果这个失败,视图应该引发一个 Http404 异常,表示该书"找不到"。 最后一步是像往常一样,使用模板名称和 context 参数(作为字典)调用 render()

注意:如果未找到记录,则 get_object_or_404()(如上所示已注释掉)是提高 Http404 异常的一个方便的快捷方式。

创建详细视图模板

创建HTML文件 /locallibrary/catalog/templates/catalog/book_detail.html ,并为其提供以下内容。 如上所述,这是基于通用类的 detail 视图(在名为目录的应用程序中名为 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> {% for genre in book.genre.all %} {{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</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 == 'd' %}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 %}

    上面的tempate中的作者链接有一个空的网址,因为我们还没有创建一个作者详细页面。 一旦存在,您应该更新URL如下:

    <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
    

    虽然有点大,几乎在这个模板中的一切都已经描述过:

    • We extend our base template and override the "content" block.
    • We use conditional processing to determine whether or not to display specific content.
    • We use for loops to loop through lists of objects.
    • We access the context fields using the dot notation (because we've used the detail generic view, the context is named book; we could also use "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 %}

    需要此方法,因为您只在关系的"一"端声明了一个 ForeignKey (一对多)字段。 因为你没有在其他("many")模型中声明关系,所以它没有任何字段来获取关联记录的集合。 为了克服这个问题,Django构造了一个可以使用的适当命名的"反向查找"函数。 该函数的名称由下壳构造,其中 ForeignKey 被声明的模型名称后面跟 _set (即在 Book code>是 bookinstance_set())。

    注意:此处我们使用 all()获取所有记录(默认)。 虽然您可以使用 filter()方法获取代码中的记录子集,但是您无法在模板中直接执行此操作,因为您不能为函数指定参数。

    它是什么样子的?

    在这一点上,我们应该创建显示图书列表和图书详细页面所需的一切。 运行服务器( python3 manage.py runserver ),然后打开浏览器 http://127.0。 0.1:8000 / 。

    警告:不要点击任何作者或作者详细信息链接 - 您将在挑战中创建这些链接!

    点击所有图书链接以显示图书列表。

    ; width:823px;">

    然后点击您的某本书的链接。 如果一切设置正确,您应该看到类似以下屏幕截图。

    ; width:926px;">

    分页

    如果你只有几个记录,我们的书列表页面看起来很好。 然而,当你进入几十或几百的记录,页面将逐渐加载(和有太多的内容,以适当浏览)。 此问题的解决方案是在列表视图中添加分页,减少每个页面上显示的项目数。

    Django对分页提供了极好的内置支持。 更好的是,这是基于类的列表视图,所以你不必做太多的启用它!

    视图

    打开 catalog / views.py ,然后添加以粗体显示的 paginate_by 行。

    class BookListView(generic.ListView):
        model = Book
        paginate_by = 10

    使用此添加,只要您有超过10条记录,视图将开始分页发送到模板的数据。 使用GET参数访问不同的网页 - 要访问第2页,您需要使用网址: / catalog / books / ?page = 2

    模板

    现在数据已分页,我们需要添加对模板的支持以滚动浏览结果集。 因为我们可能想在所有列表视图中执行此操作,我们将以可添加到基本模板的方式执行此操作。

    打开 / locallibrary / catalog / templates / base_generic.html ,并在我们的内容块下面的下面的分页块中复制(下面粗体突出显示)。 代码首先检查是否在当前页面上启用了分页。 如果是,则它适当地添加下一个和上一个链接(以及当前页码)。

    {% block content %}{% 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}} 获取用于创建分页链接的当前网页网址。 这是有用的,因为它独立于我们分页的对象。

    而已!

    它是什么样子的?

    下面的屏幕截图显示了分页的样子 - 如果您没有在数据库中输入超过10个标题,那么您可以通过降低 paginate_by 行中指定的数字更容易地测试它, strong> catalog / views.py 文件。 为了得到下面的结果,我们将它改为 paginate_by = 2

    分页链接显示在底部,根据您所在的页面显示下一个/上一个链接。

    ; width:924px;">

    挑战自己

    本文中的挑战是创建作者详细信息和列出完成项目所需的视图。 这些应在以下网址提供:

    • catalog/authors/ — The list of all authors.
    • catalog/author/<id> — The detail view for the specific author with a primary key field named <id>

    URL映射器和视图所需的代码应该与上面创建的 Book 列表和详细视图几乎相同。 模板将是不同的,但会共享类似的行为。

    注意:

    • Once you've created the URL mapper for the author list page you will also need to update the All authors link in the base template. Follow the same process as we did when we updated the All books link.
    • Once you've created the URL mapper for the author detail page, you should also update the book detail view template (/locallibrary/catalog/templates/catalog/book_detail.html) so that the author link points to your new author detail page (rather than being an empty URL). The line will change to add the template tag shown in bold below.
      <p><strong>Author:</strong> <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a></p> 
      

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

      ; width:825px;">

        概要

        恭喜,我们的基本库功能现已完成!

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

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

        也可以看看

        Express Tutorial Part 5: Displaying library data
        Django Tutorial Part 7: Sessions framework
        温馨提示
        下载编程狮App,免费阅读超1000+编程语言教程
        取消
        确定
        目录
        CSS

        关闭

        MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }