目录
制定创建网页的流程后,可以开始扩充“学习笔记”项目了。我们将创建两个显示数据的网 页,其中一个列出所有的主题,另一个显示特定主题的所有条目。对于每个网页,我们都将指定 URL模式,编写一个视图函数,并编写一个模板。但这样做之前,我们先创建一个父模板,项目 中的其他模板都将继承它。
创建网站时,几乎都有一些所有网页都将包含的元素。在这种情况下,可编写一个包含通用 元素的父模板,并让每个网页都继承这个模板,而不必在每个网页中重复定义这些通用元素。这 种方法能让你专注于开发每个网页的独特方面,还能让修改项目的整体外观容易得多。
1. 父模板
我们首先来创建一个名为base.html的模板,并将其存储在index.html所在的目录中。这个文件 包含所有页面都有的元素;其他的模板都继承base.html。当前,所有页面都包含的元素只有顶端 的标题。我们将在每个页面中包含这个模板,因此我们将这个标题设置为到主页的链接:
base.html
<p>
1 <a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
2 {% block content %}{% endblock content %}
这个文件的第一部分创建一个包含项目名的段落,该段落也是一个到主页的链接。为创建链 接,我们使用了一个模板标签,它是用大括号和百分号({% %})表示的。模板标签是一小段代 码,生成要在网页中显示的信息。在这个实例中,模板标签{% url 'learning_logs:index' %}生成一个URL,该URL与learning_logs/urls.py中定义的名为index的URL模式匹配(见?)。在这 个示例中,learning_logs是一个命名空间,而index是该命名空间中一个名称独特的URL模式。 在简单的HTML页面中,链接是使用锚标签定义的:
<a href="link_url">link text</a>
让模板标签来生成URL,可让链接保持最新容易得多。要修改项目中的URL,只需修改urls.py 中的URL模式,这样网页被请求时,Django将自动插入修改后的URL。在我们的项目中,每个网 页都将继承base.html,因此从现在开始,每个网页都包含到主页的链接。
在2处,我们插入了一对块标签。这个块名为content,是一个占位符,其中包含的信息将 由子模板指定。
子模板并非必须定义父模板中的每个块,因此在父模板中,可使用任意多个块来预留空间, 而子模板可根据需要定义相应数量的块。
在Python代码中,我们几乎总是缩进四个空格。相比于Python文件,模板文件的缩进层级 更多,因此每个层级通常只缩进两个空格。
2. 子模板
现在需要重新编写index.html,使其继承base.html,如下所示:
index.html
1 {% extends "learning_logs/base.html" %}
2 {% block content %}
<p>Learning Log helps you keep track of your learning, for any topic you're
learning about.</p>
3 {% endblock content %}
如果将这些代码与原来的index.html进行比较,可发现我们将标题Learning Log替换成了从父 模板那里继承的代码(见1)。子模板的第一行必须包含标签{% extends %},让Django知道它继 承了哪个父模板。文件base.html位于文件夹learning_logs中,因此父模板路径中包含learning_logs。 这行代码导入模板base.html的所有内容,让index.html能够指定要在content块预留的空间中添加 的内容。
在2处,我们插入了一个名为content的{% block %}标签,以定义content块。不是从父模板 继承的内容都包含在content块中,在这里是一个描述项目“学习笔记”的段落。在?处,我们 使用标签{% endblock content %}指出了内容定义的结束位置。
模板继承的优点开始显现出来了:在子模板中,只需包含当前网页特有的内容。这不仅简化 了每个模板,还使得网站修改起来容易得多。要修改很多网页都包含的元素,只需在父模板中修 改该元素,你所做的修改将传导到继承该父模板的每个页面。在包含数十乃至数百个网页的项目中,这种结构使得网站改进起来容易而且快捷得多。
在大型项目中,通常有一个用于整个网站的父模板——base.html,且网站的每个主要部 分都有一个父模板。每个部分的父模板都继承base.html,而网站的每个网页都继承相应 部分的父模板。这让你能够轻松地修改整个网站的外观、网站任何一部分的外观以及任 何一个网页的外观。这种配置提供了一种效率极高的工作方式,让你乐意不断地去改进 网站。
有了高效的网页创建方法,就能专注于另外两个网页了:显示全部主题的网页以及显示特定 主题中条目的网页。所有主题页面显示用户创建的所有主题,它是第一个需要使用数据的网页。
1. URL模式
首先,我们来定义显示所有主题的页面的URL。通常,使用一个简单的URL片段来指出网页 显示的信息;我们将使用单词topics,因此URL http://localhost:8000/topics/将返回显示所有主题的 页面。下面演示了该如何修改learning_logs/urls.py:
urls.py
"""为learning_logs定义URL模式"""
--snip--
urlpatterns = [
# 主页
url(r'^$', views.index, name='index'),
# 显示所有的主题
? url(r'^topics/$', views.topics, name='topics'),
]
我们只是在用于主页URL的正则表达式中添加了topics/(见?)。Django检查请求的URL时, 这个模式与这样的URL匹配:基础URL后面跟着topics。可以在末尾包含斜杠,也可以省略它, 但单词topics后面不能有任何东西,否则就与该模式不匹配。其URL与该模式匹配的请求都将交 给views.py中的函数topics()进行处理。
2. 视图
函数topics()需要从数据库中获取一些数据,并将其发送给模板。我们需要在views.py中添 加的代码如下:
views.py
from django.shortcuts import render
1 from .models import Topic
def index(request):
--snip--
2 def topics(request):
"""显示所有的主题"""
3 topics = Topic.objects.order_by('date_added')
4 context = {'topics': topics}
5 return render(request, 'learning_logs/topics.html', context)
我们首先导入了与所需数据相关联的模型(见1)。函数topics()包含一个形参:Django从服 务器那里收到的request对象(见2)。在3处,我们查询数据库——请求提供Topic对象,并按属 性date_added对它们进行排序。我们将返回的查询集存储在topics中。
在4处,我们定义了一个将要发送给模板的上下文。上下文是一个字典,其中的键是我们将 在模板中用来访问数据的名称,而值是我们要发送给模板的数据。在这里,只有一个键—值对, 它包含我们将在网页中显示的一组主题。创建使用数据的网页时,除对象request和模板的路径 外,我们还将变量context传递给render()(见5)。
3. 模板
显示所有主题的页面的模板接受字典context,以便能够使用topics()提供的数据。请创建一 个文件,将其命名为topics.html,并存储到index.html所在的目录中。下面演示了如何在这个模板 中显示主题:
topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
1 <ul>
2 {% for topic in topics %}
3 <li>{{ topic }}</li>
4 {% empty %}
<li>No topics have been added yet.</li>
5 {% endfor %}
6 </ul>
{% endblock content %}
就像模板index.html一样,我们首先使用标签{% extends %}来继承base.html,再开始定义 content块。这个网页的主体是一个项目列表,其中列出了用户输入的主题。在标准HTML中,项 目列表被称为无序列表,用标签
表示。包含所有主题的项目列表始于1处。 在2处,我们使用了一个相当于for循环的模板标签,它遍历字典context中的列表topics。 模板中使用的代码与Python代码存在一些重要差别:Python使用缩进来指出哪些代码行是for循环 的组成部分,而在模板中,每个for循环都必须使用{% endfor %}标签来显式地指出其结束位置。因此在模板中,循环类似于下面这样:
{% for item in list %}
do something with each item
{% endfor %}
<p>
1 <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
2 <a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
{% block content %}{% endblock content %}
我们在到主页的链接后面添加了一个连字符(见1),然后添加了一个到显示所有主题的页 面的链接——使用的也是模板标签url(见2)。这一行让Django生成一个链接,它与learning_logs/ urls.py中名为topics的URL模式匹配。
现在如果你刷新浏览器中的主页,将看到链接Topics。单击这个链接,将看到类似于图18-4 所示的网页.
接下来,我们需要创建一个专注于特定主题的页面——显示该主题的名称及该主题的所有条 目。同样,我们将定义一个新的URL模式,编写一个视图并创建一个模板。我们还将修改显示所 有主题的网页,让每个项目列表项都是一个链接,单击它将显示相应主题的所有条目。
1. URL模式
显示特定主题的页面的URL模式与前面的所有URL模式都稍有不同,因为它将使用主题的id 属性来指出请求的是哪个主题。例如,如果用户要查看主题Chess(其id为1)的详细页面,URL 将为http://localhost:8000/topics/1/。下面是与这个URL匹配的模式,它包含在learning_logs/urls.py中:
urls.py
--snip--
urlpatterns = [
--snip--
# 特定主题的详细页面
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
]
我们来详细研究这个URL模式中的正则表达式——r'^topics/(?P\d+)/$'。r让 Django将这个字符串视为原始字符串,并指出正则表达式包含在引号内。这个表达式的第二部分 (/(?P\d+)/)与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为topic_id 的实参中。这部分表达式两边的括号捕获URL中的值;?P将匹配的值存储到topic_id 中;而表达式\d+与包含在两个斜杆内的任何数字都匹配,不管这个数字为多少位。 发现URL与这个模式匹配时,Django将调用视图函数topic(),并将存储在topic_id中的值作 为实参传递给它。在这个函数中,我们将使用topic_id的值来获取相应的主题。
2. 视图
函数topic()需要从数据库中获取指定的主题以及与之相关联的所有条目,如下所示:
views.py
--snip--
1 def topic(request, topic_id):
"""显示单个主题及其所有的条目"""
2 topic = Topic.objects.get(id=topic_id)
3 entries = topic.entry_set.order_by('-date_added')
4 context = {'topic': topic, 'entries': entries}
5 return render(request, 'learning_logs/topic.html', context)