模板继承解决的是「导航条和页脚写几遍」的问题: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> 传进模板,输出会变成<script>——尖括号被换掉了,
浏览器只当普通文字。
这是 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><script>alert(1)</script></p><p><script>alert(1)</script></p>
! 第一行被转成 <script>,第二行加了 |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') 现算的,三件商品相加对得上。
转义那一段把两种写法并排输出:一个变成 <script>,一个原样。
TemplateNotFound 只会因为两件事:文件不存在,或者 template_folder 指错了。
30 秒实验
- 在 base 里加第三个 block(比如
{% block footer %}),子模板不填它,看会输出什么。 - 把
{{ title }}改成{{ title|upper }},看中文标题会不会变(答案是不变)。 - 给
Item加一个空价格None,用|default(0)兜一下,看渲染结果。
常见误区
{% extends %}前面有空格或换行:会报「子模板没有输出」之类的怪错。- 到处加
|safe:等于关掉 XSS 防护,用户输入能直接执行。 - 手写
/static/...:改了静态路径就全断。 - 在模板里做复杂计算:能算,但不该算,视图层算好再传进来。
自测三问(点开对答案)
1. `{{ }}` 和 `{% %}` 差在哪? —— 双花括号输出内容,百分号花括号只执行控制语句、不输出。 2. 为什么加 `|safe` 要谨慎? —— 它关掉自动转义,未经清洗的用户输入会变成可执行脚本,等于开了 XSS 口子。 3. `TemplateNotFound` 一般是什么原因? —— 模板文件不在模板目录里,或者 `template_folder` 参数指错了目录。页面能拼了,但还不能收用户输入。 下一篇讲请求内容:查询串、表单、JSON、文件四种取法,以及 WTForms 的校验。
- ☐ (本篇完,下一篇 1.6 表单与请求内容)