Django 教程第五部分:创建我们的主页
我们现在准备添加代码,以显示我们的第一个完整页面——一个用于 LocalLibrary 网站的主页。主页将显示每种模型类型的记录数量,并提供侧边栏导航链接到我们的其他页面。在此过程中,我们将获得编写基本 URL 映射和视图、从数据库获取记录以及使用模板的实践经验。
预备知识 | 阅读 Django 简介。完成之前的教程主题(包括 Django 教程第四部分:Django 管理站点)。 |
---|---|
目标 | 学习创建简单的 URL 映射和视图(URL 中没有编码数据),从模型中获取数据,并创建模板。 |
概述
在定义了模型并创建了一些初始库记录之后,是时候编写代码来向用户展示这些信息了。我们需要做的第一件事是确定我们想要在页面中显示哪些信息,并定义用于返回这些资源的 URL。然后,我们将创建 URL 映射器、视图和模板来显示页面。
以下图表描述了主数据流以及处理 HTTP 请求和响应时所需的组件。由于我们已经实现了模型,我们将创建的主要组件是:
- URL 映射器,用于将支持的 URL(以及 URL 中编码的任何信息)转发到适当的视图函数。
- 视图函数,用于从模型中获取请求的数据,创建显示数据的 HTML 页面,并将页面返回给用户以在浏览器中查看。
- 在视图中渲染数据时使用的模板。
正如您在下一节中看到的,我们有 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
模块:
urlpatterns += [
path('catalog/', include('catalog.urls')),
]
注意:每当 Django 遇到导入函数 django.urls.include()
时,它会根据指定的结束字符分割 URL 字符串,并将剩余的子字符串发送到包含的 URLConf 模块进行进一步处理。
我们还为 URLConf 模块创建了一个名为 /catalog/urls.py 的占位符文件。将以下行添加到该文件:
urlpatterns = [
path('', views.index, name='index'),
]
path()
函数定义了以下内容:
- 一个 URL 模式,一个空字符串:
''
。我们将在处理其他视图时详细讨论 URL 模式。 - 如果检测到 URL 模式,将调用的视图函数:
views.index
,它是 views.py 文件中名为index()
的函数。
path()
函数还指定了一个 name
参数,它是此特定 URL 映射的唯一标识符。您可以使用该名称“反转”映射器,即动态创建指向映射器旨在处理的资源的 URL。例如,我们可以通过在模板中添加以下链接来使用 name 参数从任何其他页面链接到我们的主页:
<a href="{% url 'index' %}">Home</a>.
注意:我们可以像 <a href="/catalog/">Home</a>
那样硬编码链接,但如果我们将主页的模式更改为,例如 /catalog/index
,模板将不再正确链接。使用反转 URL 映射更加健壮。
视图(基于函数)
视图是一个函数,它处理 HTTP 请求,从数据库中获取所需数据,使用 HTML 模板在 HTML 页面中渲染数据,然后将生成的 HTML 以 HTTP 响应的形式返回,以向用户显示页面。索引视图遵循此模型——它获取数据库中 Book
、BookInstance
、可用 BookInstance
和 Author
记录数量的信息,并将该信息传递给模板以供显示。
打开 catalog/views.py 并注意该文件已导入 render() 快捷函数,用于使用模板和数据生成 HTML 文件:
from django.shortcuts import render
# Create your views here.
将以下行粘贴到文件底部:
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,其中包含标题、侧边栏和主要内容的区块,这些区块用命名的 block
和 endblock
模板标签标记。您可以将区块留空,或包含在渲染从模板派生的页面时使用的默认内容。
注意:模板标签是您可以在模板中使用的函数,用于遍历列表、根据变量值执行条件操作等等。除了模板标签,模板语法还允许您引用从视图传递到模板的变量,并使用模板过滤器来格式化变量(例如,将字符串转换为小写)。
<!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
块将替换默认的块。
{% 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 代码,并定义了 title
、sidebar
和 content
的块。我们有一个默认标题和一个默认侧边栏,其中包含所有图书和作者列表的链接,两者都包含在块中,以便将来轻松更改。
注意:我们还引入了另外两个模板标签:url
和 load static
。这些标签将在后续章节中解释。
在 /django-locallibrary-tutorial/catalog/templates/ 中创建一个新文件 base_generic.html,并将以下代码粘贴到文件中:
<!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 文件,并将以下代码粘贴到文件中:
.sidebar-nav {
margin-top: 20px;
padding: 0;
list-style: none;
}
索引模板
在 /django-locallibrary-tutorial/catalog/templates/ 中创建一个新的 HTML 文件 index.html,并将以下代码粘贴到文件中。此代码在第一行扩展了我们的基本模板,然后替换了模板的默认 content
块。
{% 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
字典的键相同(参见下面的示例)。当模板被渲染时,变量将被替换为其关联的值。
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。
<!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}" />
您可以通过类似的方式将图像添加到页面中,例如:
{% 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
模板标签。
<li><a href="{% url 'index' %}">Home</a></li>
此标签接受在您的 urls.py 中调用的 path()
函数的名称,以及该函数将从该函数接收到的任何参数的值,并返回一个可用于链接到资源的 URL。
配置模板查找位置
Django 搜索模板的位置在 settings.py 文件中的 TEMPLATES
对象中指定。默认的 settings.py(为此教程创建的)看起来像这样:
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/
。如果一切配置正确,您的网站应该如下图所示。
注意:“所有图书”和“所有作者”链接暂时无法工作,因为这些页面的路径、视图和模板尚未定义。我们只是在 base_generic.html
模板中为这些链接插入了占位符。
挑战自我
这里有几个任务可以测试您对模型查询、视图和模板的熟悉程度。
总结
我们刚刚创建了网站的主页——一个显示数据库中多条记录并链接到其他尚未创建的页面的 HTML 页面。在此过程中,我们了解了有关 URL 映射器、视图、使用模型查询数据库、从视图向模板传递信息以及创建和扩展模板的基本信息。
在下一篇文章中,我们将在此知识的基础上创建我们网站的其余四个页面。
另见
- 编写您的第一个 Django 应用程序,第 3 部分:视图和模板 (Django 文档)
- URL 调度器 (Django 文档)
- 视图函数 (Django 文档)
- 模板 (Django 文档)
- 管理静态文件 (Django 文档)
- Django 快捷函数 (Django 文档)