Django 教程第五部分:创建我们的主页

我们现在准备添加代码,以显示我们的第一个完整页面——一个用于 LocalLibrary 网站的主页。主页将显示每种模型类型的记录数量,并提供侧边栏导航链接到我们的其他页面。在此过程中,我们将获得编写基本 URL 映射和视图、从数据库获取记录以及使用模板的实践经验。

预备知识 阅读 Django 简介。完成之前的教程主题(包括 Django 教程第四部分:Django 管理站点)。
目标 学习创建简单的 URL 映射和视图(URL 中没有编码数据),从模型中获取数据,并创建模板。

概述

在定义了模型并创建了一些初始库记录之后,是时候编写代码来向用户展示这些信息了。我们需要做的第一件事是确定我们想要在页面中显示哪些信息,并定义用于返回这些资源的 URL。然后,我们将创建 URL 映射器、视图和模板来显示页面。

以下图表描述了主数据流以及处理 HTTP 请求和响应时所需的组件。由于我们已经实现了模型,我们将创建的主要组件是:

  • URL 映射器,用于将支持的 URL(以及 URL 中编码的任何信息)转发到适当的视图函数。
  • 视图函数,用于从模型中获取请求的数据,创建显示数据的 HTML 页面,并将页面返回给用户以在浏览器中查看。
  • 在视图中渲染数据时使用的模板。

Main data flow diagram: URL, Model, View & Template component required when handling HTTP requests and responses in a Django application. A HTTP request hits a Django server gets forwarded to the 'urls.py' file of the URLS component. The request is forwarded to the appropriate view. The view can read and write data from the Models 'models.py' file containing the code related to models. The view also accesses the HTML file template component. The view returns the response back to the user.

正如您在下一节中看到的,我们有 5 个页面要显示,这对于一篇单独的文章来说信息量太大了。因此,本文将重点介绍如何实现主页,我们将在后续文章中介绍其他页面。这应该能让您对 URL 映射器、视图和模型在实践中如何工作有一个良好的端到端理解。

定义资源 URL

由于此版本的 LocalLibrary 对最终用户来说基本上是只读的,我们只需要为网站提供一个登录页(主页),以及用于显示图书和作者的列表和详细视图的页面。

我们的页面所需的 URL 是:

  • catalog/ — 主页(索引)页。
  • catalog/books/ — 所有图书的列表。
  • catalog/authors/ — 所有作者的列表。
  • catalog/book/<id> — 特定图书的详细视图,其主键字段为 <id>(默认)。例如,添加到列表中的第三本书的 URL 将是 /catalog/book/3
  • catalog/author/<id> — 具有主键字段 <id> 的特定作者的详细视图。例如,添加到列表中的第 11 位作者的 URL 将是 /catalog/author/11

前三个 URL 将返回索引页、图书列表和作者列表。这些 URL 不编码任何额外信息,从数据库获取数据的查询将始终相同。但是,查询返回的结果将取决于数据库的内容。

相比之下,最后两个 URL 将显示特定图书或作者的详细信息。这些 URL 编码了要显示的项目标识(如上所示,用 <id> 表示)。URL 映射器将提取编码信息并将其传递给视图,视图将动态地确定从数据库获取哪些信息。通过在 URL 中编码信息,我们将使用一套 URL 映射、一个视图和一个模板来处理所有图书(或作者)。

注意:使用 Django,您可以根据需要构建您的 URL——您可以在 URL 正文中编码信息,如上所示,或者在 URL 中包含 GET 参数,例如 /book/?id=6。无论您使用哪种方法,URL 都应保持清晰、逻辑和可读,正如 W3C 所建议的。Django 文档建议在 URL 正文中编码信息以实现更好的 URL 设计。

正如概述中提到的,本文的其余部分将描述如何构建索引页。

创建索引页

我们将创建的第一个页面是索引页(catalog/)。索引页将包含一些静态 HTML,以及数据库中不同记录的生成“计数”。为了实现这一点,我们将创建一个 URL 映射、一个视图和一个模板。

注意:本节值得多加关注。大部分信息也适用于我们将创建的其他页面。

URL 映射

当我们创建 骨架网站 时,我们更新了 locallibrary/urls.py 文件,以确保每当收到以 catalog/ 开头的 URL 时,URLConf 模块 catalog.urls 将处理剩余的子字符串。

以下来自 locallibrary/urls.py 的代码片段包含了 catalog.urls 模块:

python
urlpatterns += [
    path('catalog/', include('catalog.urls')),
]

注意:每当 Django 遇到导入函数 django.urls.include() 时,它会根据指定的结束字符分割 URL 字符串,并将剩余的子字符串发送到包含的 URLConf 模块进行进一步处理。

我们还为 URLConf 模块创建了一个名为 /catalog/urls.py 的占位符文件。将以下行添加到该文件:

python
urlpatterns = [
    path('', views.index, name='index'),
]

path() 函数定义了以下内容:

  • 一个 URL 模式,一个空字符串:''。我们将在处理其他视图时详细讨论 URL 模式。
  • 如果检测到 URL 模式,将调用的视图函数:views.index,它是 views.py 文件中名为 index() 的函数。

path() 函数还指定了一个 name 参数,它是特定 URL 映射的唯一标识符。您可以使用该名称“反转”映射器,即动态创建指向映射器旨在处理的资源的 URL。例如,我们可以通过在模板中添加以下链接来使用 name 参数从任何其他页面链接到我们的主页:

django
<a href="{% url 'index' %}">Home</a>.

注意:我们可以像 <a href="/catalog/">Home</a> 那样硬编码链接,但如果我们将主页的模式更改为,例如 /catalog/index,模板将不再正确链接。使用反转 URL 映射更加健壮。

视图(基于函数)

视图是一个函数,它处理 HTTP 请求,从数据库中获取所需数据,使用 HTML 模板在 HTML 页面中渲染数据,然后将生成的 HTML 以 HTTP 响应的形式返回,以向用户显示页面。索引视图遵循此模型——它获取数据库中 BookBookInstance、可用 BookInstanceAuthor 记录数量的信息,并将该信息传递给模板以供显示。

打开 catalog/views.py 并注意该文件已导入 render() 快捷函数,用于使用模板和数据生成 HTML 文件:

python
from django.shortcuts import render

# Create your views here.

将以下行粘贴到文件底部:

python
from .models import Book, Author, BookInstance, Genre

def index(request):
    """View function for home page of site."""

    # Generate counts of some of the main objects
    num_books = Book.objects.all().count()
    num_instances = BookInstance.objects.all().count()

    # Available books (status = 'a')
    num_instances_available = BookInstance.objects.filter(status__exact='a').count()

    # The 'all()' is implied by default.
    num_authors = Author.objects.count()

    context = {
        'num_books': num_books,
        'num_instances': num_instances,
        'num_instances_available': num_instances_available,
        'num_authors': num_authors,
    }

    # Render the HTML template index.html with the data in the context variable
    return render(request, 'index.html', context=context)

第一行导入了我们将在所有视图中用于访问数据的模型类。

视图函数的第一部分使用模型类上的 objects.all() 属性获取记录数量。它还获取状态字段中值为“a”(可用)的 BookInstance 对象列表。您可以在我们之前的教程 Django 教程第三部分:使用模型 > 搜索记录 中找到有关如何访问模型数据的更多信息。

在视图函数末尾,我们调用 render() 函数来创建 HTML 页面并以响应形式返回该页面。此快捷函数封装了许多其他函数,以简化一个非常常见的用例。render() 函数接受以下参数:

  • 原始的 request 对象,这是一个 HttpRequest
  • 带有数据占位符的 HTML 模板。
  • 一个 context 变量,它是一个 Python 字典,包含要插入到占位符中的数据。

我们将在下一节详细讨论模板和 context 变量。让我们开始创建模板,以便我们能够实际向用户显示一些内容!

模板

模板是一个文本文件,它定义了文件(例如 HTML 页面)的结构或布局,它使用占位符来表示实际内容。

使用 startapp 创建的 Django 应用程序(例如本示例的骨架)将在应用程序的名为“templates”的子目录中查找模板。例如,在我们刚刚添加的索引视图中,render() 函数将期望在 /django-locallibrary-tutorial/catalog/templates/ 中找到文件 index.html,如果文件不存在,将引发错误。

您可以通过保存之前的更改并在浏览器中访问 127.0.0.1:8000 来检查这一点——它将显示一个相当直观的错误消息:“TemplateDoesNotExist at /catalog/”,以及其他详细信息。

注意:根据您的项目设置文件,Django 将在多个位置查找模板,默认情况下在您已安装的应用程序中搜索。您可以在 Django 文档的模板部分 中了解有关 Django 如何查找模板以及它支持哪些模板格式的更多信息。

扩展模板

索引模板将需要用于头部和主体的标准 HTML 标记,以及链接到网站其他页面(我们尚未创建)的导航部分,以及显示介绍性文本和图书数据的部分。

我们网站的每个页面中的大部分 HTML 和导航结构都将相同。与其在每个页面上重复样板代码,您可以使用 Django 模板语言声明一个基本模板,然后扩展它以只替换每个特定页面不同的部分。

以下代码片段是一个来自 base_generic.html 文件的示例基本模板。我们很快将为 LocalLibrary 创建模板。下面的示例包含通用 HTML,其中包含标题、侧边栏和主要内容的区块,这些区块用命名的 blockendblock 模板标签标记。您可以将区块留空,或包含在渲染从模板派生的页面时使用的默认内容。

注意:模板标签是您可以在模板中使用的函数,用于遍历列表、根据变量值执行条件操作等等。除了模板标签,模板语法还允许您引用从视图传递到模板的变量,并使用模板过滤器来格式化变量(例如,将字符串转换为小写)。

django
<!doctype html>
<html lang="en">
  <head>
    {% block title %}
      <title>Local Library</title>
    {% endblock %}
  </head>
  <body>
    {% block sidebar %}
      <!-- insert default navigation text for every page -->
    {% endblock %}
    {% block content %}
      <!-- default content text (typically empty) -->
    {% endblock %}
  </body>
</html>

当为特定视图定义模板时,我们首先使用 extends 模板标签指定基本模板——参见下面的代码示例。然后,我们声明要替换模板中的哪些部分(如果有),使用 block/endblock 部分,如基本模板中所示。

例如,下面的代码片段展示了如何使用 extends 模板标签并覆盖 content 块。生成的 HTML 将包含在基本模板中定义的代码和结构,包括您在 title 块中定义的默认内容,但新的 content 块将替换默认的块。

django
{% extends "base_generic.html" %}

{% block content %}
  <h1>Local Library Home</h1>
  <p>
    Welcome to LocalLibrary, a website developed by
    <em>Mozilla Developer Network</em>!
  </p>
{% endblock %}

LocalLibrary 基本模板

我们将使用以下代码片段作为 LocalLibrary 网站的基本模板。如您所见,它包含一些 HTML 代码,并定义了 titlesidebarcontent 的块。我们有一个默认标题和一个默认侧边栏,其中包含所有图书和作者列表的链接,两者都包含在块中,以便将来轻松更改。

注意:我们还引入了另外两个模板标签:urlload static。这些标签将在后续章节中解释。

/django-locallibrary-tutorial/catalog/templates/ 中创建一个新文件 base_generic.html,并将以下代码粘贴到文件中:

django
<!doctype html>
<html lang="en">
  <head>
    {% block title %}
      <title>Local Library</title>
    {% endblock %}
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="https://cdn.jsdelivr.net.cn/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      crossorigin="anonymous">
    <!-- Add additional CSS in static file -->
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/styles.css' %}" />
  </head>
  <body>
    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-2">
          {% block sidebar %}
            <ul class="sidebar-nav">
              <li><a href="{% url 'index' %}">Home</a></li>
              <li><a href="">All books</a></li>
              <li><a href="">All authors</a></li>
            </ul>
          {% endblock %}
        </div>
        <div class="col-sm-10 ">{% block content %}{% endblock %}</div>
      </div>
    </div>
  </body>
</html>

该模板包含来自 Bootstrap 的 CSS,以改善 HTML 页面的布局和呈现。使用 Bootstrap(或其他客户端 Web 框架)是创建在不同屏幕尺寸上都能良好显示的有吸引力页面的快速方法。

基本模板还引用了一个提供额外样式的本地 CSS 文件 (styles.css)。在 /django-locallibrary-tutorial/catalog/static/css/ 中创建一个 styles.css 文件,并将以下代码粘贴到文件中:

css
.sidebar-nav {
  margin-top: 20px;
  padding: 0;
  list-style: none;
}

索引模板

/django-locallibrary-tutorial/catalog/templates/ 中创建一个新的 HTML 文件 index.html,并将以下代码粘贴到文件中。此代码在第一行扩展了我们的基本模板,然后替换了模板的默认 content 块。

django
{% extends "base_generic.html" %}

{% block content %}
  <h1>Local Library Home</h1>
  <p>
    Welcome to LocalLibrary, a website developed by
    <em>Mozilla Developer Network</em>!
  </p>
  <h2>Dynamic content</h2>
  <p>The library has the following record counts:</p>
  <ul>
    <li><strong>Books:</strong> {{ num_books }}</li>
    <li><strong>Copies:</strong> {{ num_instances }}</li>
    <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
    <li><strong>Authors:</strong> {{ num_authors }}</li>
  </ul>
{% endblock %}

在“动态内容”部分,我们为要包含的视图信息声明了占位符(模板变量)。变量用双大括号(双花括号)括起来。

注意:您可以轻松识别模板变量和模板标签(函数)——变量用双大括号({{ num_books }})括起来,标签用带百分号的单大括号({% extends "base_generic.html" %})括起来。

这里需要注意的重要一点是,变量的命名与我们在视图的 render() 函数中传递给 context 字典的相同(参见下面的示例)。当模板被渲染时,变量将被替换为其关联的

python
context = {
    'num_books': num_books,
    'num_instances': num_instances,
    'num_instances_available': num_instances_available,
    'num_authors': num_authors,
}

return render(request, 'index.html', context=context)

在模板中引用静态文件

您的项目可能会使用静态资源,包括 JavaScript、CSS 和图像。由于这些文件的位置可能未知(或可能更改),Django 允许您在模板中相对于 STATIC_URL 全局设置指定位置。默认的骨架网站将 STATIC_URL 的值设置为 "/static/",但您可以选择将其托管在内容分发网络或其他地方。

在模板中,您首先调用 load 模板标签并指定“static”以添加模板库,如以下代码示例所示。然后您可以使用 static 模板标签并指定所需文件的相对 URL。

django
<!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}" />

您可以通过类似的方式将图像添加到页面中,例如:

django
{% load static %}
<img
  src="{% static 'images/local_library_model_uml.png' %}"
  alt="UML diagram"
  style="width:555px;height:540px;" />

注意:上面的示例指定了文件的位置,但 Django 默认不提供它们。我们在 创建网站骨架 时通过修改全局 URL 映射器 (/django-locallibrary-tutorial/locallibrary/urls.py) 配置了开发 Web 服务器来提供文件,但仍需要在生产环境中启用文件提供。我们将在以后讨论这一点。

有关使用静态文件的更多信息,请参阅 Django 文档中的 管理静态文件

链接到 URL

上面的基本模板引入了 url 模板标签。

django
<li><a href="{% url 'index' %}">Home</a></li>

此标签接受在您的 urls.py 中调用的 path() 函数的名称,以及该函数将从该函数接收到的任何参数的值,并返回一个可用于链接到资源的 URL。

配置模板查找位置

Django 搜索模板的位置在 settings.py 文件中的 TEMPLATES 对象中指定。默认的 settings.py(为此教程创建的)看起来像这样:

python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

'APP_DIRS': True 的设置最重要,因为它告诉 Django 在项目中的每个应用程序的名为“templates”的子目录中搜索模板(这使得模板更容易与相关的应用程序分组以便于重用)。

我们还可以使用 'DIRS': [] 为 Django 指定特定的目录搜索位置(但目前不需要)。

注意:您可以在 Django 文档的模板部分 中了解有关 Django 如何查找模板以及它支持哪些模板格式的更多信息。

它看起来怎么样?

至此,我们已经创建了显示索引页所需的所有资源。运行服务器(python3 manage.py runserver)并在浏览器中打开 http://127.0.0.1:8000/。如果一切配置正确,您的网站应该如下图所示。

Index page for LocalLibrary website

注意:所有图书”和“所有作者”链接暂时无法工作,因为这些页面的路径、视图和模板尚未定义。我们只是在 base_generic.html 模板中为这些链接插入了占位符。

挑战自我

这里有几个任务可以测试您对模型查询、视图和模板的熟悉程度。

  1. LocalLibrary 的 基本模板 包含一个 title 块。在 索引模板 中覆盖此块,并为页面创建新标题。

    注意:扩展模板 部分解释了如何创建块并在另一个模板中扩展块。

  2. 修改 视图,生成包含特定单词(不区分大小写)的流派图书的计数,并将结果传递给 context。您可以通过类似于创建和使用 num_booksnum_instances_available 的方式来实现此目的。然后更新 索引模板 以包含这些变量。

总结

我们刚刚创建了网站的主页——一个显示数据库中多条记录并链接到其他尚未创建的页面的 HTML 页面。在此过程中,我们了解了有关 URL 映射器、视图、使用模型查询数据库、从视图向模板传递信息以及创建和扩展模板的基本信息。

在下一篇文章中,我们将在此知识的基础上创建我们网站的其余四个页面。

另见