构建附加页面
现在,我们已经建立了构建页面的常规方法,可以开始构建学习日志项目了。我们将创建两个显示数据的页面:一个页面列出所有主题,另一个页面显示特定主题的所有条目。我们将为每个页面指定 URL 模式、编写视图函数和模板。但在此之前,我们要先创建一个基础模板,项目中的所有模板都可以从它继承。
模板继承
在创建网站时,有些元素需要在每个页面上重复出现。与其将这些元素直接写入每个页面,不如编写一个包含重复元素的基础模板,然后让每个页面都从基础模板继承。这种方法可以让你专注于开发每个页面的独特之处,并使改变项目的整体外观和感觉变得更加容易。
父模板
我们将在与 index.xhtml 相同的目录下创建一个名为 base.xhtml 的模板。该文件将包含所有页面共有的元素;其他模板都将继承 base.xhtml。现在,我们要在每个页面上重复的唯一元素就是顶部的标题。因为我们将在每个页面上都包含这个模板,所以让标题成为指向主页的链接:
<p>
❶ <a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
❷ {% block content %}{% endblock content %}
该文件的第一部分创建一个包含项目名称的段落,该段落也充当主页链接。为了生成链接,我们使用模板标记,该标记由大括号和百分号 ({% %}
) 表示。 模板标签生成要在页面上显示的信息。 这里显示的模板标签 {% url 'learning_logs:index' %}
会生成一个与 learning_logs/urls.py 中定义的 URL 模式匹配的 URL,名称为 “index”❶。 在此示例中,learning_logs 是命名空间,index 是该命名空间中唯一命名的 URL 模式。 命名空间来自我们在 learning_logs/urls.py 文件中分配给 app_name 的值。
在简单的 HTML 页面中,链接被锚标记 <a> 包围:
<a href="link_url">link text</a>
让模板标签为我们生成 URL 可以让我们更轻松地保持链接的最新状态。我们只需更改 urls.py 中的 URL 模式,下次请求页面时,Django 就会自动插入更新后的 URL。我们项目中的每个页面都将继承自 base.xhtml,因此从现在起,每个页面都将有一个指向主页的链接。
在最后一行,我们插入了一对块标记❷。这个块名为 content,是一个占位符;子模板将定义内容块中的信息类型。
子模板不必定义父模板中的每一个块,因此你可以在父模板中为任意多个块预留空间;子模板只使用它所需要的块。
在 Python 代码中,我们缩进时几乎总是使用四个空格。与 Python 文件相比,模板文件往往有更多的嵌套层级,因此每个缩进层级通常只使用两个空格。 |
子模板
现在,我们需要重写 index.xhtml,使其继承于 base.xhtml。在 index.xhtml 中添加以下代码:
❶ {% extends 'learning_logs/base.xhtml' %}
❷ {% block content %}
<p>Learning Log helps you keep track of your learning, for
any topic you're
interested in.</p>
❸ {% endblock content %}
如果将其与原始 index.xhtml 进行比较,可以看到我们将学习日志标题替换为从父模板❶ 继承的代码。子模板的第一行必须有一个 {% extends %}
标记,以告诉 Django 从哪个父模板继承。文件 base.xhtml 是 learning_logs 的一部分,因此我们在父模板的路径中包含 learning_logs。这一行包含了 base.xhtml 模板中的所有内容,并允许 index.xhtml 定义内容块所保留的空间。
我们通过插入名称为 content ❷ 的 {% block %}
标记来定义内容块。所有不从父模板继承的内容都将放在内容块中。这里是描述学习日志项目的段落。我们使用 {% endblock content %}
标记❸来表示内容定义完毕。{% endblock %}
标签不需要名称,但如果一个模板包含多个区块,知道哪个区块结束会很有帮助。
主题页面
现在,我们有了一种高效的页面构建方法,可以专注于接下来的两个页面:常规主题页面和显示单个主题条目的页面。主题页面将显示用户创建的所有主题,这也是第一个涉及数据处理的页面。
主题 URL 模式
首先,我们定义主题页面的 URL。通常会选择一个简单的 URL 片段来反映页面上的信息类型。我们将使用 topics 这个词,因此 URL http://localhost:8000/topics/ 将返回该页面。下面是我们如何修改 learning_logs/urls.py:
"""Defines URL patterns for learning_logs."""
--snip--
urlpatterns = [
# Home page
path('', views.index, name='index'),
# Page that shows all topics.
path('topics/', views.topics, name='topics'),
]
新的 URL 模式是 topics,后面跟一个斜线。当 Django 检查请求的 URL 时,该模式将匹配任何基本 URL 后跟 topics 的 URL。您可以在末尾包含或省略正斜线,但 topics 后面不能有任何其他内容,否则模式将无法匹配。任何 URL 与此模式匹配的请求都将传递给 views.py 中的 topics()
函数。
主题视图
topics()
函数需要从数据库中获取一些数据并将其发送到模板。在 views.py 中添加以下内容:
from django.shortcuts import render
from .models import Topic # 1
def index(request):
--snip--
def topics(request): # 2
"""Show all topics."""
topics = Topic.objects.order_by('date_added') # 3
context = {'topics': topics} # 4
return render(request, 'learning_logs/topics.xhtml', context) # 5
我们首先导入与所需数据相关的模型❶。topics() 函数需要一个参数:Django 从服务器接收到的请求对象❷。我们通过请求 Topic 对象来查询数据库,并按照 date_added 属性❸ 排序。我们将得到的查询集分配给主题。
然后,我们定义一个上下文,并将其发送给模板❹。上下文是一个字典,其中的键是我们在模板中用来访问所需数据的名称,而值则是我们需要发送到模板的数据。在本例中,有一个键值对,其中包含我们要在页面上显示的主题集。在创建使用数据的页面时,我们使用请求对象、要使用的模板和上下文字典❺调用 render()。
主题模板
主题页面的模板会接收上下文字典,因此模板可以使用 topics() 提供的数据。在 index.xhtml 的同一目录下创建一个名为 topics.xhtml 的文件。下面是在模板中显示主题的方法:
{% extends 'learning_logs/base.xhtml' %}
{% block content %}
<p>Topics</p>
❶ <ul>
❷ {% for topic in topics %}
❸ <li>{{ topic.text }}</li>
❹ {% empty %}
<li>No topics have been added yet.</li>
❺ {% endfor %}
❻ </ul>
{% endblock content %}
我们使用 {% extends %}
标签继承自 base.xhtml,就像我们在主页上所做的那样,然后我们打开一个内容块。 此页面的正文包含已输入主题的项目符号列表。在标准 HTML 中,项目符号列表称为无序列表,由标签 <ul></ul> 指示。 开始标记 <ul> 开始主题项目符号列表❶。
接下来我们使用一个相当于 for 循环的模板标签,它循环上下文字典中的列表主题❷。 模板中使用的代码在一些重要方面与 Python 不同。 Python 使用缩进来指示 for 语句的哪些行是循环的一部分。在模板中,每个 for 循环都需要一个显式的 {% endfor %}
标记来指示循环结束的位置。 因此,在模板中,您将看到这样编写的循环:
{% for item in list %}
do something with each item
{% endfor %}
在循环内,我们希望将每个主题变成项目符号列表中的一个项目。 要打印模板中的变量,请将变量名称括在双括号中。大括号不会出现在页面上; 它们只是向 Django 表明我们正在使用模板变量。因此,代码 {{ topic.text }}
❸ 将在每次循环时被当前主题的 text 属性的值替换。 HTML 标签 <li></li> 表示列表项。这些标签之间、一对 <ul></ul> 标签内的任何内容都将在列表中显示为项目符号项目。
我们还使用 {%empty%}
模板标签❹,它告诉 Django 如果列表中没有项目该怎么办。 在本例中,我们打印一条消息,通知用户尚未添加任何主题。 最后两行结束了 for 循环❺,然后结束了项目符号列表❻。
现在我们需要修改基本模板以包含主题页面的链接。将以下代码添加到base.xhtml:
<p>
❶ <a href="{% url 'learning_logs:index' %}">Learning Log</a>
-
❷ <a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
{% block content %}{% endblock content %}
我们在主页链接后添加破折号 ❶,然后再次使用 {% url %}
模板标签添加指向主题页面的链接 ❷。这一行告诉 Django 在 learning_logs/urls.py 中生成一个与 URL 模式相匹配的链接,名称为 "topics"。
现在,当你在浏览器中刷新主页时,就会看到一个主题链接。点击链接后,您将看到与图 18-4 类似的页面。

单独主题页面
接下来,我们需要创建一个页面,专注于一个主题,显示主题名称和该主题的所有条目。我们将定义一个新的 URL 模式,编写一个视图,并创建一个模板。我们还将修改主题页面,使列表中的每个条目都能链接到相应的主题页面。
主题 URL 模式
主题页面的 URL 模式与之前的 URL 模式有些不同,因为它会使用主题的 id 属性来表示请求的是哪个主题。例如,如果用户想查看国际象棋主题的详细页面(id 为 1),URL 将是 http://localhost:8000/topics/1/ 。以下是与该 URL 匹配的模式,应将其放入 learning_logs/urls.py :
--snip--
urlpatterns = [
--snip--
# Detail page for a single topic.
path('topics/<int:topic_id>/', views.topic, name='topic'),
]
让我们检查一下该 URL 模式中的字符串 "topics/<int:topic_id>/"。字符串的第一部分告诉 Django 查找在基本 URL 后带有 topics 字样的 URL。字符串的第二部分,/<int:topic_id>/,匹配两个正斜线之间的整数,并将整数值赋值给名为 topic_id 的参数。
当 Django 找到与此模式匹配的 URL 时,它会调用视图函数 topic(),并将分配给 topic_id 的值作为参数。我们将使用 topic_id 的值在函数中获取正确的主题。
主题视图
topic()
函数需要从数据库中获取主题和所有相关条目,就像我们之前在 Django shell 中做的那样:
--snip--
❶ def topic(request, topic_id):
"""Show a single topic and all its entries."""
❷ topic = Topic.objects.get(id=topic_id)
❸ entries = topic.entry_set.order_by('-date_added')
❹ context = {'topic': topic, 'entries': entries}
❺ return render(request, 'learning_logs/topic.xhtml', context)
这是第一个需要请求对象以外的参数的视图函数。该函数接受表达式 /<int:topic_id>/ 捕捉到的值,并将其赋值给 topic_id ❶。然后,我们使用 get() 获取主题,就像在 Django shell ❷ 中做的那样。接下来,我们获取与该主题相关的所有条目,并根据添加日期❸ 对它们进行排序。日期_added 前面的减号会以相反的顺序排列结果,即先显示最近的条目。我们将主题和条目存储在上下文字典❹ 中,然后使用请求对象、topic.xhtml 模板和上下文字典❺ 调用 render()。
❷和❸处的代码短语称为查询,因为它们查询数据库的特定信息。当您在自己的项目中编写类似的查询时,先在 Django shell 中试用一下会很有帮助。与编写视图和模板然后在浏览器中检查结果相比,在 shell 中得到的反馈要快得多。 |
主题模板
模板需要显示主题名称和条目。如果该主题还没有条目,我们还需要通知用户。
{% extends 'learning_logs/base.xhtml' %}
{% block content %}
❶ <p>Topic: {{ topic.text }}</p>
<p>Entries:</p>
❷ <ul>
❸ {% for entry in entries %}
<li>
❹ <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
❺ <p>{{ entry.text|linebreaks }}</p>
</li>
❻ {% empty %}
<li>There are no entries for this topic yet.</li>
{% endfor %}
</ul>
{% endblock content %}
我们扩展了 base.xhtml,项目中的所有页面都将这样做。接下来,我们显示被请求的主题的文本属性❶。变量 topic 已包含在上下文字典中,因此是可用的。然后,我们启动一个项目符号列表❷ 来显示每个条目,并循环浏览这些条目❸,就像前面处理主题一样。
每个项目都列出了两条信息:时间戳和每个条目的全文。对于时间戳 ❹,我们显示属性 date_added 的值。在 Django 模板中,垂直线 (|) 代表模板过滤器—一种在呈现过程中修改模板变量值的函数。过滤器 date:'M d, Y H:i' 显示的时间戳格式为 2022 年 1 月 1 日 23:00。下一行显示当前条目的文本属性值。过滤器 linebreaks ❺ 确保长文本条目包含浏览器可理解格式的换行符,而不是显示不间断的文本块。我们再次使用 {% empty %} 模板标签❻ 打印一条信息,告知用户没有输入条目。
主题页面的链接
在浏览器中查看主题页面之前,我们需要修改 topics 模板,以便每个主题都能链接到相应的页面。以下是需要对 topics.xhtml 进行的修改:
--snip--
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">
{{ topic.text }}</a></li>
</li>
{% empty %}
--snip--
我们根据 learning_logs 中名称为 "topic" 的 URL 模式,使用 URL 模板标签生成适当的链接。这种 URL 模式需要一个 topic_id 参数,因此我们在 URL 模板标签中添加了 topic.id 属性。现在,主题列表中的每个主题都是指向主题页面的链接,例如 http://localhost:8000/topics/1/ 。
当你刷新主题页面并点击一个主题时,应该会看到一个类似图 18-5 的页面。
topic.id 与 topic_id 之间有一个微妙但重要的区别。表达式 topic.id 检查一个主题,并检索相应 ID 的值。变量 topic_id 是代码中对该 ID 的引用。如果您在处理 ID 时遇到错误,请确保您以适当的方式使用了这些表达式。 |

总结
在本章中,您将学习如何使用 Django 框架开始构建一个简单的网络应用程序。您看到了简要的项目说明,将 Django 安装到虚拟环境中,设置了一个项目,并检查了项目设置是否正确。建立应用程序并定义模型来表示应用程序的数据。您了解了数据库以及 Django 如何在您更改模型后帮助您迁移数据库。您为管理站点创建了超级用户,并使用管理站点输入了一些初始数据。
您还探索了 Django shell,它允许您在终端会话中处理项目数据。您还学习了如何定义 URL、创建视图函数和编写模板来制作网站页面。你还使用了模板继承来简化单个模板的结构,使网站更容易随着项目的发展而修改。
在第 19 章中,你将制作直观、用户友好的页面,让用户无需通过管理员站点就能添加新主题和条目以及编辑现有条目。你还将添加一个用户注册系统,允许用户创建一个账户并建立自己的学习日志。这是网络应用程序的核心—能够创建任何数量的用户都能与之互动的东西。