Django 教程第 5 部分:创建我们的主页
现在,我们准备添加代码来显示第一个完整的页面 - LocalLibrary 网站的首页。首页将显示我们拥有的每种模型类型的记录数量,并提供指向其他页面的侧边栏导航链接。在此过程中,我们将获得编写基本 URL 映射和视图、从数据库获取记录以及使用模板的实践经验。
先决条件 | 阅读 Django 简介。完成之前的教程主题(包括 Django 教程第 4 部分: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 模块以进行进一步处理。
我们还创建了一个名为 /catalog/urls.py 的 URLConf 模块的占位符文件。将以下行添加到该文件中
urlpatterns = [
path('', views.index, name='index'),
]
path()
函数定义了以下内容
- URL 模式,它是一个空字符串:
''
。在处理其他视图时,我们将详细讨论 URL 模式。 - 如果检测到 URL 模式,将调用的视图函数:
views.index
,这是 views.py 文件中名为index()
的函数。
path()
函数还指定了一个 name
参数,它是此特定 URL 映射的唯一标识符。您可以使用该名称“反转”映射器,即动态创建指向映射器旨在处理的资源的 URL。例如,我们可以使用名称参数通过在模板中添加以下链接来从任何其他页面链接到我们的主页
<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()
属性来获取记录数量。它还获取了一个 BookInstance
对象的列表,这些对象在 status 字段中具有 'a'(可用)的值。您可以在我们之前的教程 Django 教程第 3 部分:使用模型 > 搜索记录 中找到有关如何访问模型数据的更多信息。
在视图函数的末尾,我们调用 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/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
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 'catalog/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 文档)