FlaskJinja2模板入门

Jinja2是Flask默认的模板引擎,功能强大且易于学习。本教程将带你掌握Jinja2的核心概念。

1. 什么是Jinja2模板引擎

Jinja2是一个功能强大、灵活的模板引擎,专门为Python设计。它允许你将动态数据嵌入到HTML模板中。

主要特性
  • 变量插值和表达式
  • 控制结构(循环、条件)
  • 模板继承和包含
  • 自定义过滤器和测试
  • 自动HTML转义
  • 可扩展性
使用场景
  • Web应用页面渲染
  • 邮件模板
  • 配置文件生成
  • 报告生成
  • 代码生成

2. 基本语法

2.1 变量插值

使用双花括号 {{ }} 来输出变量的值:

<!-- templates/user.html -->
<h1>欢迎,@{{ username }}!</h1>
<p>您的邮箱是:{{ email }}</p>
<p>登录时间:{{ login_time.strftime('%Y-%m-%d %H:%M') }}</p>
@

2.2 表达式

Jinja2支持多种表达式:

<!-- 数学运算 -->
<p>商品数量:{{ quantity }}</p>
<p>总价:{{ price * quantity }} 元</p>
<p>折扣价:{{ price * 0.8 if is_vip else price }}</p>

<!-- 字符串操作 -->
<p>用户名大写:{{ username.upper() }}</p>
<p>简介:{{ bio[:100] + '...' if bio|length > 100 else bio }}</p>

2.3 注释

使用 {# #} 添加注释:

{# 这是一个Jinja2注释,不会输出到HTML #}
<div class="user-profile">
    {# 显示用户头像 #}
    <img src="{{ avatar_url }}" alt="{{ username }}">

    {#
        多行注释示例
        这里可以写详细的说明
    #}
    <h2>{{ full_name }}</h2>
</div>

3. 控制结构

3.1 条件语句

使用 {% if %}{% elif %}{% else %}

<!-- 简单的if语句 -->
{% if user.is_authenticated %}
    <p>欢迎回来,{{ user.name }}!</p>
    <a href="/logout">退出登录</a>
{% else %}
    <p>请先<a href="/login">登录</a></p>
{% endif %}

<!-- 复杂的条件判断 -->
{% if score >= 90 %}
    <span class="badge bg-success">优秀</span>
{% elif score >= 60 %}
    <span class="badge bg-warning">及格</span>
{% else %}
    <span class="badge bg-danger">不及格</span>
{% endif %}

3.2 循环语句

使用 {% for %} 循环:

<!-- 基本的for循环 -->
<ul>
{% for item in items %}
    <li>{{ item.name }} - ¥{{ item.price }}</li>
{% endfor %}
</ul>

<!-- 使用loop变量 -->
<table class="table">
    <thead>
        <tr>
            <th>#</th>
            <th>姓名</th>
            <th>成绩</th>
        </tr>
    </thead>
    <tbody>
    {% for student in students %}
        <tr class="{{ 'table-success' if loop.index is divisibleby 2 else '' }}">
            <td>{{ loop.index }}</td>
            <td>{{ student.name }}</td>
            <td>{{ student.score }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

<!-- 遍历字典 -->
<dl>
{% for key, value in config.items() %}
    <dt>{{ key }}</dt>
    <dd>{{ value }}</dd>
{% endfor %}
</dl>
loop变量在for循环中可用,包含以下属性: loop.index(从1开始)、loop.index0(从0开始)、 loop.firstloop.lastloop.length

4. 过滤器

过滤器用于修改变量的输出,使用管道符号 |

4.1 内置过滤器

<!-- 字符串过滤器 -->
<p>原始:{{ text }}</p>
<p>大写:{{ text|upper }}</p>
<p>小写:{{ text|lower }}</p>
<p>标题化:{{ text|title }}</p>
<p>首字母大写:{{ text|capitalize }}</p>
<p>截断:{{ text|truncate(50) }}</p>

<!-- 列表过滤器 -->
<p>列表长度:{{ items|length }}</p>
<p>第一个元素:{{ items|first }}</p>
<p>最后一个元素:{{ items|last }}</p>
<p>排序:{{ items|sort }}</p>
<p>连接:{{ items|join(', ') }}</p>

<!-- 其他常用过滤器 -->
<p>默认值:{{ value|default('暂无数据') }}</p>
<p>四舍五入:{{ 3.14159|round(2) }}</p>
<p>绝对值:{{ -5|abs }}</p>
<p>安全HTML:{{ html_content|safe }}</p>
<p>转义HTML:{{ user_input|e }}</p>

4.2 过滤器链

可以连续使用多个过滤器:

<p>{{ content|truncate(100)|striptags|safe }}</p>
<p>{{ datetime|date('Y-m-d H:i:s') }}</p>
<p>{{ items|selectattr('active')|list|length }} 个活跃项目</p>

5. 模板继承

模板继承是Jinja2最强大的功能之一,可以创建基础模板,然后在子模板中扩展。

5.1 基础模板 (base.html)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}默认标题{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    {% block head_extra %}{% endblock %}
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">我的网站</a>
            {% block navbar %}{% endblock %}
        </div>
    </nav>

    <div class="container mt-4">
        {% block content %}
            <!-- 默认内容 -->
            <p>这里是默认内容。</p>
        {% endblock %}
    </div>

    <footer class="mt-5 py-3 bg-light">
        <div class="container text-center">
            {% block footer %}
                <p>&copy; 2023 我的网站。保留所有权利。</p>
            {% endblock %}
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    {% block scripts %}{% endblock %}
</body>
</html>

5.2 子模板继承 (home.html)

{% extends "base.html" %}

{% block title %}首页 - 我的网站{% endblock %}

{% block head_extra %}
    <link rel="stylesheet" href="/css/home.css">
{% endblock %}

{% block navbar %}
    <ul class="navbar-nav me-auto">
        <li class="nav-item">
            <a class="nav-link active" href="/">首页</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="/about">关于</a>
        </li>
    </ul>
{% endblock %}

{% block content %}
    <h1 class="mb-4">欢迎来到我的网站!</h1>

    {{ super() }}  {# 调用父模板中的content块内容 #}

    <div class="row">
    {% for product in products %}
        <div class="col-md-4 mb-3">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">{{ product.name }}</h5>
                    <p class="card-text">{{ product.description }}</p>
                    <span class="badge bg-primary">¥{{ product.price }}</span>
                </div>
            </div>
        </div>
    {% endfor %}
    </div>
{% endblock %}

{% block scripts %}
    <script src="/js/home.js"></script>
{% endblock %}

6. 宏(Macros)

宏类似于函数,可以重用HTML代码片段。

6.1 定义和使用宏

{# 在macros.html中定义宏 #}
{% macro render_product_card(product) %}
    <div class="card product-card">
        <img src="{{ product.image_url or '/images/default.png' }}"
             class="card-img-top"
             alt="{{ product.name }}">
        <div class="card-body">
            <h5 class="card-title">{{ product.name }}</h5>
            <p class="card-text">{{ product.description|truncate(100) }}</p>
            <div class="d-flex justify-content-between align-items-center">
                <span class="h5 text-danger">¥{{ product.price }}</span>
                <a href="/product/{{ product.id }}" class="btn btn-primary">查看详情</a>
            </div>
        </div>
    </div>
{% endmacro %}

{% macro render_user_avatar(user, size='md') %}
    {% set size_class = {
        'sm': 'avatar-sm',
        'md': 'avatar-md',
        'lg': 'avatar-lg'
    } %}
    <div class="avatar {{ size_class.get(size, 'avatar-md') }}">
        <img src="{{ user.avatar }}" alt="{{ user.username }}">
        {% if user.is_online %}
            <span class="online-dot"></span>
        {% endif %}
    </div>
{% endmacro %}

6.2 导入和使用宏

{# 在模板中导入宏 #}
{% from 'macros.html' import render_product_card, render_user_avatar %}

<!-- 使用宏 -->
<div class="user-profile">
    {{ render_user_avatar(current_user, 'lg') }}
    <h2>{{ current_user.username }}</h2>
</div>

<div class="row">
{% for product in products %}
    <div class="col-md-4 mb-4">
        {{ render_product_card(product) }}
    </div>
{% endfor %}
</div>

7. 最佳实践

推荐做法
  • 使用模板继承保持一致性
  • 将复杂逻辑移到视图函数中
  • 使用宏减少重复代码
  • 对用户输入使用 |e 过滤器
  • 使用 include 引入公共组件
  • 为模板添加适当的注释
避免做法
  • 不要在模板中写复杂业务逻辑
  • 避免过深的嵌套继承
  • 不要信任用户输入的HTML
  • 避免在模板中直接查询数据库
  • 不要忘记处理空值情况
  • 避免硬编码路径和URL
实战示例:用户列表页面
{% extends "base.html" %}

{% block title %}用户管理{% endblock %}

{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1>用户列表</h1>
    <a href="/users/add" class="btn btn-primary">添加用户</a>
</div>

{% if users %}
    <table class="table table-striped table-hover">
        <thead>
            <tr>
                <th>ID</th>
                <th>用户名</th>
                <th>邮箱</th>
                <th>角色</th>
                <th>注册时间</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
        {% for user in users %}
            <tr>
                <td>{{ user.id }}</td>
                <td>
                    <img src="{{ user.avatar }}" class="rounded-circle me-2" width="30">
                    {{ user.username }}
                    {% if user.is_vip %}
                        <span class="badge bg-warning ms-2">VIP</span>
                    {% endif %}
                </td>
                <td>{{ user.email }}</td>
                <td>
                    <span class="badge {{ 'bg-info' if user.role == 'admin' else 'bg-secondary' }}">
                        {{ user.role|title }}
                    </span>
                </td>
                <td>{{ user.created_at|date('Y-m-d') }}</td>
                <td>
                    <a href="/users/{{ user.id }}" class="btn btn-sm btn-outline-primary">查看</a>
                    <a href="/users/{{ user.id }}/edit" class="btn btn-sm btn-outline-warning">编辑</a>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

    <!-- 分页 -->
    {% if pagination %}
        <nav>
            <ul class="pagination justify-content-center">
                {% if pagination.has_prev %}
                    <li class="page-item">
                        <a class="page-link" href="?page={{ pagination.prev_num }}">上一页</a>
                    </li>
                {% endif %}

                {% for page in pagination.iter_pages() %}
                    {% if page %}
                        <li class="page-item {{ 'active' if page == pagination.page else '' }}">
                            <a class="page-link" href="?page={{ page }}">{{ page }}</a>
                        </li>
                    {% else %}
                        <li class="page-item disabled">
                            <span class="page-link">...</span>
                        </li>
                    {% endif %}
                {% endfor %}

                {% if pagination.has_next %}
                    <li class="page-item">
                        <a class="page-link" href="?page={{ pagination.next_num }}">下一页</a>
                    </li>
                {% endif %}
            </ul>
        </nav>
    {% endif %}
{% else %}
    <div class="alert alert-info">
        <i class="fas fa-info-circle me-2"></i>
        暂无用户数据。
    </div>
{% endif %}
{% endblock %}
学习总结

通过本教程,你已经掌握了Jinja2模板引擎的核心功能:

  • 变量插值:使用 {{ }} 输出动态内容
  • 控制结构{% if %}{% for %}
  • 过滤器:使用管道符 | 修改变量输出
  • 模板继承{% extends %}{% block %}
  • :定义可重用的模板片段
下一步学习建议
  • 学习Flask的表单处理与验证
  • 掌握Flask的数据库集成(SQLAlchemy)
  • 学习Flask的用户认证与授权
  • 了解Flask的REST API开发
  • 实践Flask项目部署