Jinja2 模板引擎(上):变量渲染、循环与条件判断
📂 所属阶段:第一阶段 — 破冰启航(基础篇)
🔗 相关章节:路由(Routing)艺术 · Jinja2 模板引擎(下)
1. 先搞懂:Jinja2 是个啥?为啥要用?
Jinja2 是 Flask 生态里默认集成、开箱即用的模板引擎。简单说,它的任务就是帮你把后端 Python 拿到的数据「塞」进 HTML 骨架里,生成用户最终看到的网页。
用传统方式在 Python 里直接拼 HTML 字符串,不仅代码乱成一团,还容易出 XSS 安全问题。Jinja2 用两套清晰的标记,把「要展示的内容」和「控制逻辑」彻底分开:
{{ 变量 / 表达式 }} — 告诉引擎:这里输出一个值(自动转义,防攻击)
{% 逻辑 / 控制结构 %} — 告诉引擎:这里执行条件/循环/宏定义
下面这段 Flask 代码,展示了最典型的用法:
# Flask 会自动去项目根目录下的 templates/ 文件夹找同名 HTML 文件
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
# 用关键字参数把数据传给模板,也可以用 **context 解包字典
return render_template(
"index.html",
page_title="我的 Flask 博客首页",
current_user={"name": "Alice", "is_logged_in": True}
)
2. 第一步:基础变量渲染
把后端数据「贴」到页面上,就是模板最基础的任务。
2.1 简单变量、属性、字典索引
Jinja2 访问数据的方式和 Python 一样直观,无论属性还是字典键,点号或者方括号都行:
<!-- templates/index.html -->
<header>
<h1>{{ page_title }}</h1>
</header>
<main>
<!-- 点号访问属性或字典键(两者通用) -->
<p>Hi, {{ current_user.name }} 👋</p>
<!-- 方括号语法适合带特殊字符的键,比如 "user_id" -->
<p>邮箱前缀:{{ current_user["user_id"] }}</p>
</main>
2.2 实用过滤器(Filters):给变量「一键加工」
过滤器,就是 Jinja2 内置的一套变量处理小工具,像 Linux 管道符 | 一样可以链式叠加。
假设后端传了这些数据:
name=" alice ", price=29.99, bio=None, tags=["Flask", "Jinja2"]
<section>
<h2>变量处理小工具</h2>
<ul>
<li>全大写:{{ name|upper }}</li>
<!-- 先去首尾空格 → 再首字母大写 -->
<li>首字母大写:{{ name|trim|capitalize }}</li>
<!-- 为空时设置默认值 -->
<li>默认值:{{ bio|default("这位博主还没写简介") }}</li>
<li>数字四舍五入:{{ price|round(1) }}</li>
<!-- ⚠️ safe 仅用于绝对可信内容,比如你自己写的静态文案 -->
<li>安全 HTML:{{ "<b>这是加粗的安全内容</b>"|safe }}</li>
<li>去除 HTML 标签:{{ "<i>我是有HTML标签内容</i>"|striptags }}</li>
<li>列表连接成字符串:{{ tags|join(" · ") }}</li>
<li>变量长度:{{ tags|length }}</li>
</ul>
</section>
2.3 高频内置过滤器速查表
3. 逻辑控制第一步:条件判断 {% if %}
根据后端数据的不同,让模板渲染出不同的 HTML 片段。
3.1 完整分支 if / elif / else
可以嵌套使用,但为了可读性,强烈建议控制在 2-3 层以内。
<!-- 假设后端传了 current_user,包含 is_logged_in、role -->
<nav>
{% if current_user.is_logged_in %}
<span>👤 {{ current_user.name }}</span>
{% if current_user.role == "admin" %}
<a href="/admin">管理后台</a>
{% elif current_user.role == "editor" %}
<a href="/editor">编辑后台</a>
{% else %}
<a href="/profile">个人中心</a>
{% endif %}
<a href="/logout">退出登录</a>
{% else %}
<a href="/login">登录</a>
<a href="/register">注册</a>
{% endif %}
</nav>
3.2 常用判断运算符
Jinja2 的运算符几乎和 Python 一模一样,还额外支持一些方便的「模板测试」。
<!-- 假设后端传了 articles=[...] -->
<section>
<h2>运算符测试</h2>
<ul>
<!-- 普通比较和逻辑运算 -->
{% if current_user.age is defined and current_user.age >= 18 %}
<li>✅ 成年用户,可访问全部内容</li>
{% endif %}
{% if articles[0].views > 1000 and "Python" in articles[0].tags %}
<li>🔥 Python 热门文章推荐</li>
{% endif %}
{% if "banned" not in current_user.roles %}
<li>💬 可以发言评论</li>
{% endif %}
<!-- 模板测试用 is / is not -->
{% if current_user.bio is none %}
<li>ℹ️ 个人简介待完善</li>
{% endif %}
{% if articles is iterable %}
<li>📝 文章列表已准备好渲染</li>
{% endif %}
</ul>
</section>
4. 逻辑控制核心:循环 {% for %}
遍历后端传过来的列表、字典,把数据一条条渲染出来。
4.1 基础循环:遍历列表
<!-- 假设后端传了 articles 列表 -->
<section>
<h2>最新文章</h2>
<ul class="article-list">
{% for article in articles %}
<li>
<a href="{{ url_for('show_article', id=article.id) }}">
{{ article.title }}
</a>
<span class="meta">{{ article.published_at }} · {{ article.views }} 阅读</span>
</li>
{% endfor %}
</ul>
</section>
4.2 循环专属变量 loop
每个循环里 Jinja2 会悄悄给你一组 loop 变量,帮你轻松处理序号、首尾判断等常见需求。
<section>
<h2>带专属效果的文章列表</h2>
<div class="article-cards">
{% for article in articles %}
<div class="card {% if loop.first %}highlight{% endif %}">
<!-- 从 1 开始的序号 -->
<span class="index">#{{ loop.index }}</span>
<!-- 反向序号(最后一篇是 #1) -->
<span class="rev-index">(倒数第{{ loop.revindex }})</span>
<!-- 总篇数 -->
<span class="total">共{{ loop.length }}篇</span>
<h3>{{ article.title }}</h3>
<p>{{ article.summary }}</p>
</div>
{% endfor %}
</div>
</section>
4.3 遍历字典:items() / keys() / values()
和 Python 一样,三种方式随你选。
<section>
<h2>用户信息表</h2>
<table>
<thead>
<tr>
<th>属性名</th>
<th>属性值</th>
</tr>
</thead>
<tbody>
{% for key, value in current_user.items() %}
<tr>
<!-- 把下划线替换成空格,让显示更友好 -->
<td>{{ key|replace("_", " ") }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
4.4 空循环兜底:{% else %}
这里的 else 是紧跟在 for 后面的,只有当循环一次都没执行(比如列表为空)时才会触发。
<section>
<h2>草稿箱</h2>
<ul class="draft-list">
{% for draft in drafts %}
<li>{{ draft.title }}</li>
{% else %}
<p>🎉 暂无草稿!</p>
{% endfor %}
</ul>
</section>
5. 代码复用利器:宏 {% macro %}
宏就像 Python 里的函数,把反复出现的 HTML 片段封装起来,一处定义,到处调用,避免复制粘贴。
5.1 定义宏
建议新建一个 templates/macros.html,把全局通用的宏统一放在里面。
<!-- templates/macros.html -->
{# 宏:渲染单个文章卡片 #}
{% macro render_article_card(article, show_published=False) %}
<div class="card">
<h3>{{ article.title }}</h3>
<!-- truncate 是一个常用过滤器,截断文本并自动加省略号 -->
<p>{{ article.summary|truncate(100) }}</p>
{% if show_published %}
<span class="published-at">{{ article.published_at }}</span>
{% endif %}
<a href="{{ url_for('show_article', id=article.id) }}" class="btn">阅读更多</a>
</div>
{% endmacro %}
{# 宏:渲染单个表单字段,适合和 Flask-WTF 配合 #}
{% macro render_wtf_field(field) %}
<div class="form-group">
{{ field.label(class="form-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<small class="text-danger">{{ field.errors[0] }}</small>
{% endif %}
</div>
{% endmacro %}
5.2 导入宏
可以导入整个文件,也可以只导入需要的宏(推荐后者,更轻量)。
<!-- templates/articles.html -->
{# 只导入需要的宏 #}
{% from "macros.html" import render_article_card %}
<section>
<h2>精选文章</h2>
<div class="grid">
{% for article in featured_articles %}
{{ render_article_card(article, show_published=True) }}
{% endfor %}
</div>
</section>
6. 安全第一:自动转义与块级处理
6.1 Flask 自动转义:默认防 XSS 攻击
Flask 下的 Jinja2 会自动把 HTML 特殊字符(<、>、&、" 等)转义成安全的实体形式。这样即使后端不小心传入了恶意脚本,浏览器也不会执行,只会以文本形式显示。
<!-- 假设后端传了:malicious_content="<script>alert('恶意弹窗!')</script>" -->
<p>{{ malicious_content }}</p>
<!-- 实际渲染结果:<script>alert('恶意弹窗!')</script>,完全不会弹窗! -->
如果你输出的内容绝对可信(例如自己写的静态文案),可以通过 |safe 过滤器手动标记:
<p>{{ "<b>Flask 真好用!</b>"|safe }}</p>
<!-- 浏览器会显示加粗文字 -->
6.2 块级免解析:{% raw %}
不想让 Jinja2 动一整块内容,只想原样输出给用户看?用 {% raw %} 包起来就行。
<section>
<h2>Jinja2 语法示例</h2>
<pre>
{% raw %}
这里的 {{ 变量 }} 和 {% if %} 都不会被解析
完全原封不动地显示给用户
{% endraw %}
</pre>
</section>
7. 快速小结
Jinja2 核心语法速览(基础篇):
{{ variable }} 输出转义后的变量
{{ name|upper }} 变量过滤器(可以链式:name|trim|capitalize)
{% if cond %} ... {% endif %} 条件判断
{% for item in list %} 循环
{% for ... %} {% else %} {% endfor %} 循环兜底(列表为空时触发 else)
{% macro name() %} 宏定义(代码复用)
{% from "file.html" import ... %} 导入宏
{% raw %} {% endraw %} 块级免解析
{{ url_for('func') }} Flask 内置生成 URL 的函数
{{ session['key'] }} 直接访问 session
{{ request.args.get('q') }} 直接访问请求参数
💡 核心最佳实践:模板里只做「展示相关的逻辑」(简单的判断、循环、变量过滤)。所有复杂的业务逻辑、数据处理,全部交给后端视图函数,或者自定义 Jinja2 扩展。这样前后端职责划分清晰,代码也更容易维护。
🔗 扩展阅读