4、Jinja2 模板初探:用 HTML 模板渲染动态内容,告别字符串拼接
说实话,我刚开始写 Web 应用的时候,也干过那种「傻事」——直接在 Python 代码里拼 HTML 字符串。
你想想看,一个简单的用户列表,我得这么写:
html = "<ul>"
for user in users:
html += f"<li>{user['name']} - {user['email']}</li>"
html += "</ul>"
return html
看着还行?等页面复杂起来,你就知道有多痛苦了。条件判断、循环嵌套、特殊字符转义……代码乱成一锅粥。我在一个老项目里接手过这种代码,改一个样式得翻半天,简直想哭。
后来我遇到了 Jinja2,嗯,世界清净了。
Jinja2 是什么?
说白了,Jinja2 就是一个模板引擎。它让你把 HTML 结构和 Python 数据分开管理。
模板文件长这样:
<!-- user_list.html -->
<ul>
{% for user in users %}
<li>{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>
Python 代码里只需要传数据:
from flask import render_template
@app.route('/users')
def users():
users_data = [
{'name': '张三', 'email': 'zhangsan@example.com'},
{'name': '李四', 'email': 'lisi@example.com'}
]
return render_template('user_list.html', users=users_data)
干净、清爽、好维护。我个人习惯把模板文件放在 templates/ 目录下,Flask 会自动找到它们。
核心语法:三分钟上手
Jinja2 的语法其实就三样东西,记住它们就够了:
| 语法 | 作用 | 示例 |
|---|---|---|
{{ 变量 }} |
输出变量值 | {{ username }} |
{% 语句 %} |
控制结构(if/for) | {% if user.is_admin %} |
{# 注释 #} |
模板注释,不会输出 | {# 这里是注释 #} |
举个例子,假设我们要渲染一个话题列表:
<!DOCTYPE html>
<html>
<head>
<title>话题列表</title>
</head>
<body>
<h1>最新话题</h1>
{% if topics %}
<ul>
{% for topic in topics %}
<li>
<strong>{{ topic.title }}</strong>
<span>作者:{{ topic.author }}</span>
<span>时间:{{ topic.created_at }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p>暂无话题,快来发布第一个吧!</p>
{% endif %}
</body>
</html>
你看,{% if topics %} 判断列表是否为空,{% for topic in topics %} 循环输出每个话题。逻辑清晰,一目了然。
loop.index 获取当前循环的序号(从1开始)。比如 {{ loop.index }}. {{ topic.title }} 就能生成带编号的列表。
过滤器:给数据加点「魔法」
有时候我们想对变量做点处理,比如把时间格式化成更友好的样子,或者把文本截断。Jinja2 的过滤器就是干这个的。
用法很简单:{{ 变量 | 过滤器 }}
<p>{{ content | truncate(50) }}</p> <!-- 截断到50个字符 -->
<p>{{ created_at | datetimeformat('%Y-%m-%d') }}</p> <!-- 格式化日期 -->
<p>{{ username | upper }}</p> <!-- 转大写 -->
常用的内置过滤器有这些:
safe— 标记为安全 HTML,不转义capitalize— 首字母大写length— 获取长度default('默认值')— 变量不存在时使用默认值join(', ')— 把列表拼接成字符串
< 变成 <)。这是为了防止 XSS 攻击。如果你确定内容是安全的,可以用 | safe 过滤器。我曾经见过有人滥用 | safe,结果用户提交的脚本直接执行了……嗯,别学他。
模板继承:告别重复代码
每个页面都有相同的头部、导航栏、底部版权信息。如果每个模板都复制一遍,改个链接就得改所有文件——太傻了。
Jinja2 的模板继承就是解决这个问题的。
先写一个基础模板 base.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}我的话题发布器{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<nav>
<a href="/">首页</a>
<a href="/topics">话题列表</a>
<a href="/publish">发布话题</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2025 话题发布器</p>
</footer>
</body>
</html>
然后子模板只需要填充 block 里的内容:
{% extends "base.html" %}
{% block title %}发布新话题 - {{ super() }}{% endblock %}
{% block content %}
<h1>发布新话题</h1>
<form method="POST">
<input type="text" name="title" placeholder="话题标题">
<textarea name="content" placeholder="话题内容"></textarea>
<button type="submit">发布</button>
</form>
{% endblock %}
{% extends "base.html" %} 告诉 Jinja2 继承哪个模板。{% block 名称 %} 定义可替换的区域。{{ super() }} 可以保留父模板中 block 的原有内容。
这样一来,改导航栏只需要改 base.html,所有页面自动更新。我在项目中一直用这个模式,省了不知道多少时间。
实战:渲染话题发布页面
结合我们之前学的 Flask 路由,来看看完整的流程:
# app.py
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
# 模拟数据存储
topics = []
@app.route('/')
def index():
return render_template('index.html', topics=topics)
@app.route('/publish', methods=['GET', 'POST'])
def publish():
if request.method == 'POST':
title = request.form.get('title')
content = request.form.get('content')
topics.append({
'title': title,
'content': content,
'author': '匿名用户',
'created_at': '2025-01-15'
})
return redirect(url_for('index'))
return render_template('publish.html')
if __name__ == '__main__':
app.run(debug=True)
对应的 publish.html 模板:
{% extends "base.html" %}
{% block content %}
<h2>发布新话题</h2>
<form method="POST">
<div>
<label>标题:</label>
<input type="text" name="title" required>
</div>
<div>
<label>内容:</label>
<textarea name="content" rows="5" required></textarea>
</div>
<button type="submit">发布</button>
</form>
{% endblock %}
你看,模板里没有一行 Python 代码,全是干净的 HTML。数据通过 render_template 的第二个参数传进去,Jinja2 自动渲染。
本章知识体系
下面这张图帮你理清 Jinja2 的核心脉络:
核心要点:
- 用
{{ }}输出变量,用{% %}写逻辑 - 过滤器让数据展示更灵活
- 模板继承消除重复代码
- Flask 的
render_template()自动加载模板目录下的文件
好了,现在你已经掌握了 Jinja2 的核心用法。从今天开始,别再在 Python 里拼 HTML 字符串了——那真的是在给自己挖坑。
可可精品资料库