🧪 第1章 建立最基础的应用程序
📄 一句话概括

模板继承解决的是「导航条和页脚写几遍」的问题:base 挖坑,子模板填坑。 Jinja2 默认转义 HTML,|safe 是唯一的例外开关,能不加就不加。 模板找不到只有一种原因:文件不在,或者 template_folder 指错了。

📝 本章和前面几章的关系

这一篇对应教材 1.2.6、1.2.8、1.2.9 三节:模板引擎、url_for、静态文件。 上一篇管「地址怎么对」,这一篇管「页面怎么拼」,两篇合起来才是一个能看的网站。

一、模板不是字符串拼接

最原始的做法是在函数里拼 HTML 字符串。拼十行还行,拼一百行就没人敢改了。 模板引擎把「哪些地方会变」标出来,其余保持原样。 {{ }} 放变量,{% %} 放控制语句,{# #} 是注释。 这三组符号要背下来,但更要知道区别:双花括号输出内容,百分号花括号只执行、不输出。

把 {% for %} 写成 {{ for }} 是新手第一个语法错,报的错还很难懂。

二、继承:base 挖坑,子模板填坑

base.html 里用 {% block 名字 %} 挖若干坑,子模板用同名 block 填。 子模板第一行必须是 {% extends "base.html" %},而且它前面不能有任何输出——空格都不行。 一个页面只能 extends 一个模板,但可以被无数个模板 extends。所以 base 是所有页面的共同祖先。

想保留父模板的内容再加东西,用 {{ super() }}。这在「标题后面统一加站点名」这类需求上很好用。 第 2 章做 CRUD 时,六张页面全部长在这个 base 上,那时才能体会到继承省了多少行。

三、过滤器:管道右边的函数

写法是变量加竖线再接函数名。竖线念「交给」——把左边的值交给右边处理。

过滤器 作用 容易错的地方
length 取长度 字典取的是键数,不是字符数
round(1) 四舍五入 只对数字有效,字符串会报错
sum(attribute=…) 按属性求和 属性名要写字符串
default(…) 空值兜底 只兜 None,兜不住空字符串
safe 不转义 慎用,见下一节

过滤器可以链式串起来:price|round(1)|default(0)。左边先算,结果交给右边。 内置过滤器有五十多个,不用背。常用的十几个熟了,其余的用到时查。

四、转义:默认站在你这边

把一段 <script>alert(1)</script> 传进模板,输出会变成&lt;script&gt;——尖括号被换掉了, 浏览器只当普通文字。 这是 Jinja2 默认开启的自动转义,是它替你做的最重要的一件事。没有它, 任何用户输入都能变成一段可执行脚本。 加 |safe 就不转义了。什么时候该加? 只有当这段内容确实是你自己生成的可信 HTML(比如富文本编辑器的输出经过清洗之后)。

「经过清洗」这四个字是前提。没清洗就加 safe,等于把 XSS 漏洞写进代码。

五、静态文件与 url_for

CSS、JS、图片放在 static/ 目录下,Flask 自动提供 /static/... 访问。 模板里引用它们一律用 url_for('static', filename='css/app.css')。 不要手写 /static/css/app.css。哪天把 static_url_path 改成 /assets,手写的全断, 用 url_for 的一个都不用改。

静态文件在生产环境一般交给 Nginx 直接发,Flask 那个路由会关掉——这时候 url_for 生成的地址依然对, 因为它生成的是相对路径。

六、动手 Demo:模板引擎与 url_for

这一节建一个 base 加一个子模板,跑通继承、循环、过滤器和转义,最后故意把模板目录指错,看它报什么。

# base.html
{% block title %}默认标题{% endblock %}
{% block body %}{% endblock %}

# list.html
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
{% for it in items %}<li>{{ it.name }}</li>{% endfor %}
{% endblock %}

实跑输出:

--------------------------------------------------------------------
模板引擎与 url_for
--------------------------------------------------------------------

  ① 继承:子模板只写变的那块
  --------------------------------------------------------------------

  [代码原样]
  | {% extends "base.html" %}
  | {% block title %}{{ title }}{% endblock %}
  | {% block body %} … {% endblock %}
  base 里挖了两个 block,子模板只填这两个洞。

  <!doctype html>
  <html><head><title>商品清单</title></head>
  <body>
  <nav><a href="/">首页</a></nav>

  <ul>

    <li>1. 键盘 — 199.0</li>

    <li>2. 鼠标 — 89.5</li>

    <li>3. 显示器 — 1299.0</li>

  </ul>
  <p>总计 1587.5</p>

  <footer>共 3 条</footer>
  </body></html>
  ! 导航条和页脚只在 base 里写了一次。第 2 章做 CRUD 时,所有页面都会长在这个 base 上。

  ② 过滤器:模板里的函数
  --------------------------------------------------------------------

| 过滤器 | 作用 |
|---|---|
| round(1) | 四舍五入到一位小数 |
| length | 取长度 |
| sum(attribute='price') | 按属性求和 |
| upper | 转大写 |
| default('匿名') | 空值兜底 |

  用法是竖线接函数名:价格那块写成 price 加竖线再接 round(1)。
  竖线在这里念「交给」——把左边的值交给右边的函数处理。
  管道的右半边是函数名。过滤器可以做全局注册,第 5 章会用到。

  ③ 转义:默认是开的
  --------------------------------------------------------------------
  <p>&lt;script&gt;alert(1)&lt;/script&gt;</p><p><script>alert(1)</script></p>
  ! 第一行被转成 &lt;script&gt;,第二行加了 |safe 就原样输出。
  默认转义是 Flask 站在你这边的表现,别为了省事到处加 safe。

  ④ url_for 在模板里怎么用
  --------------------------------------------------------------------
  url_for('index') -> /
  url_for('static', filename='css/app.css') -> /static/css/app.css
  静态文件的地址不要手写 /static/…,用 url_for('static', …)。
  哪天把 static_url_path 改了,手写的全断,url_for 的不断。

  ⑤ 模板找不到会怎样
  --------------------------------------------------------------------
  模板目录指错 -> TemplateNotFound: list.html
  ! 报的是 TemplateNotFound。遇到它先查两件事:文件在不在这、template_folder 指的对不对。

导航条和页脚只在 base 里出现一次,子模板里干干净净。 「总计 1587.5」是 sum(attribute='price') 现算的,三件商品相加对得上。 转义那一段把两种写法并排输出:一个变成 &lt;script&gt;,一个原样。 TemplateNotFound 只会因为两件事:文件不存在,或者 template_folder 指错了。

30 秒实验

  1. 在 base 里加第三个 block(比如 {% block footer %}),子模板不填它,看会输出什么。
  2. 把 {{ title }} 改成 {{ title|upper }},看中文标题会不会变(答案是不变)。
  3. 给 Item 加一个空价格 None,用 |default(0) 兜一下,看渲染结果。

常见误区

  1. {% extends %} 前面有空格或换行:会报「子模板没有输出」之类的怪错。
  2. 到处加 |safe:等于关掉 XSS 防护,用户输入能直接执行。
  3. 手写 /static/...:改了静态路径就全断。
  4. 在模板里做复杂计算:能算,但不该算,视图层算好再传进来。
自测三问(点开对答案) 1. `{{ }}` 和 `{% %}` 差在哪? —— 双花括号输出内容,百分号花括号只执行控制语句、不输出。 2. 为什么加 `|safe` 要谨慎? —— 它关掉自动转义,未经清洗的用户输入会变成可执行脚本,等于开了 XSS 口子。 3. `TemplateNotFound` 一般是什么原因? —— 模板文件不在模板目录里,或者 `template_folder` 参数指错了目录。

页面能拼了,但还不能收用户输入。 下一篇讲请求内容:查询串、表单、JSON、文件四种取法,以及 WTForms 的校验。

相关笔记