第 3 章:个人网站¶
在本章中,我们将构建一个包含主页和"关于"页面的个人网站,同时深入了解 Django 的模板、基于函数的视图和测试。模板是控制数据显示方式的展示层,但它们也支持继承和符合 Django"不要重复自己"(DRY)设计理念的基本逻辑。视图将 URL 和模板结合在一起,同时为数据添加大量逻辑;它们通常被视为 Django 应用的逻辑层。第三个基本概念是测试,它对任何 Web 项目都至关重要,并且在 Django 中得到了很好的支持。在本章结束时,你将学会如何使用简单的基于函数的视图和模板,并编写你的第一批测试。
初始设置¶
我们的初始设置与上一章类似,包含以下步骤:
- 为我们的代码创建一个名为
personal_website的新目录并导航到其中 - 创建一个名为
.venv的新虚拟环境并激活它 - 安装 Django 和 Black
- 创建一个名为
django_project的新 Django 项目 - 创建一个名为
pages的新应用
在新的命令行 Shell 中,导航到桌面的 code 文件夹并创建一个名为 personal_website 的新文件夹。在命令行提示符前,你不应该看到活动的虚拟环境(用 (.venv) 表示)。如果看到了,说明你还在已有的虚拟环境中,输入 deactivate 来退出。切换目录进入 personal_website,创建一个新的虚拟环境并激活它。
# Windows
$ cd onedrive\desktop\code
$ mkdir personal_website
$ cd personal_website
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $
# macOS
$ cd ~/desktop/code
$ mkdir personal_website
$ cd personal_website
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $
接下来,安装 Django 和 Black,创建一个名为 django_project 的新项目,并创建一个名为 pages 的新应用。到本书结束时,这些命令将变得非常熟悉,唯一变化的只是项目或应用的名称。
(.venv) $ python -m pip install django~=5.0.0 black
(.venv) $ django-admin startproject django_project .
(.venv) $ python manage.py startapp pages
即使我们添加了一个新应用,Django 也不会识别它,直到我们更新 django_project/settings.py 中的 INSTALLED_APPS 设置。现在打开你的文本编辑器,将其添加到底部:
# django_project/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"pages", # new
]
用 migrate 初始化数据库,用 runserver 启动本地 Web 服务器。
(.venv) $ python manage.py migrate
(.venv) $ python manage.py runserver
然后,导航到 http://127.0.0.1:8000/ 查看 Django 欢迎页面。只用了几个命令,我们就有了一个在虚拟环境中运行的全新 Django 项目,安装了 Black,以及一个 pages 应用。
主页¶
Django 的"视图"是一个 Python 函数,它接受 Web 请求并返回 Web 响应。当一个网页被请求时,Django 会自动创建一个包含请求元数据的 HttpRequest 对象。视图返回一个 HttpResponse 对象。
我们将首先重复上一章的步骤,仅使用视图和 URL 分发器来创建主页。然后,我们将使用模板和更复杂的视图逻辑来创建一个"关于"页面。
让我们从视图开始。Django 在应用内预填充了 views.py 文件,内容如下:
# pages/views.py
from django.shortcuts import render
# Create your views here.
我们将在"关于"页面中使用 render,所以保留该导入即可。对于我们的主页,我们将重复上一章的步骤:导入 HttpResponse 类,创建一个名为 home_page_view 的视图,将第一个参数(始终是 HttpRequest 对象!)命名为 request,然后返回文本"Homepage"。
# pages/views.py
from django.http import HttpResponse
from django.shortcuts import render
def home_page_view(request):
return HttpResponse("Homepage")
下一步是在 pages 应用内创建一个 urls.py 文件,从 django.urls 导入 path,从本地目录的 views.py 文件导入 home_page_view,并在空字符串 "" 处设置路由来调用 home_page_view。
# pages/urls.py
from django.urls import path
from .views import home_page_view
urlpatterns = [
path("", home_page_view),
]
最后一步是更新项目级的 urls.py 文件,它是所有 URL 请求的初始入口。我们导入 include 函数来包含其他 URL 配置,并为 pages 应用设置 "" 的 URL 路由。
# django_project/urls.py
from django.contrib import admin
from django.urls import path, include # new
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("pages.urls")), # new
]
就是这样!如果本地服务器仍在命令行上运行,你应该能在浏览器中访问主页:

基于函数的视图——关于页面¶
render() 是一个处理模板时非常有用的 Django 快捷函数。render() 的第一个参数是 request 对象,第二个参数是模板名称。这意味着如果我们想使用名为 about.html 的模板来创建关于页面的视图,可以这样写:
# pages/views.py
from django.http import HttpResponse
from django.shortcuts import render
def home_page_view(request):
return HttpResponse("Homepage")
def about_page_view(request): # new
return render(request, "pages/about.html")
新视图名为 about_page_view,其第一个参数是 HttpRequest 对象,我们将其命名为 request。它使用 render() 函数返回 request 对象和一个名为"pages/about.html"的相关模板。
模板¶
模板是 Django 中的展示层,提供了一种便捷的方式来生成 HTML 文件,还可以包含 CSS、JavaScript 和图像等媒体。默认情况下,Django 的模板加载引擎会在每个应用中查找"templates"子目录。这意味着我们可以在 pages 应用内添加一个 templates 目录,然后按以下方式包含 about.html:
└── pages
└── templates
└── about.html
这种方法可以工作,但不是最佳实践。Django 会选择它找到的第一个名称匹配的模板。如果在两个不同的应用中都有 about.html 文件会怎样?Django 无法轻易判断我们的函数中应该使用哪一个。幸运的是,一个简单的修复方法是将模板文件放在包含应用名称的另一个目录中来为模板文件添加命名空间。换句话说:app -> templates -> app -> 模板文件。
在我们的 pages 应用中,看起来像这样:
└── pages
└── templates
└── pages
└── about.html
作为 Django 的最佳实践,在应用内存储模板文件时你应该始终采用这种方法。你可以在文本编辑器中或直接在命令行上创建这些新目录:
(.venv) $ mkdir pages/templates
(.venv) $ mkdir pages/templates/pages
然后,在 pages/templates/pages 目录中添加一个名为 about.html 的新文件。你可以在 Visual Studio Code 中通过鼠标导航到屏幕左上角,点击"File",然后点击"New File"来完成。确保在正确的位置命名和保存文件。
about.html 文件将有一个 <h1> 标签作为标题和一个 <p> 标签作为段落文本。
<!-- pages/templates/pages/about.html -->
<h1>About page</h1>
<p>This is the new template-powered About page.</p>
我们的模板完成了!视图 about_page_view 可以在调用时访问模板,但剩余的步骤是配置 URL 分发器。
URL 分发器¶
此时,添加新 URL 路由的模式应该开始变得熟悉了。我们导入视图 about_page_view,然后设置其路径 about/ 和视图名称。
# pages/urls.py
from django.urls import path
from .views import home_page_view, about_page_view # new
urlpatterns = [
path("about/", about_page_view), # new
path("", home_page_view),
]
就是这样!使用 runserver 命令启动开发 Web 服务器。
(.venv) $ python manage.py runserver
如果你导航到 http://127.0.0.1:8000/about/,关于页面将可见。

Django 模板语言¶
所有 Web 框架(包括 Django)都需要一种动态生成 HTML 的方式,最常见的方法是使用包含静态内容和用于插入动态内容的特殊语法的模板。Django 有自己的模板语言,它并不试图替代像 Vue、React 或 Angular 这样的专用 JavaScript 前端。相反,它是一种刻意受限但仍然强大的方式,用于使用注释、变量、过滤器、标签和模板继承。
让我们重新访问我们的 about.html 模板来尝试一些这些功能。我们先从注释标签开始,它允许我们添加开发者可读但不在页面上显示的代码。{% comment %} 和 {% endcomment %} 标签之间的任何内容都会被忽略。
<!-- pages/templates/pages/about.html -->
<h1>About page</h1>
<p>This is the new template-powered About page.</p>
{% comment %}This is just a comment. It won't appear on the web page!{% endcomment %}
如果你保存文件并刷新 Web 浏览器,注释将不可见。它不会在发送到 about_page_view 再到 Web 浏览器的模板 HTML 中渲染。如果你想添加多行注释,可以使用 comment 标签,但总体来说,Django 模板不应该太复杂。虽然可以向其中添加基本逻辑,但最好将逻辑上的繁重思考移到视图或模型文件中(我们将在本书后面这样做)。
模板上下文¶
每当渲染一个 Django 模板时,也会创建一个上下文。这个上下文是一个类似字典的对象,变量名作为键,变量值作为值。我们可以并且会用信息(通常来自数据库)来更新这个上下文。当你用给定的上下文渲染模板时,上下文字典中的每个键都成为模板中你可以访问和使用的变量。
render() 快捷函数按以下顺序接收参数:
- 按惯例命名为
request的HttpRequest对象 template_namecontext字典
换句话说:render(request, template_name, context)。默认情况下,context 设置为 None,但我们可以手动添加信息来看看它的效果。
# pages/views.py
from django.http import HttpResponse
from django.shortcuts import render
def home_page_view(request):
return HttpResponse("Homepage")
def about_page_view(request):
context = {"name": "Alice"} # new
return render(request, "pages/about.html", context) # new
这里,我们添加了一个变量 context,包含一个字典,键为"name",值为"Alice"。要在模板中显示上下文,变量的语法是用双花括号包围变量名。由于键值是"name",我们输入 {{ name }} 来使其可见。
<!-- pages/templates/pages/about.html -->
<h1>About page</h1>
<p>My name is {{ name }}.</p>
如果你刷新浏览器,它将显示名称"Alice"。

作为练习,在 pages/views.py 文件中将名称"Alice"换成你的名字。然后,刷新 Web 浏览器看看它是否现在显示在网页上。
我们还可以向上下文添加其他键/值对。例如,让我们添加一个"age"。
# pages/views.py
from django.http import HttpResponse
from django.shortcuts import render
def home_page_view(request):
return HttpResponse("Homepage")
def about_page_view(request):
context = {
"name": "Alice",
"age": 33, # new
}
return render(request, "pages/about.html", context)
要在我们的网页上显示年龄,我们可以使用 {{ age }} 语法将其作为变量显示在模板中。注意 name 是一个字符串,因为它在引号中,而 33 是一个整数。设置数据类型在 Python 代码和 Django 应用中很重要,我们稍后会探讨。
<!-- pages/templates/pages/about.html -->
<h1>About page</h1>
<p>My name is {{ name }}. I am {{ age }} years old.</p>
再次刷新关于页面来查看更新。

在 pages/views.py 文件中,用你自己的年龄更新 age 变量,刷新网页查看更新后的结果。任何我们作为变量传递给模板上下文的内容都可以在我们的网页上渲染。
Django 模板语言还附带了超过八十个内置标签和内置过滤器,提供额外的功能。我们将在本书后面更深入地探讨它们。
测试¶
为你的代码编写测试和编写代码本身一样重要。没有配套的测试,你无法向开源项目或任何结构良好的公司提交新的代码更改。原因很简单:虽然你的代码更改可能很小,但无法知道它可能会无意中破坏项目中的其他什么。编写良好的测试为你的代码库提供了信心,不需要太多时间,并且可以自动在任何新的代码更改上运行。用 Django 联合创始人 Jacob Kaplan-Moss 的话说,"没有测试的代码在设计上就是坏的。"
测试可以分为两大类:单元测试和集成测试。单元测试独立检查一段功能,而集成测试检查多个关联的部分。单元测试运行更快,更容易维护,因为它们只关注很少量的代码。集成测试较慢且更难维护,因为失败不会指向你具体原因的方向。大多数开发者专注于编写大量单元测试和少量集成测试。
接下来的问题是,测试什么?每当你创建新功能时,都需要测试来确认它按预期工作。例如,在我们的项目中,我们有一个主页和一个关于页面,我们应该测试它们是否存在于预期的 URL 上。现在看起来可能没有必要,但随着项目规模的增长,无法预测什么可能会改变。
Python 标准库包含一个名为 unittest 的内置测试框架,它使用 TestCase 实例和一长串 assert 方法来检查和报告失败。
Django 的测试框架在 Python 的 unittest.TestCase 基类基础上提供了几个扩展。这些包括用于创建虚拟 Web 浏览器请求的测试客户端、几个 Django 特定的额外断言,以及四个测试用例类:SimpleTestCase、TestCase、TransactionTestCase 和 LiveServerTestCase。
一般来说,SimpleTestCase 在不需要数据库时使用,而 TestCase 在你想要测试数据库时使用。TransactionTestCase 有助于直接测试数据库事务,而 LiveServerTestCase 启动一个实时服务器线程,用于与 Selenium 等基于浏览器的工具进行测试。
注意:你可能已经注意到,
unittest和django.test中的方法使用 camelCase 而非更具 Python 风格的 snake_case 模式。原因是 unittest 基于 Java 的 jUnit 测试框架,后者使用 camelCase,所以当 Python 添加 unittest 时,camelCase 命名也随之而来。
如果你查看我们的 pages 应用,Django 已经提供了一个我们可以使用的 tests.py 文件。由于我们的项目不涉及数据库,我们将在文件顶部导入 SimpleTestCase。对于我们的第一批测试,我们将检查网站的两个 URL——主页和关于页面——是否返回 HTTP 200 状态码,即成功 HTTP 请求的标准响应。
# pages/tests.py
from django.test import SimpleTestCase
class HomepageTests(SimpleTestCase):
def test_url_exists_at_correct_location(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
class AboutpageTests(SimpleTestCase):
def test_url_exists_at_correct_location(self):
response = self.client.get("/about/")
self.assertEqual(response.status_code, 200)
要运行测试,用 Control+c 退出服务器,然后在命令行上输入 python manage.py test 来运行它们。
(.venv) $ python manage.py test
Found 2 test(s).
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s
OK
如果你看到类似 AssertionError: 301 != 200 的错误,你可能忘记在上面给 "/about" 添加尾部斜杠了。Web 浏览器知道如果未自动提供就添加斜杠,但这会导致 301 重定向,而不是 200 成功响应!
Git 和 GitHub¶
是时候用 Git 跟踪我们的更改并将它们推送到 GitHub 了。我们首先初始化我们的目录并检查更改的状态。
(.venv) $ git init
(.venv) $ git status
我们在上一章学到,通过创建 .gitignore 文件,我们可以指示 Git 忽略某些文件和目录。我们将包含 .venv 目录(包含我们的虚拟环境)。但我们还将忽略 __pycache__ 目录(包含已编译并准备好执行的字节码)和数据库本身 db.sqlite3。除了文件很大之外,包含在版本控制中的基于文件的数据库会带来安全风险。在大型团队中对项目有不同级别的访问权限是很常见的。通常,每个开发者都可以查看源代码,因为任何错误都可以通过 Git 快速回滚和更改。回滚对生产数据库的更改要困难得多,而且大多数开发者不需要访问它,只要他们有本地或测试数据库可用即可。因此,最佳实践是将数据库访问与源代码分开,即使使用像 SQLite 这样的基于文件的数据库也是如此,所以我们在这里也这样做。创建一个新的 .gitignore 文件并添加以下三行:
# .gitignore
.venv/
__pycache__/
db.sqlite3
再次运行 git status 来确认这三个文件正在被忽略。我们想要虚拟环境中安装包的记录,可以通过创建 requirements.txt 文件来实现。
(.venv) $ pip freeze > requirements.txt
最后一次运行 git status 来确认新创建的 requirements.txt 文件是可见的。然后,添加所有预期的文件和目录,并包含初始提交消息。
(.venv) $ git status
(.venv) $ git add -A
(.venv) $ git commit -m "initial commit"
在 GitHub 上创建一个新仓库,命名为"personal-website",并确保选择"Private"单选按钮。然后点击"Create repository"按钮。
在下一页,向下滚动到写着"...or push an existing repository from the command line"的地方。将那里的两个命令复制粘贴到你的终端中。
它应该看起来像下面这样,只是用户名不是 wsvincent 而是你的 GitHub 用户名。
(.venv) $ git remote add origin https://github.com/wsvincent/personal-website.git
(.venv) $ git branch -M main
(.venv) $ git push -u origin main
小结¶
恭喜你构建了你的第二个 Django 网站。本章的完整源代码可在 GitHub 上找到,如果你需要参考的话。虽然网站相当基础,但我们已经探索了几个基本概念,包括基于函数的视图、模板、URL 分发器和测试。我们还再次使用了 Git,创建了 requirements.txt 和 .gitignore 文件,并首次将代码推送到了 GitHub。有了这些基础,我们可以更快地进入下一章,在那里我们将使用基于类的视图、模板继承以及更多测试来构建一个公司网站。