Jinja2 详细使用文档(配合wkhtmltoimage生成html图片)

Jinja2 详细教程与完整语法文档

Jinja2 是 Python 生态中最流行的模板引擎之一,广泛应用于 FlaskDjangoAnsibleFastAPIMicroDot(单片机物联网框架) 等框架和工具中。

导言

最近在使用Jinja2 +wkhtmltoimage ,实现通过html 生成图片的应用,其中需要Jinja2html 中传入变量值。所以这里总结一下Jinja2 使用文档

wkhtmltoimage生成html图片

这里贴一下html生成图片核心代码,需要用户自行安装wkhtmltopdf,并配置好环境变量

python 复制代码
import subprocess
import os
import sys


def generate_image(
    html, output_path, img_width="1400", img_quality="70", img_format="png"
):
    """
    将HTML转换为图片,依赖wkhtmltoimage

    参数:
        html (str): 要转换的HTML内容
        output_path (str): 输出图片路径

    返回:
        str: 成功返回输出路径,失败返回None

    异常:
        会捕获并处理各种可能的错误,打印错误信息
    """
    try:
        # 检查wkhtmltoimage是否可用
        try:
            subprocess.run(
                ["wkhtmltoimage", "--version"],
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
        except FileNotFoundError:
            raise RuntimeError("wkhtmltoimage未安装或未加入系统PATH环境变量")
        except subprocess.CalledProcessError:
            raise RuntimeError("wkhtmltoimage版本检查失败")

        # 检查输出目录是否存在
        output_dir = os.path.dirname(output_path) or "."
        if not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        # 准备命令参数
        cmd = [
            "wkhtmltoimage",
            "--format",
            img_format,
            "--width",
            img_width,
            "--quality",
            img_quality,
            "--disable-smart-width",  # 避免内容被截断
            "-",  # 表示从标准输入读取HTML
            output_path,
        ]

        # 执行转换
        process = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            # stdout=subprocess.PIPE,
            # stderr=subprocess.PIPE,
            universal_newlines=False,
        )

        # 写入HTML内容并等待完成
        stdout, stderr = process.communicate(input=html.encode("utf-8"))

        # 检查执行结果
        if process.returncode != 0:
            error_msg = stderr.decode("utf-8") if stderr else "未知错误"
            raise RuntimeError(
                f"图片生成失败 (返回码: {process.returncode}): {error_msg}"
            )

        # 检查输出文件是否生成
        if not os.path.exists(output_path):
            raise RuntimeError("图片文件未生成")

        # 检查文件是否有效
        if os.path.getsize(output_path) == 0:
            os.remove(output_path)
            raise RuntimeError("生成的图片文件为空")
        return output_path

    except Exception as e:
        # 打印错误信息
        print(f"错误: {str(e)}", file=sys.stderr)

        # 尝试清理可能生成的不完整文件
        if os.path.exists(output_path) and os.path.getsize(output_path) == 0:
            try:
                os.remove(output_path)
            except:
                pass

        return None

一、Jinja2快速入门

1.1 安装与基础使用

python 复制代码
pip install Jinja2
python 复制代码
from jinja2 import Template

# 简单模板渲染
template = Template('Hello, {{ name }}!')
result = template.render(name='World')
print(result)  # 输出: Hello, World!

1.2 核心语法标记

Jinja2 使用三种基本语法标记:

  • 变量输出{{ variable }}
  • 控制语句{% statement %}
  • 注释{# comment #}

二、完整语法详解

2.1 变量与表达式

变量使用双大括号输出,支持 Python 所有数据类型:

python 复制代码
<!-- 字符串 -->
<p>{{ username }}</p>

<!-- 字典访问 -->
<p>邮箱: {{ user.email }}</p>

<!-- 列表索引 -->
<p>第一个元素: {{ items[0] }}</p>

<!-- 对象方法调用 -->
<p>时间: {{ datetime.now().strftime('%Y-%m-%d') }}</p>

表达式支持:数学运算、比较运算、逻辑运算、三元表达式等:

python 复制代码
{{ 1 + 2 * 3 }}           {# 输出: 7 #}
{{ "hello" if True else "world" }}  {# 输出: hello #}
{{ user.age > 18 }}       {# 输出: True #}

2.2 控制结构

条件语句 (if/elif/else)
python 复制代码
{% if score >= 90 %}
    <p class="excellent">优秀</p>
{% elif score >= 60 %}
    <p class="pass">及格</p>
{% else %}
    <p class="fail">不及格</p>
{% endif %}
循环语句 (for)
python 复制代码
<ul>
{% for user in users %}
    <li>{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>

循环控制变量loop对象提供循环状态信息

python 复制代码
{% for item in items %}
    <p>
        当前索引: {{ loop.index }} (从1开始),
        当前索引0: {{ loop.index0 }} (从0开始),
        是否第一次: {{ loop.first }},
        是否最后一次: {{ loop.last }},
        剩余次数: {{ loop.revindex }}
    </p>
{% endfor %}
循环控制语句
python 复制代码
{% for item in items %}
    {% if loop.index > 10 %}
        {% break %}  {# 跳出循环 #}
    {% endif %}
    
    {% if item.hidden %}
        {% continue %}  {# 跳过当前项 #}
    {% endif %}
    
    {{ item.name }}
{% endfor %}

2.3 过滤器 (Filters)

过滤器通过管道符 |应用,支持链式调用:

python 复制代码
{{ "hello world" | upper }}          {# 转大写: HELLO WORLD #}
{{ "  hello  " | trim }}             {# 去空格: hello #}
{{ 3.14159 | round(2) }}             {# 四舍五入: 3.14 #}
{{ items | length }}                 {# 列表长度 #}
{{ html_content | safe }}            {# 禁用HTML转义 #}
{{ text | truncate(50) }}           {# 截断文本 #}
{{ list | join(', ') }}              {# 列表转字符串 #}
{{ value | default('N/A') }}         {# 默认值 #}

链式调用示例

python 复制代码
{{ "  hello world  " | trim | upper | truncate(5) }}
{# 输出: HELLO #}

2.4 测试器 (Tests)

测试器用于条件判断,语法为 is

python 复制代码
{% if number is even %}
    偶数
{% endif %}

{% if user is defined %}
    用户已定义
{% endif %}

{% if value is none %}
    值为空
{% endif %}

2.5 宏 (Macros)

宏是可重用的模板片段,类似函数:

python 复制代码
{# 定义宏 #}
{% macro input_field(name, value='', type='text', class='') %}
    <input type="{{ type }}" name="{{ name }}" 
           value="{{ value }}" class="{{ class }}">
{% endmacro %}

{# 使用宏 #}
{{ input_field('username') }}
{{ input_field('password', type='password', class='form-control') }}

带默认参数的宏

python 复制代码
{% macro card(title, content, color='primary') %}
    <div class="card card-{{ color }}">
        <h3>{{ title }}</h3>
        <p>{{ content }}</p>
    </div>
{% endmacro %}

2.6 模板继承

基础模板 (base.html)
python 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}默认标题{% endblock %}</title>
    {% block head %}{% endblock %}
</head>
<body>
    <header>{% block header %}导航栏{% endblock %}</header>
    
    <main>
        {% block content %}
            <p>默认内容</p>
        {% endblock %}
    </main>
    
    <footer>{% block footer %}页脚{% endblock %}</footer>
</body>
</html>
子模板继承
python 复制代码
{% extends "base.html" %}

{% block title %}子页面标题{% endblock %}

{% block head %}
    {{ super() }}  {# 保留父模板内容 #}
    <style>
        .custom-style { color: red; }
    </style>
{% endblock %}

{% block content %}
    <h1>子页面内容</h1>
    <p>这是子模板的内容</p>
{% endblock %}

继承要点

  • extends必须是模板的第一个标签
  • block定义可覆盖的区域
  • super()调用父模板的块内容
  • 未覆盖的块使用父模板默认内容

2.7 模板包含

python 复制代码
{# 包含其他模板文件 #}
{% include "header.html" %}

<main>主内容</main>

{% include "footer.html" %}

包含时可以传递变量:

python 复制代码
{% include "user_card.html" with user=current_user %}

2.8 变量设置

在模板中定义变量:

python 复制代码
{% set title = "页面标题" %}
{% set items = [1, 2, 3] %}

{{ title }}  {# 输出: 页面标题 #}

2.9 空白控制

控制模板渲染后的空白字符:

python 复制代码
{# 删除前导空白 #}
{% for item in items -%}
    {{ item }}
{%- endfor %}

{# 输出: item1item2item3 #}

2.10 注释

注释不会出现在渲染结果中:

python 复制代码
{# 这是单行注释 #}

{#
    这是多行注释
    不会输出到结果
#}

三、API 与高级用法

3.1 Environment 环境配置

python 复制代码
from jinja2 import Environment, FileSystemLoader

# 创建环境
env = Environment(
    loader=FileSystemLoader('templates'),  # 模板加载路径
    autoescape=True,                      # 自动HTML转义
    trim_blocks=True,                     # 删除块后换行
    lstrip_blocks=True,                   # 删除块前空格
    undefined=StrictUndefined            # 严格未定义变量处理
)

# 加载模板
template = env.get_template('index.html')

# 渲染
result = template.render(title='首页', users=user_list)

3.2 自定义过滤器

python 复制代码
from jinja2 import Environment

def reverse_string(value):
    """反转字符串"""
    return value[::-1]

def format_currency(amount):
    """格式化货币"""
    return f"¥{amount:.2f}"

# 注册过滤器
env = Environment()
env.filters['reverse'] = reverse_string
env.filters['currency'] = format_currency

# 使用
template = env.from_string('{{ text|reverse }} {{ price|currency }}')
result = template.render(text='hello', price=99.99)
print(result)  # 输出: olleh ¥99.99

3.3 自定义测试器

python 复制代码
def is_even(value):
    """判断是否为偶数"""
    return value % 2 == 0

def is_positive(value):
    """判断是否为正数"""
    return value > 0

env.tests['even'] = is_even
env.tests['positive'] = is_positive

# 模板中使用
# {% if number is even %}...{% endif %}

3.4 全局函数

python 复制代码
def get_current_time():
    """获取当前时间"""
    from datetime import datetime
    return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

env.globals['current_time'] = get_current_time

# 模板中直接调用
# {{ current_time() }}

四、内置过滤器列表(常用)

Jinja2 常用内置过滤器列表

过滤器 说明 示例代码 输出结果
abs 绝对值 `{{ -5 abs }}`
capitalize 首字母大写 `{{ "hello" capitalize }}`
default 设置默认值 `{{ value default("N/A") }}`
escape HTML转义 `{{ "<script>" escape }}`
first 获取第一个元素 `{{ [1,2,3] first }}`
float 转为浮点数 `{{ "3.14" float }}`
int 转为整数 `{{ "42" int }}`
join 列表连接为字符串 `{{ [1,2,3] join(',') }}`
last 获取最后一个元素 `{{ [1,2,3] last }}`
length 获取长度 `{{ "hello" length }}`
lower 转小写 `{{ "HELLO" lower }}`
map 映射列表 `{{ users map(attribute='name') }}`
random 随机选择元素 `{{ [1,2,3] random }}`
replace 字符串替换 `{{ "hello" replace('h', 'j') }}`
reverse 反转 `{{ "hello" reverse }}`
round 四舍五入 `{{ 3.14159 round(2) }}`
safe 标记为安全HTML `{{ html_content safe }}`
slice 切片操作 `{{ [1,2,3,4] slice(1,3) }}`
sort 排序 `{{ [3,1,2] sort }}`
striptags 去除HTML标签 `{{ "hello" striptags }}`
sum 求和 `{{ [1,2,3] sum }}`
title 每个单词首字母大写 `{{ "hello world" title }}`
trim 去除首尾空格 `{{ " hello " trim }}`
truncate 截断文本 `{{ text truncate(20) }}`
unique 去重 `{{ [1,2,2,3] unique }}`
upper 转大写 `{{ "hello" upper }}`
urlencode URL编码 `{{ "hello world" urlencode }}`

五、内置测试器列表

测试器 说明 示例
defined 是否已定义 {% if var is defined %}
divisibleby 是否可被整除 {% if 10 is divisibleby(5) %}
even 是否为偶数 {% if num is even %}
iterable 是否可迭代 {% if obj is iterable %}
lower 是否小写 {% if str is lower %}
mapping 是否为映射类型 {% if obj is mapping %}
none 是否为None {% if val is none %}
number 是否为数字 {% if val is number %}
odd 是否为奇数 {% if num is odd %}
sameas 是否相同对象 {% if obj1 is sameas(obj2) %}
sequence 是否为序列 {% if obj is sequence %}
string 是否为字符串 {% if val is string %}
undefined 是否未定义 {% if var is undefined %}
upper 是否大写 {% if str is upper %}

六、实战示例

6.1 用户列表页面

Python 代码

python 复制代码
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('user_list.html')

users = [
    {'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'},
    {'name': '李四', 'age': 30, 'email': 'lisi@example.com'},
    {'name': '王五', 'age': 28, 'email': 'wangwu@example.com'}
]

result = template.render(users=users, title='用户列表')
print(result)

模板文件 (user_list.html)

python 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        tr:nth-child(even) { background-color: #f9f9f9; }
    </style>
</head>
<body>
    <h1>{{ title }}</h1>
    
    <table>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>邮箱</th>
        </tr>
        
        {% for user in users %}
        <tr>
            <td>{{ loop.index }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.age }}</td>
            <td>{{ user.email }}</td>
        </tr>
        {% else %}
        <tr>
            <td colspan="4">暂无用户数据</td>
        </tr>
        {% endfor %}
    </table>
    
    <p>共 {{ users|length }} 条记录</p>
</body>
</html>

6.2 配置文件生成

数据文件 (config.yaml)

python 复制代码
servers:
  - name: web-server
    ip: 192.168.1.10
    port: 80
    services: ['nginx', 'php-fpm']
  - name: db-server
    ip: 192.168.1.20
    port: 3306
    services: ['mysql']

模板文件 (server.conf.j2)

python 复制代码
# 自动生成的服务器配置
# 生成时间: {{ now() }}

{% for server in servers %}
server {
    server_name {{ server.name }};
    listen {{ server.ip }}:{{ server.port }};
    
    {% for service in server.services %}
    # {{ service }} 服务配置
    {% endfor %}
}
{% endfor %}

生成脚本

python 复制代码
import yaml
from jinja2 import Environment, FileSystemLoader
from datetime import datetime

def now():
    return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# 加载数据
with open('config.yaml', 'r') as f:
    data = yaml.safe_load(f)

# 配置环境
env = Environment(loader=FileSystemLoader('templates'))
env.globals['now'] = now

# 渲染模板
template = env.get_template('server.conf.j2')
config_content = template.render(servers=data['servers'])

# 保存结果
with open('nginx.conf', 'w') as f:
    f.write(config_content)

结尾

挺好用的

相关推荐
我想吃烤肉肉9 小时前
Python 中 asyncio 是什么?
爬虫·python·自动化
林太白9 小时前
ofd文件
前端·后端
闲云一鹤9 小时前
Git 焚决!一个绝招助你找回丢失的代码文件!
前端·git
小宇的天下9 小时前
Calibre 3Dstack--每日一个命令day 6 [process和export layout](3-6)
java·前端·数据库
咕噜签名-铁蛋9 小时前
英伟达旗下
python
皮肤科大白9 小时前
图像处理的 Python库
图像处理·人工智能·python
冴羽10 小时前
2025 年最火的前端项目出炉,No.1 易主!
前端·javascript·node.js
wordbaby10 小时前
Flexbox 布局中的滚动失效问题:为什么需要 `min-h-0`?
前端·css
demo007x10 小时前
在国内也能使用 Claude cli给自己提效,附实操方法
前端·后端·程序员
FL162386312910 小时前
基于yolo11实现的车辆实时交通流量进出统计与速度测量系统python源码+演示视频
开发语言·python·音视频