从Django入手
Django是一个Web框架——一套用于帮助开发交互式网站的工具
- Django能够响应网页请求,还能让你更轻松地读写数据库、管理用户等
1. 建立项目
(1) 制定规范
- 我们要编写一个名为“学习笔记”的 Web 应用程序,让用户能够记录感兴趣的主题,并在学习每个主题的过程中添加日志条目
- “学习笔记”的主页对这个网站进行描述,并邀请用户注册或登录
- 用户登录后,就可创建新主题、添加新条目以及阅读既有的条目
(2) 建立虚拟环境
虚拟环境是系统的一个位置,你可以在其中安装包,并将其与其他 Python 包隔离
python3 -m venv <virtual_environment_name>
(3) 激活虚拟环境
激活后环境名将包含在括号内,此时你可以在环境中安装包,并使用已安装的包
source <virtual_environment_dir>/bin/activate
- 注意:Windows 使用
<virtual_environment_dir>\Scripts\activate
(不包含source)激活
停用虚拟环境
deactivate
- 如果关闭运行虚拟环境的终端,虚拟环境也将不再处于活动状态
(4) 安装 Django
创建并激活虚拟环境后,就可安装 Django 了
pip install Django
(5) 创建项目
django-admin startproject <project_name> .
- 末尾的句点让新项目使用合适的目录结构,这样开发完成后可轻松地将应用程序部署到服务器
- 会创建目录 <project_name> 和 manage.py 文件
- 项目目录包含4个文件,其中最重要的是 settings.py、urls.py 和 wsgi.py
- settings.py 指定 Django 如何与你的系统交互以及如何管理项目,在开发项目的过程中,我们将修改其中一些设置,并添加一些设置
- urls.py 告诉 Django 应创建哪些网页来响应浏览器请求
- wsgi.py(web server gateway interface/ Web服务器网关接口)帮助 Django 提供它创建的文件
- manage.py 程序接受命令并将其交给 Django 的相关部分去运行,我们将使用这些命令来管理诸如使用数据库和运行服务器等任务
- 项目目录包含4个文件,其中最重要的是 settings.py、urls.py 和 wsgi.py
(6) 创建数据库
- 我们将修改数据库称为迁移数据库
- 首次执行命令 migrate 时,将让Django确保数据库与项目的当前状态匹配
- 在使用 SQLite 的新项目中首次执行这个命令时,Django将新建一个数据库
- 还创建了一个文件——db.sqlite3
- SQLite是一种使用单个文件的数据库,是编写简单应用程序的理想选择,因为它让你不用太关注数据库管理的问题
python manage.py migrate
(7) 查看项目
- 命令 runserver 启动 development server 服务器,让你能够查看系统中的项目,了解它们的工作情况
- 当你在浏览器中输入URL(此处为 http://localhost:8000/ 或 http://127.0.0.1:8000/)以请求网页时,该 Django 服务器将进行响应:生成合适的网页,并将其发送给浏览器
python manage.py runserver
2. 创建应用程序
Django 项目由一系列应用程序组成,它们协同工作,让项目成为一个整体
(1) 建立基础设施
再打开一个终端窗口(或标签页),并切换到 manage.py 所在的目录,激活该虚拟环境,再执行命令 startapp,该命令让 Django 建立创建应用程序所需的基础设施
- 其中最重要的文件是 models.py、admin.py 和 views.py
- 我们将使用 models.py 来定义我们要在应用程序中管理的数据
python manage.py startapp <app_name>
(2) 定义模型
1) 涉及的数据
- 每位用户都需要在学习笔记中创建很多主题
- 用户输入的每个条目都与特定主题相关联,这些条目将以文本的方式显示
- 我们还需要存储每个条目的时间戳,以便能够告诉用户各个条目都是什么时候创建的
2) models.py
a. 初始内容
为我们导入了模块 models,还让我们创建自己的模型
- 模型告诉 Django 如何处理应用程序中存储的数据
- 在代码层面,模型就是一个类,包含属性和方法
from django.db import models
# Create your models here.
b. 存储主题的模型
from django.db import models
# 创建一个名为 Topic 的类,它继承了 Model(Django 中一个定义了模型基本功能的类)
class Topic(models.Model):
"""用户学习的主题"""
# 属性 text 是一个 CharField —— 由字符或文本组成的数据
# - 需要存储少量的文本,如名称、标题或城市时,可使用 CharField
# - 定义 CharField 属性时,必须告诉 Django 该在数据库中预留多少空间(这里为存储主题名将 max_length 设置成了200字符)
text = models.CharField(max_length=200)
# 属性 date_added 是一个 DateTimeField —— 记录日期和时间的数据
# - 这里传了实参 auto_now_add=True,表示每当用户创建新主题时,这都让 Django 将这个属性自动设置成当前日期和时间)
date_added = models.DateTimeField(auto_now_add=True)
# Django 调用方法 __str__() 来显示模型的简单表示
# - 我们需要告诉 Django,默认应使用哪个属性来显示有关主题的信息,所以在这里,我们编写了方法 __str__(),它返回存储在属性 text 中的字符串
# - 若为 Python 2.7,应调用方法 __unicode__(),而不是 __str__() ,但其中的代码相同
def __str__(self):
"""返回模型的字符串表示"""
return self.text
要获悉可在模型中使用的各种字段,请参阅 Django Model FieldReference(Django模型字段参考)
(3) 激活模型
要使用模型,必须让 Django 将应用程序包含到项目中
1) 添加应用程序
目录 learning_log/learning_log 下 settings.py 文件中的“INSTALLED_APPS”片段告诉 Django 哪些应用程序被安装到了项目中并将协同工作,我们将前面的应用程序添加到这个列表中
- 通过将应用程序编组,在项目不断增大,包含更多的应用程序时,有助于对应用程序进行跟踪
--snip--
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# My apps
'learning_logs'
]
--snip--
2) 修改数据库
命令 makemigrations 让 Django 确定该如何修改数据库,使其能够存储与我们定义的新模型(Topic)相关联的数据
python manage.py makemigrations <app_name>
3) 应用迁移
让Django替我们修改数据库
python manage.py migrate
更改数据的步骤
每当需要修改“学习笔记”管理的数据时,都采取如下三个步骤:
- 修改 models.py
- 对 learning_logs 调用 makemigrations
python manage.py makemigrations <app_name>
- 让 Django 迁移项目
python manage.py migrate
(4) 管理网站
Django提供的管理网站(admin site)让你能够轻松地处理模型
- 网站的管理员可使用管理网站,但普通用户不能使用
1) 创建超级用户
Django允许你创建具备所有权限的用户——超级用户
python manage.py createsuperuser
2) 向管理网站注册模型
Django 自动在管理网站中添加了一些模型,如 User 和 Group,但对于我们创建的模型,必须手工进行注册
- 我的理解:要向管理网站手工注册是因为网站所有者通常需要访问网站存储的所有信息
admin.py
from django.contrib import admin
# 导入我们要注册的模型
from learning_logs.models import Topic
# 让 Django 通过管理网站管理我们的模型
admin.site.register(Topic)
使用超级用户账户访问管理网站
- 这个网页让你能够添加和修改用户和用户组,还可以管理与刚才定义的模型Topic相关的数据
3) 添加主题
按如下步骤添加主题 Chess 和 Rock Climbing
- 单击 Topics 进入主题网页
- 单击 Add,你将看到一个用于添加新主题的表单
- 在第一个方框中输入主题,再单击Save,这将返回到主题管理页面,其中包含刚创建的主题
(5) 定义模型 Entry
每个条目都与特定主题相关联,这种关系被称为多对一关系,即多个条目可关联到同一个主题
models.py
class Topic(models.Model):
--snip--
# 像 Topic 一样,Entry 也继承了 Django 基类 Model
class Entry(models.Model):
"""学到的有关某个主题的具体知识"""
# 属性 topic 是一个 ForeignKey 实例
# - 外键是一个数据库术语,它引用了数据库中的另一条记录,这里是将每个条目关联到特定主题
# - 每个主题创建时,都给它分配了一个键(或ID)
# - 需要在两项数据之间建立联系时,Django 使用与每项信息相关联的键
# - 稍后我们将根据这些联系获取与特定主题相关联的所有条目
# 实参 on_delete=models.CASCADE 让 Django 在删除主题的同时删除所有与之相关联的条目,这称为级联删除
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
# 属性 text 是一个 TextField 实例
# - 这种字段不需要长度限制,因为我们不想限制条目的长度
text = models.TextField()
# 属性 date_added 让我们能够按创建顺序呈现条目,并在每个条目旁边放置时间戳
date_added = models.DateTimeField(auto_now_add=True)
# 在 Entry 类中嵌套了 Meta 类,Meta 存储用于管理模型的额外信息
# - 在这里,它让我们能够设置一个特殊属性,让 Django 在需要时使用 Entries 来表示多个条目
# - 如果没有这个类, Django 将使用 Entrys 来表示多个条目
class Meta:
verbose_name_plural = 'entries'
# 方法 __str__() 告诉 Django,呈现条目时应显示哪些信息
def __str__(self):
"""返回模型的字符串表示"""
if len(self.text) <= 50:
return self.text
else:
# 由于条目包含的文本可能很长,我们让 Django 只显示 text 的前50个字符和1个省略号,指出显示的并非整个条目
return f"{self.text[:50]}..."
(6) 迁移模型 Entry
由于我们添加了一个新模型,因此需要再次迁移数据库
(7) 向管理网站注册 Entry
更新 admin.py
from django.contrib import admin
# 将我们要注册的模型 Entry 也导入
from learning_logs.models import Topic, Entry
admin.site.register(Topic)
# 让 Django 通过管理网站也管理我们的模型 Entry
admin.site.register(Entry)
再次访问管理网站,你将看到 learning_logs 下列出了 Entries
添加条目
按如下步骤创建两个国际象棋和一个攀岩条目
- 单击 Entries 的 Add 链接,或者单击 Entries 再选择 Add entry
- 从下拉列表中选择主题,并添加一个条目
(8) Django shell
- 输入一些数据后,就可通过交互式终端会话以编程方式查看这些数据了,这种交互式环境称为 Django shell,是测试项目和排除其故障的理想之地
- 编写用户可请求的网页时,我们将使用这种语法
- 确认代码能获取所需的数据时,shell 很有帮助
- 如果代码在 shell 中的行为符合预期,那么它们在项目文件中也能正确地工作
- 如果代码引发了错误或获取的数据不符合预期,那么在简单的 shell 环境中排除故障要比在生成网页的文件中排除故障容易得多
- 我们不会太多地使用 shell ,但应继续使用它来熟悉对存储在项目中的数据进行访问的 Django 语法
注意
- 每次修改模型后,你都需要重启 shell,这样才能看到修改的效果
- 要退出shell会话,可按 Ctr+D;如果你使用的是 Windows 系统,应按 Ctr+Z,再按回车键
python manage.py shell
>>> from learning_logs.models import Topic
>>> Topic.objects.all()
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>
1) 遍历查询集
像遍历列表一样查看分配给每个主题对象的ID:
>>> topics = Topic.objects.all()
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing
2) 获取对象并查看属性
通过 ID 获取主题Chess对象,并查看其属性 text 和 date_added 的值:
>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2022, 2, 26, 4, 58, 42, 478568, tzinfo=datetime.timezone.utc)
3) 查看与主题相关联的条目
前面我们给模型 Entry 定义了属性 topic,这是一个 ForeignKey,将条目与主题关联起来,利用这种关联,Django 能够获取与特定主题相关联的所有条目:
# 为通过外键关系获取数据,可使用相关模型的小写名称、下划线和单词 set
# - 假设你有模型 Pizza 和 Topping,而 Topping 通过一个外键关联到 Pizza;如果你有一个名为 my_pizza 的对象,表示一张比萨,就可使用代码 my_pizza.topping_set.all() 来获取这张比萨的所有配料
>>> t.entry_set.all()
<QuerySet [<Entry: The opening is the first part of the game, roughly...>, <Entry: In the opening phase of the game, it's important t...>]>
(9) Django API
编写访问项目中的数据的代码时,请浏览有关如何查询数据的文档
3. 创建页面:学习笔记主页
- 使用 Django 创建网页的过程通常分三个阶段:定义 URL、编写视图和编写模板
- URL 模式描述了 URL 是如何设计的,让 Django 知道如何将浏览器请求与网站 URL 匹配,以确定返回哪个页面
- 每个URL都被映射到特定的视图——视图函数获取并处理网页所需的数据
- 视图函数通常使用模板来渲染页面,而模板定义页面的总体结构
- 创建页面的过程看起来可能很复杂,但将 URL、视图和模板分离的效果很好
- 这让我们能够分别考虑项目的不同方面,在项目很大时,可让各个参与者专注于最擅长的方面
- 例如,数据库专家专注于模型,程序员专注于视图代码,而 Web 设计人员专注于模板
- 这让我们能够分别考虑项目的不同方面,在项目很大时,可让各个参与者专注于最擅长的方面
(1) 映射 URL
当前,基础 URL 返回默认的 Django 网站,让我们知道正确地建立了项目,我们将做修改以将其映射到“学习笔记”的主页
urls.py
# 前两行默认导入了一个模块(admin)和一个函数(path),以便对管理网站的 URL 进行管理
from django.contrib import admin
# 添加 include 的导入
from django.urls import path, include
# 这个文件的主体定义了变量 urlpatterns
# - 在这个针对整个项目的 urls.py 文件中,变量 urlpatterns 包含项目中应用程序的 URL
urlpatterns = [
# admin.site.urls 模块定义了可在管理网站中请求的所有 URL
path('admin/', admin.site.urls),
# 添加一行代码来包含模块 learning_logs.urls,即包含 learning_logs 的 URL
path('', include('learning_logs.urls')),
]
默认的 urls.py 包含在文件夹 learning_log 中,现在需要在文件夹 learning_logs 中再创建一个 urls.py 文件并输入如下代码:
# 指出当前位于哪个 urls.py 文件中的文档字符串
"""定义 learning_logs 的 URL 模式"""
# 导入了函数 path,因为需要使用它将 URL 映射到视图
from django.urls import path
# 导入了模块 views,其中的句点让 Python 从当前 urls.py 模块所在的文件夹导入 views.py
from . import views
# 变量 app_name 让 Django 能够将这个 urls.py 文件同项目内其他应用程序中的同名文件区分开来
app_name = 'learning_logs'
# 变量 urlpatterns 是一个列表,包含可在应用程序 learning_logs 中请求的页面
urlpatterns = [
# 主页
# 实际的 URL 模式是对函数 path() 的调用,这个函数接受三个实参
# - 1. 第一个是一个字符串,帮助 Django 正确地路由(route)请求
# - 收到请求的 URL 后,Django 力图将请求路由给一个视图,为此,它搜索所有的URL模式,找到与当前请求匹配的那个
# - Django 忽略项目的基础 URL(http://localhost:8000/),因此空字符串('')与基础 URL 匹配,其他 URL 都与这个模式不匹配
# - 如果请求的 URL 与任何既有的 URL 模式都不匹配,Django将返回一个错误页面
#
# - 2. 第二个实参指定了要调用 view.py 中的哪个函数
# - 请求的 URL 与前述正则表达式匹配时,Django 将调用 view.py 中的函数 index()(这个视图函数将在下一节编写)
#
# - 3. 第三个实参将这个 URL 模式的名称指定为 index,让我们能够在代码的其他地方引用它
# - 每当需要提供到这个主页的链接时,都将使用这个名称,而不编写 URL
path('', views.index, name='index'),
]
(2) 编写视图
视图函数接受请求中的信息,准备好生成页面所需的数据,再将这些数据发送给浏览器——这通常是使用定义页面外观的模板实现的
views.py
# 导入了函数 render(),它根据视图提供的数据渲染响应
from django.shortcuts import render
# 添加为主页编写视图的代码
# - URL 请求与刚才定义的模式匹配时,Django 将在文件 views.py 中查找函数 index(),再将对象 request 传递给这个视图函数
def index(request):
"""学习笔记的主页"""
# 这里不需要处理任何数据,因此这个函数只包含调用 render() 的代码
# - 两个实参:对象 request 以及一个可用于创建页面的模板
return render(request, 'learning_logs/index.html')
(3) 编写模板
模板定义页面的外观,而每当页面被请求时,Django 将填入相关的数据,模板让你能够访问视图提供的任何数据
- 在文件夹 learning_logs 中新建文件夹 templates,再在 templates 中新建文件夹 learning_logs,这样建立了 Django 能够明确解读的结构
- 在最里面的文件夹 learning_logs 中,新建文件 index.html(这个文件的路径为learning_log/learning_logs/templates/learning_logs/index.html),再在其中编写如下代码:
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>
- 现在,如果请求这个项目的基础 URL,将看到刚才创建的页面,而不是默认的 Django 页面
- Django 接受请求的 URL,发现该 URL 与模式’'匹配,因此调用函数 views.index(),这将使用 index.html 包含的模板来渲染页面
- Django 接受请求的 URL,发现该 URL 与模式’'匹配,因此调用函数 views.index(),这将使用 index.html 包含的模板来渲染页面
4. 创建其他页面
我们将创建两个显示数据的页面,其中一个列出所有的主题,另一个显示特定主题的所有条目
- 对于每个页面,我们都将指定URL模式、编写一个视图函数并编写一个模板
- 但这样做之前,我们先创建一个父模板,项目中的其他模板都将继承它
(1) 模板继承
创建网站时,一些通用元素几乎会在所有页面中出现,在这种情况下,可编写一个包含通用元素的父模板,并让每个页面都继承这个模板,而不必在每个页面中重复定义这些通用元素
- 这种方法能让你专注于开发每个页面的独特方面,还能让修改项目的整体外观容易得多
1) 父模板
在 index.html 所在目录创建模板 base.html,该模板包含所有页面都有的元素,而其他模板都继承它
- 当前,所有页面都包含的元素只有顶端的标题,所以将这个标题设置为到主页的链接
base.html
<!--创建一个包含项目名的段落,该段落也是到主页的链接-->
<p>
<!--为创建链接,使用了一个模板标签,它是用花括号和百分号({% %})表示的-->
<!--模板标签是一小段代码,生成要在页面中显示的信息-->
<!--相比于 Python 文件,模板文件的缩进层级更多,因此每个层级通常只缩进两个空格(每个层级缩进多少个空格无关紧要,只需确保一致即可)-->
<!--通过使用模板标签来生成 URL,能很容易地确保链接是最新的:只需修改 urls.py 中的 URL 模式,Django 就会在页面下次被请求时自动插入修改后的 URL-->
<!--这里的模板标签 {% url 'learning_logs:index' %} 生成一个 URL,该 URL 与在 learning_logs/urls.py 中定义的名为 'index' 的 URL 模式匹配-->
<!--在本例中,learning_logs 是一个命名空间,而 index 是该命名空间中一个名称独特的 URL 模式-->
<!--这个命名空间来自在文件 learning_logs/urls.py 中赋给 app_name 的值-->
<a href="{% url 'learning_logs:index'%}">Learning Log</a>
</p>
<!--名为 content 的块标签是一个占位符,其中包含的信息由子模板指定-->
<!--子模板并非必须定义父模板中的每个块,因此在父模板中,可使用任意多个块来预留空间,而子模板可根据需要定义相应数量的块-->
{% block content %}{% endblock content %}
2) 子模板
重写 index.html,使其继承 base.html:
<!--标题 Learning Log 没有了,取而代之的是指定要继承哪个模板的代码-->
<!--子模板的第一行必须包含标签 {% extends %},让 Django 知道它继承了哪个父模板-->
<!--文件 base.html 位于文件夹 learning_logs 中,因此父模板路径中包含 learning_logs-->
<!--这行代码导入模板 base.html 的所有内容,让 index.html 能够指定要在 content 块预留的空间中添加的内容-->
{% extends "learning_logs/base.html" %}
<!--插入了一个名为 content 的 {% block %} 标签,以定义 content 块-->
<!--不是从父模板继承的内容都包含在 content 块中-->
{% block content %}
<p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>
<!--标签 {% endblock content %} 指出了内容定义的结束位置-->
<!--在标签 {% endblock %} 中,并非必须指定块名,但如果模板包含多个块,指定块名有助于确定结束的是哪个块-->
{% endblock content %}
模板继承的优点开始显现出来了:在子模板中,只需包含当前页面特有的内容
- 这不仅简化了每个模板,还使得网站修改起来容易得多
- 要修改很多页面都包含的元素,只需修改父模板即可,所做的修改将传导到继承该父模板的每个页面
- 在包含数十乃至数百个页面的项目中,这种结构使得网站改进起来更容易、更快捷。
注意
- 在大型项目中,通常有一个用于整个网站的父模板 base.html,且网站的每个主要部分都有一个父模板,每个部分的父模板都继承 base.html,而网站的每个页面都继承相应部分的父模板
- 这让你能够轻松地修改整个网站的外观、网站任何一部分的外观以及任何一个页面的外观
- 这种配置提供了一种效率极高的工作方式,让你乐意不断地去改进网站
(2) 显示所有主题的页面
1) URL 模式
我们通常使用一个简单的 URL 片段来指出页面显示的信息,这里使用单词 topics 来定义显示所有主题的页面的 URL,因此 URL http://localhost:8000/topics/ 将返回显示所有主题的页面
learning_logs/urls.py
"""定义 learning_logs 的 URL 模式"""
--snip--
urlpatterns = [
# 主页
path('', views.index, name='index'),
# 显示所有的主题
# 在用于主页 URL 的字符串参数中添加了 topics/
# - Django 检查请求的 URL 时,这个模式与如下 URL 匹配:基础 URL 后面跟着 topics
# - 可在末尾包含斜杠,也可省略,但单词 topics 后面不能有任何东西,否则就与该模式不匹配
# URL 与该模式匹配的请求都将交给 views.py 中的函数 topics() 处理
path('topics/', views.topics, name='topics'),
]
2) 视图
函数 topics() 需要从数据库中获取一些数据,并将其交给模板
views.py
from django.shortcuts import render
# 导入与所需数据相关联的模型
from .models import Topic
def index(request):
--snip--
# 函数 topics() 包含一个形参:Django 从服务器那里收到的 request 对象
def topics(request):
"""显示所有的主题"""
# 查询数据库 —— 请求提供 Topic 对象,并根据属性 date_added 进行排序
# 返回的查询集被存储在 topics 中
topics = Topic.objects.order_by('date_added')
# 定义一个将发送给模板的上下文
# - 上下文是一个字典,其中的键是将在模板中用来访问数据的名称,而值是要发送给模板的数据
# - 这里只有一个键值对,包含一组将在页面中显示的主题
# - 创建使用数据的页面时,除了对象 request 和模板的路径外,还将变量 context 传递给 render()
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
3) 模板
在 index.html 所在的目录中创建文件 topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
<!--使用一个相当于 for 循环的模板标签,它遍历字典 context 中的列表 topics-->
<!--模板中使用的代码与 Python 代码存在一些重要差别:Python 使用缩进来指出哪些代码行是for循环的组成部分;而在模板中,每个for循环都必须使用 {% endfor %} 标签来显式地指出其结束位置-->
{% for topic in topics %}
<!-- 要在模板中打印变量,需要将变量名用双花括号括起,这些花括号不会出现在页面中,只是用于告诉 Django 我们使用了一个模板变量-->
<!-- 每次循环时,{{ topic }} 都被替换为 topic 的当前值-->
<li>{{ topic }}</li>
<!--模板标签 {% empty %} 告诉 Django 在列表 topics 为空时该如何办-->
{% empty %}
<li>No topics have been added yet.</li>
<!--结束for循环-->
{% endfor %}
</ul>
{% endblock content %}
修改父模板,使其包含到显示所有主题的页面的链接
base.html
<p>
<!--在到主页的链接后面添加一个连字符-->
<a href="{% url 'learning_logs:index'%}">Learning Log</a> -
<!--添加一个到显示所有主题的页面的链接 —— 使用的也是模板标签 {% url %} -->
<!--这行让 Django 生成一个链接,它与 learning_logs/urls.py 中名为 'topics' 的 URL 模式匹配-->
<a href="{% url 'learning_logs:topics'%}">Topics</a> -
</p>
{% block content %}{% endblock content %}
(3) 显示特定主题的页面
创建一个专注于特定主题的页面,它显示该主题的名称以及所有条目,且将修改显示所有主题的页面,让每个项目列表项都变为到相应主题页面的链接
1) URL 模式
显示特定主题的页面的 URL 模式与前面的所有 URL 模式都稍有不同,因为它使用主题的id属性来指出请求的是哪个主题
- 例如,如果用户要查看主题 Chess(其id为1)的详细页面,URL 将为 http://localhost:8000/topics/1/
learning_logs/urls.py
--snip--
urlpatterns = [
--snip--
# 特定主题的详细页面
# URL 模式中的字符串 'topics/<int:topic_id>/'
# - 'topics' 让 Django 查找在基础 URL 后包含单词 topics 的 URL
# - '/<int:topic_id>/' 与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为 topic_id 的实参中
# 发现 URL 与这个模式匹配时,Django 将调用视图函数 topic(),并将存储在 topic_id 中的值作为实参传递给它
# 在这个函数中,将使用 topic_id 的值来获取相应的主题
path('topics/<int:topic_id>/', views.topic, name='topic'),
]
2) 视图
函数 topic() 需要从数据库中获取指定的主题以及与之相关联的所有条目
views.py
--snip--
# 这个函数接受表达式 /<int:topic_id>/ 捕获的值,并将其存储到 topic_id 中
def topic(request, topic_id):
"""显示单个主题及其所有的条目"""
# 使用 get() 来获取指定的主题
topic = Topic.objects.get(id=topic_id)
# 获取与该主题相关联的条目,并根据 date_added 进行排序:date_added 前面的减号指定按降序排列,即先显示最近的条目
entries = topic.entry_set.order_by('-date_added')
# 将主题和条目都存储在字典 context 中
context = {'topic': topic, 'entries': entries}
# 再将这个字典发送给模板 topic.html
return render(request, 'learning_logs/topic.html', context)
3) 模板
这个模板需要显示主题的名称和条目的内容
topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<!--显示当前的主题,它存储在模板变量 {{ topic }} 中(为什么可以使用变量 topic 呢?因为它包含在字典 context 中)-->
<p>Topic: {{ topic }}</p>
<p>Entries:</p>
<!--定义一个显示每个条目的项目列表-->
<ul>
<!--遍历条目-->
{% for entry in entries %}
<!--每个项目列表项都将列出两项信息:条目的时间戳和完整的文本-->
<li>
<!--为列出时间戳,我们显示属性 date_added 的值-->
<!--在 Django 模板中,竖线(|)表示模板过滤器,即对模板变量的值进行修改的函数-->
<!--过滤器 date: 'M d, YH:i' 以类似于这样的格式显示时间戳:January 1, 2018 23:00-->
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
<!--显示 text 的完整值,而不仅仅是前50字符-->
<!--过滤器 linebreaks 将包含换行符的长条目转换为浏览器能够理解的格式,以免显示为不间断的文本块-->
<p>{{ entry.text|linebreaks }}</p>
</li>
<!--使用模板标签 {% empty %} 打印一条消息,告诉用户当前主题还没有条目-->
{% empty %}
<li>There are no entries for this topic yet.</li>
{% endfor %}
</ul>
{% endblock content %}
4) 将显示所有主题的页面中的主题设置为链接
在浏览器中查看显示特定主题的页面前,需要修改模板topics.html,让每个主题都链接到相应的页面
topics.html
--snip--
{% for topic in topics %}
<li>
<!--我们使用模板标签 url 根据 learning_logs 中名为 'topic' 的 URL 模式生成了合适的链接-->
<!--这个 URL 模式要求提供实参 topic_id,因此在模板标签 url 中添加了属性 topic.id-->
<!--topic.id 和 topic_id 之间存在细微而重要的差别:表达式 topic.id 检查主题并获取其 ID 值,而在代码中,变量 topic_id 是指向该 ID 的引用-->
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</li>
{% empty %}
--snip--