Skip to content

Commit 2b0539b

Browse files
committed
Fix code and missing info
1 parent 99305be commit 2b0539b

6 files changed

Lines changed: 62 additions & 25 deletions

File tree

chapters/10-test.md

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,13 @@ class WatchlistTestCase(unittest.TestCase):
9090

9191
def setUp(self):
9292
# 使用测试配置创建程序实例
93-
app = create_app(config_name='testing')
94-
# 创建数据库和表
93+
self.app = create_app(config_name='testing')
94+
# 创建程序上下文
95+
self.context = self.app.app_context()
96+
# 激活上下文
97+
self.context.push()
98+
99+
# 创建数据库和表
95100
db.create_all()
96101
# 创建测试数据,一个用户,一个电影条目
97102
user = User(name='Test', username='test')
@@ -101,12 +106,13 @@ class WatchlistTestCase(unittest.TestCase):
101106
db.session.add_all([user, movie])
102107
db.session.commit()
103108

104-
self.client = app.test_client() # 创建测试客户端
105-
self.runner = app.test_cli_runner() # 创建测试命令运行器
109+
self.client = self.app.test_client() # 创建测试客户端
110+
self.runner = self.app.test_cli_runner() # 创建测试命令运行器
106111

107112
def tearDown(self):
108113
db.session.remove() # 清除数据库会话
109114
db.drop_all() # 删除数据库表
115+
self.context.pop() # 清除上下文
110116

111117
# 测试程序实例是否存在
112118
def test_app_exist(self):
@@ -127,9 +133,16 @@ class TestingConfig(BaseConfig):
127133

128134
这里将 `TESTING` 设为 `True` 来开启测试模式,这样在出错时不会输出多余信息;然后将 `SQLALCHEMY_DATABASE_URI` 设为 `'sqlite:///:memory:'`,这会使用 SQLite 内存型数据库,不会干扰开发时使用的数据库文件。你也可以使用不同文件名的 SQLite 数据库文件,但内存型数据库速度更快。
129135

136+
某些程序操作需要在激活 Flask 上下文时执行,比如前面介绍的 `url_for()` 函数,或是创建数据库表的 `db.session.create_all()` 操作。我们在 `setUp()` 方法中创建并激活程序上下文:
137+
138+
```python
139+
self.context = self.app.app_context()
140+
self.context.push()
141+
```
142+
130143
接着,我们调用 `db.create_all()` 创建数据库和表,然后添加测试数据到数据库中。在 `setUp()` 方法最后创建的两个类属性分别为测试客户端和测试命令运行器,前者用来模拟客户端请求,后者用来触发自定义命令,下一节会详细介绍。
131144

132-
`tearDown()` 方法中,我们调用 `db.session.remove()` 清除数据库会话并调用 `db.drop_all()` 删除数据库表。测试时的程序状态和真实的程序运行状态不同,所以需要调用 `db.session.remove()` 来确保数据库会话被清除。
145+
`tearDown()` 方法中,我们调用 `db.session.remove()` 清除数据库会话并调用 `db.drop_all()` 删除数据库表。测试时的程序状态和真实的程序运行状态不同,所以需要调用 `db.session.remove()` 来确保数据库会话被清除。最后调用 `self.context.pop()` 清除上下文。
133146

134147

135148
### 测试程序功能
@@ -468,25 +481,27 @@ OK
468481

469482
```bash
470483
$ coverage report
471-
Name Stmts Miss Cover
472-
-------------------------------------------
473-
watchlist\__init__.py 25 1 96%
474-
watchlist\commands.py 35 1 97%
475-
watchlist\errors.py 8 2 75%
476-
watchlist\models.py 16 0 100%
477-
watchlist\extensions.py 77 2 97%
478-
...
479-
-------------------------------------------
480-
TOTAL 161 6 96%
484+
Name Stmts Miss Cover
485+
------------------------------------------------------
486+
watchlist/__init__.py 23 0 100%
487+
watchlist/blueprints/__init__.py 0 0 100%
488+
watchlist/blueprints/auth.py 28 0 100%
489+
watchlist/blueprints/main.py 61 1 98%
490+
watchlist/commands.py 41 1 98%
491+
watchlist/errors.py 11 2 82%
492+
watchlist/extensions.py 13 3 77%
493+
watchlist/models.py 20 0 100%
494+
watchlist/settings.py 15 0 100%
495+
------------------------------------------------------
496+
TOTAL 212 7 97%
481497
```
482498

483499
测试覆盖率报告列出了包内文件的覆盖率情况,包括每个文件的行数(Stmts),没测试到的代码行数(Miss),以及测试覆盖率(Cover)。
484500

485-
你还可以使用 coverage html 命令获取详细的 HTML 格式的覆盖率报告,它会在当前目录生成一个 htmlcov 文件夹,打开其中的 index.html 即可查看覆盖率报告。点击文件名可以看到具体的代码覆盖情况,如下图所示:
486-
487-
![覆盖率报告](images/9-1.png)
501+
你还可以使用 `coverage html` 命令获取详细的 HTML 格式的覆盖率报告,它会在当前目录生成一个 htmlcov 文件夹,打开其中的 index.html 即可查看覆盖率报告。点击文件名可以看到具体的代码覆盖情况,如下图所示:
502+
![Coverage report](images/9-1.png)
488503

489-
记得在 .gitignore 文件后追加下面两行,忽略掉生成的覆盖率报告文件:
504+
最后记得在 .gitignore 文件后追加下面两行,忽略掉生成的覆盖率报告文件:
490505

491506
```
492507
htmlcov/

chapters/5-database.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class Movie(db.Model): # 表名将会是 movie
108108
* 模型类要声明继承 `db.Model`
109109
* 使用 `__tablename__` 属性定义表名称。
110110
* 每一个类属性(字段)的类型通过类型标注(type hint)定义,类型信息通过 `Mapped[]` 传入。下面的表格列出了常用的字段类型和对应的类型对象。
111-
* 如果对字段有额外的设置,可以使用 `mapped_column()` 调用传入额外的参数。比如,`primary_key` 设置当前字段是否为主键。除此之外,常用的选项还有 `nullable`(布尔值,是否允许为空值)、`index`(布尔值,是否设置索引)、`unique`(布尔值,是否允许重复值)、`default`(设置默认值)等。
111+
* 如果对字段有额外的设置,可以使用 `mapped_column()` 调用传入额外的参数。比如,`primary_key` 设置当前字段是否为主键。除此之外,常用的选项还有 `index`(布尔值,是否设置索引)、`unique`(布尔值,是否允许重复值)、`default`(设置默认值)等。
112112

113113
常用的字段类型如下表所示:
114114

@@ -344,11 +344,11 @@ movie = db.session.scalar(select(Movie))
344344
@app.route('/')
345345
def index():
346346
user = db.session.execute(select(User)).scalar() # 读取用户记录
347-
movies = db.session.execute(select(Movie)).scalars() # 读取所有电影记录
347+
movies = db.session.execute(select(Movie)).scalars().all() # 读取所有电影记录
348348
return render_template('index.html', user=user, movies=movies)
349349
```
350350

351-
> **提示** 这里我们获取电影记录时,仅调用了 `scalars()` 而不是 `scalars().all()`。因为我们在模板中唯一用到这个 movies 的地方就是用来在 for 循环中迭代所有电影条目,而 `scalars()` 返回的 Result 对象可以直接作为迭代器使用。这样处理会比后者把所有结果都加载出来再进行 for 循环性能更好。
351+
> **提示** `scalars()` 返回的 Result 对象可以直接作为迭代器使用。如果你对返回的结果只需要调用 for 循环迭代,那么可以仅调用 `scalars()` 而不是 `scalars().all()`,这样会比后者把所有结果都加载出来再进行 for 循环性能更好。
352352
353353
`index` 视图中,原来传入模板的 `name` 变量被 `user` 实例取代,模板 index.html 中的两处 `name` 变量也要相应的更新为 `user.name` 属性:
354354

@@ -370,8 +370,9 @@ import click
370370
@app.cli.command()
371371
def forge():
372372
"""Generate fake data."""
373+
db.drop_all()
373374
db.create_all()
374-
375+
375376
# 全局的两个变量移动到这个函数内
376377
name = 'Grey Li'
377378
movies = [
@@ -386,13 +387,13 @@ def forge():
386387
{'title': 'WALL-E', 'year': '2008'},
387388
{'title': 'The Pork of Music', 'year': '2012'},
388389
]
389-
390+
390391
user = User(name=name)
391392
db.session.add(user)
392393
for m in movies:
393394
movie = Movie(title=m['title'], year=m['year'])
394395
db.session.add(movie)
395-
396+
396397
db.session.commit()
397398
click.echo('Done.')
398399
```

chapters/6-advanced-template.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,5 +298,6 @@ $ git push
298298
## 进阶提示
299299

300300
* 本章介绍的自定义错误页面是为了引出两个重要的知识点,因此并没有着重介绍错误页面本身。这里只为 404 错误编写了自定义错误页面,对于另外两个常见的错误 400 错误和 500 错误,你可以自己试着为它们编写错误处理函数和对应的模板。
301+
* `abort()` 函数接受传入一个 description 参数,你可以使用它来传入自定义错误消息。在错误处理函数中,记得从错误对象获取这个参数的值(`error.description`),并将其渲染到错误页面中。类似地,上一章介绍的 `get_or_404()``first_or_404()` 也支持传入 description 参数。
301302
* 因为示例程序的语言和电影标题使用了英文,所以电影网站的搜索链接使用了 IMDb。对于中文,你可以使用豆瓣电影或时光网。以豆瓣电影为例,它的搜索链接为 <https://movie.douban.com/subject_search?search_text=关键词>,对应的 `href` 属性值即 `https://movie.douban.com/subject_search?search_text={{ movie.title }}`
302303
* 因为基模板会被所有其他页面模板继承,如果你在基模板中使用了某个变量,那么这个变量也需要使用模板上下文处理函数注入到模板里。

chapters/8-auth.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ class User(db.Model):
5050
(.venv) $ flask init-db --drop
5151
```
5252

53+
注意同时更新命令函数 `forge()`,在创建用户记录时传入这两个新字段的值:
54+
55+
```python
56+
user = User(name=name, username='admin')
57+
user.set_password('helloflask')
58+
```
5359
## 生成管理员账户
5460

5561
因为程序只允许一个人使用,没有必要编写一个注册页面。我们可以编写一个命令来创建管理员账户,下面是实现这个功能的 `admin()` 函数:

chapters/9-organize.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ app.register_blueprint(auth_bp)
8585

8686
注意要在视图函数的下面定义这两行调用,这样可以确保注册蓝本的时候,所有视图函数的信息已经注册到蓝本上。
8787

88+
引入蓝本后,我们还要对程序中所有作为第一个参数传入 url_for() 的端点值进行更新。蓝本扩展了端点的命名空间,所有现在的端点值也要加入蓝本名称,即 `蓝本名.视图函数名`。比如原来的:
89+
90+
```python
91+
url_for('index')
92+
```
93+
94+
则需要更新为:
95+
96+
```python
97+
url_for('main.index')
98+
```
99+
100+
你可以用编辑器的全局搜索功能找到所有 url_for 调用(包括模板里的调用),然后逐一进行更新。
101+
88102
> **提示** 蓝本支持嵌套,你可以在某个蓝本上再注册子蓝本。同时蓝本支持很多特定的方法和属性,具体可以访问[蓝本的 API 文档](https://flask.palletsprojects.com/en/stable/api/#blueprint-objects)查看。
89103
90104

chapters/images/9-1.png

263 KB
Loading

0 commit comments

Comments
 (0)