frappe-printing-templates
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrappe Printing & Templates
Frappe 打印与模板
Create print formats, email templates, and document templates using Jinja in Frappe.
在Frappe中使用Jinja创建打印格式、邮件模板和文档模板。
When to use
适用场景
- Creating custom print formats for documents
- Building email templates with dynamic content
- Generating PDFs from documents
- Using Jinja templating in web pages
- Configuring letter heads for branding
- Using the Print Format Builder
- 为文档创建自定义打印格式
- 构建含动态内容的邮件模板
- 从文档生成PDF
- 在网页中使用Jinja模板
- 配置品牌信头
- 使用打印格式构建器
Inputs required
所需输入
- Target DocType for the print format
- Layout requirements (fields, tables, headers)
- Whether format is standard (version controlled) or custom (DB-stored)
- Letter Head / branding requirements
- PDF generation needs
- 打印格式的目标DocType
- 布局要求(字段、表格、页眉)
- 格式类型为标准型(版本控制)还是自定义型(存储于数据库)
- 信头/品牌化要求
- PDF生成需求
Procedure
操作步骤
0) Choose format type
0) 选择格式类型
| Type | How to Create | Version Controlled | Customizable by User |
|---|---|---|---|
| Standard | Developer Mode, saved as JSON | Yes | No |
| Print Format Builder | Drag-and-drop UI | No (DB) | Yes |
| Custom HTML (Jinja) | Type "new print format" in awesomebar | Optional | Depends |
| 类型 | 创建方式 | 版本控制 | 用户可自定义 |
|---|---|---|---|
| 标准型 | 开发者模式,保存为JSON | 是 | 否 |
| 打印格式构建器 | 拖拽式界面 | 否(存储于数据库) | 是 |
| 自定义HTML(Jinja) | 在快捷搜索栏输入“new print format” | 可选 | 视情况而定 |
1) Create a Jinja print format
1) 创建Jinja打印格式
Create via awesomebar → "New Print Format":
- Set a unique name
- Link to the target DocType
- Set "Standard" = "No" (or "Yes" for dev mode export)
- Check "Custom Format"
- Set Print Format Type = "Jinja"
- Write your Jinja HTML
jinja
<div class="print-format">
<h1>{{ doc.name }}</h1>
<p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p>
<p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p>
<table class="table table-bordered">
<thead>
<tr>
<th>{{ _("Item") }}</th>
<th>{{ _("Qty") }}</th>
<th class="text-right">{{ _("Rate") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
</thead>
<tbody>
{% for item in doc.items %}
<tr>
<td>{{ item.item_name }}</td>
<td>{{ item.qty }}</td>
<td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td>
<td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td>
<td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td>
</tr>
</tfoot>
</table>
{% if doc.terms %}
<div class="terms">
<h4>{{ _("Terms & Conditions") }}</h4>
<p>{{ doc.terms }}</p>
</div>
{% endif %}
</div>
<style>
.print-format { font-family: Arial, sans-serif; }
.print-format h1 { color: #333; }
.print-format table { width: 100%; margin-top: 20px; }
</style>通过快捷搜索栏创建 → “New Print Format”:
- 设置唯一名称
- 关联目标DocType
- 设置“Standard”为“No”(若要在开发者模式下导出则设为“Yes”)
- 勾选“Custom Format”
- 设置打印格式类型为“Jinja”
- 编写Jinja HTML代码
jinja
<div class="print-format">
<h1>{{ doc.name }}</h1>
<p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p>
<p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p>
<table class="table table-bordered">
<thead>
<tr>
<th>{{ _("Item") }}</th>
<th>{{ _("Qty") }}</th>
<th class="text-right">{{ _("Rate") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
</thead>
<tbody>
{% for item in doc.items %}
<tr>
<td>{{ item.item_name }}</td>
<td>{{ item.qty }}</td>
<td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td>
<td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td>
<td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td>
</tr>
</tfoot>
</table>
{% if doc.terms %}
<div class="terms">
<h4>{{ _("Terms & Conditions") }}</h4>
<p>{{ doc.terms }}</p>
</div>
{% endif %}
</div>
<style>
.print-format { font-family: Arial, sans-serif; }
.print-format h1 { color: #333; }
.print-format table { width: 100%; margin-top: 20px; }
</style>2) Use Frappe Jinja API
2) 使用Frappe Jinja API
Data fetching in templates:
jinja
{# Fetch a document #}
{% set customer = frappe.get_doc('Customer', doc.customer) %}
{{ customer.customer_name }}
{# List query (ignores permissions) #}
{% set open_orders = frappe.get_all('Sales Order',
filters={'customer': doc.customer, 'status': 'To Deliver and Bill'},
fields=['name', 'grand_total'],
order_by='creation desc',
page_length=5) %}
{# Permission-aware list query #}
{% set my_tasks = frappe.get_list('Task',
filters={'owner': frappe.session.user}) %}
{# Single value lookup #}
{% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %}
{# Settings value #}
{% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}Formatting:
jinja
{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}Session and context:
jinja
{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}URLs:
jinja
<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>模板中的数据获取:
jinja
{# 获取文档 #}
{% set customer = frappe.get_doc('Customer', doc.customer) %}
{{ customer.customer_name }}
{# 列表查询(忽略权限) #}
{% set open_orders = frappe.get_all('Sales Order',
filters={'customer': doc.customer, 'status': 'To Deliver and Bill'},
fields=['name', 'grand_total'],
order_by='creation desc',
page_length=5) %}
{# 权限感知型列表查询 #}
{% set my_tasks = frappe.get_list('Task',
filters={'owner': frappe.session.user}) %}
{# 单值查询 #}
{% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %}
{# 设置值查询 #}
{% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}格式化:
jinja
{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}会话与上下文:
jinja
{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}URL:
jinja
<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>3) Build email templates
3) 构建邮件模板
jinja
Dear {{ doc.customer_name }},
Your order {{ doc.name }} has been confirmed.
Items:
{% for item in doc.items %}
- {{ item.item_name }} x {{ item.qty }}
{% endfor %}
Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}
Thank you,
{{ frappe.get_fullname() }}jinja
Dear {{ doc.customer_name }},
Your order {{ doc.name }} has been confirmed.
Items:
{% for item in doc.items %}
- {{ item.item_name }} x {{ item.qty }}
{% endfor %}
Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}
Thank you,
{{ frappe.get_fullname() }}4) Generate PDFs programmatically
4) 程序化生成PDF
python
import frappepython
import frappeGenerate PDF
Generate PDF
pdf_content = frappe.get_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
as_pdf=True
)
pdf_content = frappe.get_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
as_pdf=True
)
Attach PDF to document
Attach PDF to document
frappe.attach_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
file_name="invoice.pdf"
)
frappe.attach_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
file_name="invoice.pdf"
)
Send with email
Send with email
frappe.sendmail(
recipients=["customer@example.com"],
subject="Your Invoice",
message="Please find attached your invoice.",
attachments=[{
"fname": "invoice.pdf",
"fcontent": pdf_content
}]
)
undefinedfrappe.sendmail(
recipients=["customer@example.com"],
subject="Your Invoice",
message="Please find attached your invoice.",
attachments=[{
"fname": "invoice.pdf",
"fcontent": pdf_content
}]
)
undefined5) Configure Letter Head
5) 配置信头
- Navigate to Letter Head list → New
- Upload company logo and header image
- Set as default for the company
- Letter Head appears automatically on print formats
- 进入信头列表 → 新建
- 上传公司Logo和页眉图片
- 设置为公司默认信头
- 信头会自动显示在打印格式中
6) Use Jinja filters
6) 使用Jinja过滤器
jinja
{{ doc.customer_name|upper }} {# UPPERCASE #}
{{ doc.notes|truncate(100) }} {# Truncate text #}
{{ doc.description|striptags }} {# Remove HTML #}
{{ doc.html_content|safe }} {# Render raw HTML (trusted only!) #}
{{ items|length }} {# Count items #}
{{ items|first }} {# First item #}
{{ names|join(', ') }} {# Join list #}
{{ amount|round(2) }} {# Round number #}
{{ value|default('N/A') }} {# Default if undefined #}
{{ data|tojson }} {# Convert to JSON #}jinja
{{ doc.customer_name|upper }} {# 大写 #}
{{ doc.notes|truncate(100) }} {# 截断文本 #}
{{ doc.description|striptags }} {# 移除HTML标签 #}
{{ doc.html_content|safe }} {# 渲染原始HTML(仅信任内容使用!) #}
{{ items|length }} {# 统计数量 #}
{{ items|first }} {# 第一个元素 #}
{{ names|join(', ') }} {# 连接列表 #}
{{ amount|round(2) }} {# 四舍五入保留两位小数 #}
{{ value|default('N/A') }} {# 未定义时显示默认值 #}
{{ data|tojson }} {# 转换为JSON #}7) Template inheritance and macros
7) 模板继承与宏
jinja
{# macros/fields.html #}
{% macro field_row(label, value) %}
<tr>
<td class="label"><strong>{{ _(label) }}</strong></td>
<td>{{ value }}</td>
</tr>
{% endmacro %}
{# In print format #}
{% from "macros/fields.html" import field_row %}
<table>
{{ field_row("Customer", doc.customer_name) }}
{{ field_row("Date", frappe.format_date(doc.posting_date)) }}
{{ field_row("Total", frappe.format(doc.grand_total, {'fieldtype': 'Currency'})) }}
</table>jinja
{# macros/fields.html #}
{% macro field_row(label, value) %}
<tr>
<td class="label"><strong>{{ _(label) }}</strong></td>
<td>{{ value }}</td>
</tr>
{% endmacro %}
{# 在打印格式中使用 #}
{% from "macros/fields.html" import field_row %}
<table>
{{ field_row("Customer", doc.customer_name) }}
{{ field_row("Date", frappe.format_date(doc.posting_date)) }}
{{ field_row("Total", frappe.format(doc.grand_total, {'fieldtype': 'Currency'})) }}
</table>Verification
验证项
- Print format renders correctly in Print View
- All fields display with proper formatting
- PDF generation works without errors
- Email templates render with correct data
- Letter Head appears on printed documents
- Translations work in templates ()
_() - No XSS risks from unescaped content
- 打印格式在打印视图中渲染正常
- 所有字段显示格式正确
- PDF生成无错误
- 邮件模板渲染数据正确
- 信头显示在打印文档上
- 模板中的翻译功能正常()
_() - 无未转义内容导致的XSS风险
Failure modes / debugging
故障排查
- Template syntax error: Check Jinja delimiters (,
{{ }}); look for unclosed blocks{% %} - Field not rendering: Verify field name matches DocType schema; check child table access pattern
- PDF generation fails: Check wkhtmltopdf installation; verify print format Jinja is valid
- Styling issues in PDF: Use inline styles; avoid complex CSS; test with Print View first
- Permission error in template: Use (no permission check) vs
frappe.get_allfrappe.get_list
- 模板语法错误:检查Jinja分隔符(、
{{ }});查找未闭合的代码块{% %} - 字段未渲染:确认字段名称与DocType schema匹配;检查子表访问方式
- PDF生成失败:检查wkhtmltopdf是否安装;验证打印格式的Jinja代码是否有效
- PDF样式问题:使用内联样式;避免复杂CSS;先在打印视图中测试
- 模板权限错误:区分使用(无权限检查)和
frappe.get_all(有权限检查)frappe.get_list
Escalation
升级路径
- For app-level hooks and structure →
frappe-app-development - For DocType schema questions →
frappe-doctype-development
- 应用级钩子与结构问题 → 联系
frappe-app-development - DocType schema问题 → 联系
frappe-doctype-development
References
参考资料
- references/jinja.md — Jinja templating and Frappe Jinja API
- references/printing.md — Print formats and PDF generation
- references/jinja.md — Jinja模板与Frappe Jinja API
- references/printing.md — 打印格式与PDF生成
Guardrails
注意事项
- Test with actual data: Always preview with real documents; edge cases break templates
- Handle missing fields gracefully: Use or
{{ doc.field or '' }}{% if doc.field %} - Use for images: Never hardcode URLs; use
get_url(){{ frappe.utils.get_url() }}/files/... - Escape user content: Use for user-generated content to prevent XSS
{{ value | e }} - Keep styling inline: PDF generators don't support external CSS; use inline attributes
style
- 使用真实数据测试:始终用真实文档预览;边缘场景会导致模板失效
- 优雅处理缺失字段:使用或
{{ doc.field or '' }}{% if doc.field %} - 图片使用:切勿硬编码URL;使用
get_url(){{ frappe.utils.get_url() }}/files/... - 转义用户内容:对用户生成的内容使用以防止XSS攻击
{{ value | e }} - 样式使用内联方式:PDF生成器不支持外部CSS;使用内联属性
style
Common Mistakes
常见错误
| Mistake | Why It Fails | Fix |
|---|---|---|
| Wrong Jinja syntax | Template error, blank output | Use |
| Missing filters | Raw data displayed | Use |
| Hardcoded URLs | Images/links break across sites | Use |
| Accessing child table wrong | Empty or error | Use |
| Complex CSS in print format | Styling lost in PDF | Use inline styles, simple layouts, |
| Not handling None values | | Use |
| 错误 | 失败原因 | 修复方案 |
|---|---|---|
| Jinja语法错误 | 模板报错,输出空白 | 输出用 |
| 未使用过滤器 | 显示原始数据 | 使用 |
| 硬编码URL | 跨站点时图片/链接失效 | 使用 |
| 子表访问方式错误 | 输出空白或报错 | 使用 |
| 打印格式使用复杂CSS | PDF中样式丢失 | 使用内联样式、简单布局,用 |
| 未处理空值 | 输出显示 | 使用 |