现在位置: 首页 > Flask 教程 > 正文

Flask 模板渲染

在 Python 代码中拼接 HTML 字符串既繁琐又危险(容易产生 XSS 漏洞),Flask 集成了 Jinja2 模板引擎来解决这个问题。

模板让你将业务逻辑和页面展示分离,写出更安全、更易维护的代码。


render_template 基础

render_template() 是 Flask 中最常用的模板渲染函数。

它的第一个参数是模板文件名,后面的关键字参数会作为变量传递给模板。

实例

# 文件路径:app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # 渲染 templates/index.html 模板,传递 title 和 name 变量
    return render_template("index.html", title="RUNOOB 首页", name="World")

@app.route("/user/<username>")
def profile(username):
    # 将 URL 中的 username 传递给模板
    return render_template("user.html", username=username, posts=[
        {"title": "Flask 入门", "date": "2026-05-01"},
        {"title": "Jinja2 模板", "date": "2026-05-10"},
    ])

模板文件放在项目目录下的 templates/ 文件夹中:

runoob-flask-test/
├── app.py
└── templates/
    ├── index.html
    └── user.html

index.html 内容为:

实例( index.html )

<!-- 文件路径:templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }} - RUNOOB</title>
</head>
<body>
    <h1>{{ title }} </h1>
     <p>Hello {{ name }}。</p>
</body>
</html>

访问 http://127.0.0.1:5000/ ,输出结果为:


Jinja2 基础语法

Jinja2 模板中三种核心语法:

语法 用途 示例
{{ ... }} 输出变量值,自动转义 HTML {{ username }}
{% ... %} 控制语句(if、for、block 等) {% if user %}
{# ... #} 注释,不会出现在渲染结果中 {# 这是注释 #}

模板示例

实例( user.html )

<!-- 文件路径:templates/user.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ username }} - RUNOOB</title>
</head>
<body>
    <h1>{{ username }} 的个人主页</h1>

    <!-- 条件判断:如果 posts 列表为空,显示提示信息 -->
    {% if posts %}
        <h2>最近文章:</h2>
        <ul>
        <!-- 循环遍历 posts 列表 -->
        {% for post in posts %}
            <!-- loop.index 是 Jinja2 内置变量,从 1 开始的序号 -->
            <li>{{ loop.index }}. {{ post.title }} ({{ post.date }})</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>暂无文章</p>
    {% endif %}

    <!-- 使用过滤器:upper 将文本转为大写 -->
    <p>用户名大写:{{ username|upper }}</p>
</body>
</html>

常用过滤器

过滤器用于修改变量的输出格式,使用管道符 | 调用。

过滤器 作用 示例 输出
upper 转大写 {{ "hello"|upper }} HELLO
lower 转小写 {{ "HELLO"|lower }} hello
title 首字母大写 {{ "hello world"|title }} Hello World
length 获取长度 {{ [1,2,3]|length }} 3
default 设置默认值 {{ name|default("匿名") }} 匿名(当 name 为空时)
safe 标记为安全 HTML(不转义) {{ "粗体"|safe }} 渲染为粗体文字
join 连接列表 {{ ["a","b"]|join(",") }} a,b

安全警告safe 过滤器会关闭自动转义,只在绝对信任数据来源时使用。对于用户输入的内容,永远不要使用 safe


自动转义——XSS 防护

Jinja2 默认会对所有变量输出进行 HTML 转义,这是 Web 安全的一道关键防线。

例如,如果用户提交的用户名是 <script>alert("xss")</script>,模板渲染时会自动转换为安全文本:

<!-- 模板中直接使用变量 -->
<p>{{ username }}</p>

<!-- 渲染结果(HTML 特殊字符已被转义) -->
&lt;p&gt;&amp;lt;script&amp;gt;alert(&amp;quot;xss&amp;quot;)&amp;lt;/script&amp;gt;&lt;/p&gt;

转义适用于 .html.htm.xml.xhtml.svg 结尾的模板文件。


模板继承——消除重复代码

模板继承是 Jinja2 最强大的功能之一,它让你可以定义一个「基础布局」,然后通过子模板填充内容。

这避免了在每个页面中重复编写头部、导航、页脚等公共部分。

基础模板(父模板)

实例

<!-- 文件路径:templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}RUNOOB Flask 教程{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>RUNOOB Flask 教程</h1>
        <nav>
            <a href="/">首页</a> |
            <a href="/about">关于</a>
        </nav>
    </header>

    <main>
    <!-- block 是占位区域,子模板可以填充或覆盖 -->
    {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2026 RUNOOB.com</p>
    </footer>
</body>
</html>

子模板(继承父模板)

实例

<!-- 文件路径:templates/index.html -->
{% extends "base.html" %}

<!-- 覆盖父模板的 title block -->
{% block title %}RUNOOB 教程 - 首页{% endblock %}

<!-- 填充父模板的 content block -->
{% block content %}
    <h2>欢迎来到 RUNOOB</h2>
    <p>{{ greeting }}</p>

    {% if user %}
        <p>当前用户:{{ user }}</p>
    {% else %}
        <p><a href="/login">请先登录</a></p>
    {% endif %}
{% endblock %}
标签 作用
{% extends "base.html" %} 声明当前模板继承自 base.html(必须放在第一行)
{% block name %}...{% endblock %} 定义一个可被子模板覆盖的区域
{{ super() }} 在子模板 block 中调用父模板同名 block 的内容

使用模板继承后,你只需要修改 base.html 一处,所有页面的公共部分(如导航、页脚)都会自动更新。


模板中的内置对象

以下 Flask 对象在模板中可以直接使用,无需通过 render_template() 传递:

对象 说明 模板中使用示例
request 当前请求对象 {{ request.path }}
session 当前会话数据 {{ session.get("username") }}
g 请求级全局变量 {{ g.user }}
config 应用配置 {{ config["APP_NAME"] }}
url_for() URL 生成函数 {{ url_for("index") }}
get_flashed_messages() 获取 flash 消息 {% for msg in get_flashed_messages() %}...

包含其他模板——include

使用 {% include %} 可以在一个模板中嵌入另一个模板:

实例

<!-- 文件路径:templates/_navbar.html(下划线开头表示这是一个局部模板) -->
<nav style="background:#f0f0f0;padding:10px;">
    <a href="/">首页</a> |
    <a href="/posts">文章</a> |
    <a href="/about">关于</a>
</nav>

实例

<!-- 在任意模板中引入导航栏 -->
<!DOCTYPE html>
<html>
<head><title>页面</title></head>
<body>
    {% include "_navbar.html" %}

    <h1>页面内容</h1>
    <p>这是页面主体内容。</p>
</body>
</html>

最佳实践:模板继承(extends)用于页面级的布局复用,include 用于组件级的代码复用。导航栏、侧边栏等可复用组件适合用 include。


完整示例:博客首页

将以上知识整合到一个实际场景中:

实例

# 文件路径:app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # 模拟数据库中的文章数据
    articles = [
        {"id": 1, "title": "Flask 入门指南", "author": "runoob", "views": 1024},
        {"id": 2, "title": "Jinja2 模板详解", "author": "admin", "views": 512},
        {"id": 3, "title": "RESTful API 设计", "author": "runoob", "views": 256},
    ]
    return render_template("blog.html", articles=articles)

实例

<!-- 文件路径:templates/blog.html -->
{% extends "base.html" %}

{% block title %}RUNOOB 博客{% endblock %}

{% block content %}
    <h2>最新文章</h2>

    {% if articles %}
        {% for article in articles %}
            <!-- 根据浏览数添加不同的样式 -->
            <div class="article" {% if article.views > 1000 %}style="background:#fff3cd;"{% endif %}>
                <h3>{{ article.title }}</h3>
                <p>作者:{{ article.author }} | 浏览:{{ article.views }}</p>
                <!-- 使用 loop 变量实现分隔线 -->
                {% if not loop.last %}
                    <hr>
                {% endif %}
            </div>
        {% endfor %}
    {% else %}
        <p>暂时没有文章,请稍后再来。</p>
    {% endif %}
{% endblock %}

Jinja2 特殊变量

变量 说明
loop.index 当前循环索引,从 1 开始
loop.index0 当前循环索引,从 0 开始
loop.first 是否为第一次循环
loop.last 是否为最后一次循环
loop.length 序列总长度