🧪 第2章 建立数据库应用程序
📄 一句话概括

base 模板收公共布局,子模板只写差异部分,super() 能把父模板那段拿回来。 flash 消息放 base 里,因为所有页面都要显示它,改一处要能生效到所有页面。 配置写成类层次,公共项在基类、差异项在子类,换环境只是换加载哪个类。

📝 本章和前面几章的关系

这一篇对应教材 2.6 与 2.7 两节:模板的通用化与继承、设定组态。 这是第 2 章最后一篇,把页面和配置两件「收拾房间」的活做完。

一、base 收什么

<!doctype html>
<html><head>
<title>{% block title %}后台{% endblock %}</title>
</head><body>
<main>{% block body %}{% endblock %}</main>
</body></html>

base 里放三样:head 里的标题与 CSS、页头导航、flash 消息的渲染。 中间那段 {% block body %} 留给子模板。 子模板第一行写 {% extends "base.html" %},然后只填 block。

block 名字对不上不报错,那段内容静默丢失。这是模板继承最常见的一处空页。

二、super() 把父模板那段拿回来

{% extends "base.html" %}
{% block title %}首页 · {{ super() }}{% endblock %}
{% block body %}<p>用户共 {{ n }} 人</p>{% endblock %}

实测渲染出来的标题是「首页 · 后台」:子模板给「首页」,super() 拿回「后台」, 中间那个点是自己写的。 这个技巧在「所有页面标题都以站点名结尾」的场景里最省事。 不用 super() 也行,代价是每个子模板都要把站点名再写一遍,改名时一改就是十几处。

三、flash 为什么放 base

flash 消息是「这一次请求的提示」,跳转之后要显示在新页面上。 每个页面都可能显示提示,所以渲染代码放 base,写一次就够。 公共部分的判据不是「长得一样」,是「改一处要生效到所有页面」。

实测这次页面里没有待显示的 flash,所以那段 for 循环一个都没进, 渲染结果里是三行空行——空的不是错误,是没有消息。

四、配置写成一个类层次

class BaseConfig:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-key")

class DevConfig(BaseConfig):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"

class ProdConfig(BaseConfig):
    DEBUG = False
    SESSION_COOKIE_SECURE = True

公共项放基类(密钥、追踪开关),差异项放子类(调试、库地址、Cookie 安全)。 实测用 DevConfig 时 DEBUG 是 True、地址是 sqlite:///dev.db; 用 ProdConfig 时 DEBUG 是 False、SESSION_COOKIE_SECURE 是 True。 加载靠 app.config.from_object(DevConfig)。它只读类里的大写属性,方法和小写属性都跳过。

大写这个约定是有意义的:配置项全大写,一眼能和普通数据成员分开。写成小写就读不到,而且不报错。 生产密钥从环境变量来:os.environ.get("SECRET_KEY", "dev-key")。 那个默认值只是给开发用的,上线必须给真值。

五、配置从哪来、按什么顺序

顺序 来源
1 Flask 内置默认值
2 config.from_object 与 from_mapping
3 config.from_prefixed_env()
4 config.from_pyfile("instance/config.py")

后面的覆盖前面的。所以实例目录里那份最硬——它给这台机器专用,一般不进仓库。 这条顺序要记住的原因是排错:配置没生效时,先确认「是不是被后面的来源盖掉了」,再去怀疑代码。 第 1 章实测过另一条相关的坑:from_prefixed_env 只认前缀 FLASK_,不带前缀的变量它看都不看。

六、抽出来到底图什么

两个应用的代码一模一样,差别只在于加载了哪个配置类。 这就是把配置从代码里抽出来的全部意义:同一份代码,不同环境跑出不同行为,而且不用改代码。 反面写法是把判断散在代码里:if app.debug: 到处出现,最后没人说得清线上到底走哪条分支。

这一条和第 1 章讲的工厂函数是同一件事的两面:一个让应用可以被造多次, 一个让每次造出来的行为可以不同。

七、动手 Demo:模板通用化与配置管理

这个 Demo 把前几篇的页面收进一个 base 模板,再把配置写成类的层次。 最后一节对比 DevConfig 与 ProdConfig 加载之后,哪些开关变了。

from web.config import DevConfig, ProdConfig

app = create_app()
app.config.from_object(DevConfig)
print(app.config["DEBUG"])          # True

app2 = create_app()
app2.config.from_object(ProdConfig)
print(app2.config["DEBUG"])         # False
print(app2.config["SESSION_COOKIE_SECURE"])  # True

实跑输出:

--------------------------------------------------------------------
模板通用化与配置管理
--------------------------------------------------------------------

  ① 配置写成一个类层次
  --------------------------------------------------------------------

  [代码原样]
  | class BaseConfig:
  |     SECRET_KEY = os.environ.get("SECRET_KEY", "dev-key")
  |
  | class DevConfig(BaseConfig):
  |     DEBUG = True
  |
  | class ProdConfig(BaseConfig):
  |     DEBUG = False
  |     SESSION_COOKIE_SECURE = True
  公共项放基类,差异项放子类。

  from_object 是把类里的「大写属性」读进配置,方法和属性都不算。
  用 DevConfig -> DEBUG=True,URI=sqlite:///dev.db
  用 ProdConfig -> DEBUG=False,SESSION_COOKIE_SECURE=True
  ! 两个应用的代码一模一样,差别只在「加载了哪个配置类」。这就是把配置从代码里抽出来的全部意义。

  ② 配置从哪来、按什么顺序
  --------------------------------------------------------------------

| 顺序 | 来源 |
|---|---|
| 1 | Flask 内置默认值 |
| 2 | config.from_object / from_mapping |
| 3 | config.from_prefixed_env() |
| 4 | config.from_pyfile("instance/config.py") |

  后面的覆盖前面的。所以「实例目录里的配置」最硬——它是给这台机器专用的,一般不进仓库。

  ③ 模板通用化:base 收公共部分
  --------------------------------------------------------------------
  <!doctype html>
  <html><head>
  <title>首页 · 后台</title>
  <link rel="stylesheet" href="/static/css/app.css">
  </head><body>
  <header><h1>我的站点</h1></header>



  <main><p>用户共 2 人</p></main>
  </body></html>
  ! 标题是「首页 · 后台」——那个「后台」就是 super() 拿回来的父模板内容。
  这是 base 模板最实用的一个技巧。

  ④ flash 那段为什么放 base 里
  --------------------------------------------------------------------
  所有页面都要显示提示,所以放 base。
  放子模板就得每张页面复制一遍,改一次颜色要改十处。
  第二点才是关键:公共部分不是「长得一样」,是「改一处要生效到所有页面」。

第三节渲染出来的整页 HTML,就是「base 收公共、子模板填差异」的结果。 标题里那个「后台」是 super() 拿回来的,子模板自己没写。 第四节两个应用代码完全相同,只有加载的配置类不一样——这就是配置分层的全部收益。

30 秒实验

  1. 给 base.html 再加一个 block footer,让所有子模板都能填自己的页脚。
  2. 把 ProdConfig 也加载一遍,看 DEBUG 与 SESSION_COOKIE_SECURE 变成什么。
  3. 想一想:为什么密钥的默认值不能是一个固定的字符串?

常见误区

  1. 子模板 block 名字写错:那段内容静默丢失,页面少一块但不报错。
  2. flash 渲染写在每个子模板里:改一次样式要改十处,漏一处就不显示。
  3. 配置项写成小写:from_object 读不到,而且不报任何错。
  4. 密钥写死在代码里:进仓库等于公开,签名等于没有。
自测三问(点开对答案) 1. super() 拿回来的是什么? —— 父模板里同名 block 的内容。 2. from_object 读哪些属性? —— 只读大写的类属性,方法和小写属性都跳过。 3. 配置加载顺序里,什么最先被覆盖? —— Flask 内置默认值最先,后面的来源一层层盖上去。

第 2 章到此结束。下一章写登录:蓝图、flask-login、注册、登入、登出。

相关笔记