Jinja2 template engine (Part 1): variable rendering, looping and conditional judgment

📂 Stage: Stage 1 - Breaking the ice and setting sail (Basics) 🔗 Related chapters: 路由(Routing)艺术 · Jinja2 模板引擎(下)


1. First understand: what is Jinja2? Why use it?

Jinja2 is the default integrated and out-of-the-box template engine in the Flask ecosystem. Simply put, its task is to help you "stuff" the data obtained by the back-end Python into the HTML skeleton to generate the web page that the user finally sees.

Using the traditional method to directly spell HTML strings in Python will not only make the code messy, but also prone to XSS security issues. Jinja2 uses two sets of clear tags to completely separate "content to be displayed" and "control logic":

  • {{ 变量 / 表达式 }}— Tell the engine: Output a value here (automatic escaping to prevent attacks)
  • {% 逻辑 / 控制结构 %}— Tell the engine: Execute conditions/loops/macro definitions here

The following Flask code shows the most typical usage:

# 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. The first step: basic variable rendering

"Pasting" the back-end data to the page is the most basic task of the template.

2.1 Simple variables, attributes, and dictionary indexes

The way Jinja2 accesses data is as intuitive as Python, whether it is attributes or dictionary keys, dots or square brackets:

<!-- 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 Practical Filters: "One-click processing" for variables

Filters are a set of variable processing gadgets built into Jinja2, such as the Linux pipe character|Chain stacking is also possible.

Assume that the backend passes this data: 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 High frequency built-in filter cheat sheet

FilterDescriptionExamples and expected results
upper / lowerConvert all to uppercase/lowercase`"hello"
trimRemove leading and trailing spaces, newlines, and tabs`" hi "
capitalizeCapitalize the first letter, lowercase the rest`"hELLO"
default(val)Returns the default value when the variable is undefined/empty/None`None
safe⚠️ Marked as "Safe HTML" to skip XSS protectionUse only for your own or moderated content
striptagsRemove all HTML tags`""
join(delimiter)Join lists, tuples, or collections with specified delimiters`[1,2,3]
round(n)Round to n decimal places`3.14159
lengthGet the length of string, list, dictionary`["a","b"]
tojson⚠️ Convert Python data into JSON string (for front-end use)`{"a":1}

3. The first step of logic control: conditional judgment{% if %}

Let the template render different HTML fragments based on different backend data.

3.1 Complete branchif / elif / else

It can be nested, but for readability, it is strongly recommended to control it within 2-3 layers**.

<!-- 假设后端传了 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 Common judgment operators

Jinja2's operators are almost identical to those of Python, and it also supports some convenient "template tests".

<!-- 假设后端传了 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. Logic control core: loop{% for %}

Traverse the list and dictionary passed from the backend and render the data one by one.

4.1 Basic loop: traversing the list

<!-- 假设后端传了 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-specific variablesloop

In each cycle, Jinja2 will quietly give you a set ofloopVariables help you easily handle common needs such as serial numbers and head and tail judgments.

<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 Traverse dictionary:items() / keys() / values()

Just like Python, there are three methods to choose from.

<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 Empty loop:{% else %}

Here'selseis following closelyforLater, ** will only be triggered when the loop is not executed once (for example, the list is empty).

<section>
  <h2>草稿箱</h2>
  <ul class="draft-list">
    {% for draft in drafts %}
      <li>{{ draft.title }}</li>
    {% else %}
      <p>🎉 暂无草稿!</p>
    {% endfor %}
  </ul>
</section>

5. Code reuse tool: macros{% macro %}

Macros are like functions in Python, encapsulating recurring HTML fragments, defining them in one place and calling them everywhere to avoid copying and pasting.

5.1 Define macros

It is recommended to create a new onetemplates/macros.html, put the globally common macros in it.

<!-- 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 Import macros

You can import the entire file or only the required macros (the latter is recommended, as it is more lightweight).

<!-- 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. Safety first: automatic escaping and block-level processing

6.1 Flask automatic escaping: prevent XSS attacks by default

Jinja2 under Flask will automatically convert HTML special characters (<>&"etc.) into a safe entity form. In this way, even if the backend accidentally passes in a malicious script, the browser will not execute it and will only display it in text form.

<!-- 假设后端传了:malicious_content="<script>alert('恶意弹窗!')</script>" -->
<p>{{ malicious_content }}</p>
<!-- 实际渲染结果:&lt;script&gt;alert('恶意弹窗!')&lt;/script&gt;,完全不会弹窗! -->

If the content you output is absolutely credible (such as static copy written by yourself), you can pass|safeFilter manual marking:

<p>{{ "<b>Flask 真好用!</b>"|safe }}</p>
<!-- 浏览器会显示加粗文字 -->

6.2 Block-level parsing-free:{% raw %}

Don’t want Jinja2 to move a whole piece of content, but just want to output it to the user as it is? use{% raw %}Just wrap it up.

<section>
  <h2>Jinja2 语法示例</h2>
  <pre>
    {% raw %}
      这里的 {{ 变量 }} 和 {% if %} 都不会被解析
      完全原封不动地显示给用户
    {% endraw %}
  </pre>
</section>

7. Quick summary

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') }}         直接访问请求参数

💡 Core Best Practices: Only do "Display related logic" (simple judgments, loops, variable filtering) in the template. All complex business logic and data processing are handed over to the back-end view functions or customized Jinja2 extensions. In this way, the responsibilities of the front-end and back-end are clearly divided, and the code is easier to maintain.


🔗 Extended reading