关于 frappe-printing-templates
Frappe Printing & Templates 是用于 Frappe 文档呈现的技能,面向需要制作打印格式、邮件模板和文档模板的开发者。它涵盖 Jinja 模板、PDF 生成、信头配置及输出验证。
主要功能
- 01创建 Jinja 打印格式
- 02制作动态邮件模板
- 03从文档生成 PDF
- 04配置信头与品牌元素
- 05检查渲染和转义风险
适用场景
- 01定制 DocType 文档的打印布局
- 02生成包含动态字段的订单邮件
- 03通过程序生成单据 PDF
- 04排查模板字段或 PDF 输出问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ lubusin/ frappe-skills --skill frappe-printing-templates安装后在 Claude Code 中输入/frappe-printing-templates确认已加载。
附带文件references/jinja.mdreferences/printing.md
详细介绍
Frappe 打印与模板
在 Frappe 中使用 Jinja 创建打印格式、电子邮件模板和文档模板。
适用场景
- 为文档创建自定义打印格式
- 使用动态内容构建电子邮件模板
- 根据文档生成 PDF
- 在网页中使用 Jinja 模板
- 配置用于品牌展示的信头
- 使用打印格式构建器
所需输入
- 打印格式的目标 DocType
- 布局要求(字段、表格、页眉)
- 格式是标准格式(受版本控制)还是自定义格式(存储在数据库中)
- 信头/品牌展示要求
- PDF 生成需求
流程
0) 选择格式类型
| 类型 | 创建方式 | 受版本控制 | 用户可自定义 |
|---|---|---|---|
| 标准格式 | 开发者模式,保存为 JSON | 是 | 否 |
| 打印格式构建器 | 拖放式 UI | 否(数据库) | 是 |
| 自定义 HTML(Jinja) | 在 awesomebar 中输入 "new print format" | 可选 | 视情况而定 |
1) 创建 Jinja 打印格式
通过 awesomebar → "New Print Format" 创建:
- 设置唯一名称
- 链接到目标 DocType
- 将 "Standard" 设为 "No"(开发模式导出时设为 "Yes")
- 勾选 "Custom Format"
- 将 Print Format Type 设为 "Jinja"
- 编写 Jinja HTML
<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) 使用 Frappe Jinja API
在模板中获取数据:
{# 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') %}
格式化:
{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}
会话和上下文:
{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}
URL:
<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>
3) 构建电子邮件模板
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) 以编程方式生成 PDF
import frappe
# Generate PDF
pdf_content = frappe.get_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
as_pdf=True
)
# Attach PDF to document
frappe.attach_print(
doctype="Sales Invoice",
name="SINV-001",
print_format="Custom Invoice",
file_name="invoice.pdf"
)
# Send with email
frappe.sendmail(
recipients=["[email protected]"],
subject="Your Invoice",
message="Please find attached your invoice.",
attachments=[{
"fname": "invoice.pdf",
"fcontent": pdf_content
}]
)
5) 配置信头
- 前往信头列表 → New
- 上传公司徽标和页眉图像
- 将其设为公司的默认信头
- 信头会自动显示在打印格式中
6) 使用 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 #}
7) 模板继承与宏
{# 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>
验证
- 打印格式在打印视图中正确呈现
- 所有字段均以正确格式显示
- PDF 生成无错误
- 电子邮件模板使用正确的数据呈现
- 信头显示在打印文档中
- 模板中的翻译正常工作(
_()) - 不存在未转义内容导致的 XSS 风险
故障模式/调试
- 模板语法错误:检查 Jinja 分隔符(
{{ }}、{% %});查找未闭合的代码块 - 字段未呈现:确认字段名与 DocType 架构一致;检查子表的访问方式
- PDF 生成失败:检查 wkhtmltopdf 是否已安装;确认打印格式中的 Jinja 有效
- PDF 样式问题:使用内联样式;避免复杂 CSS;先在打印视图中测试
- 模板权限错误:使用
frappe.get_all(不检查权限)而不是frappe.get_list
升级处理
- 应用级钩子和结构问题 →
frappe-app-development - DocType 架构问题 →
frappe-doctype-development
参考资料
- references/jinja.md — Jinja 模板和 Frappe Jinja API
- references/printing.md — 打印格式和 PDF 生成
防护措施
- 使用实际数据测试:始终用真实文档预览;边缘情况可能导致模板出错
- 妥善处理缺失字段:使用
{{ doc.field or '' }}或{% if doc.field %} - 使用
get_url()获取图像:切勿硬编码 URL;使用{{ frappe.utils.get_url() }}/files/... - 转义用户内容:对用户生成的内容使用
{{ value | e }},以防止 XSS - 保持样式内联:PDF 生成器不支持外部 CSS;请使用内联
style属性
常见错误
| 错误 | 失败原因 | 修正方法 |
|---|---|---|
| Jinja 语法错误 | 模板报错,输出为空 | 输出使用 {{ }},逻辑使用 {% %};检查结束标签 |
| 缺少过滤器 | 显示原始数据 | 使用 frappe.format() 或 frappe.format_date() 进行格式化 |
| URL 硬编码 | 图片/链接在不同站点上失效 | 使用 {{ frappe.utils.get_url() }} 获取绝对 URL |
| 子表访问方式错误 | 内容为空或报错 | 使用 {% for item in doc.items %},而不是 doc.child_table_name |
| 打印格式中的 CSS 过于复杂 | PDF 中样式丢失 | 使用内联样式、简单布局,并使用 <table> 构建结构 |
| 未处理 None 值 | 输出中出现 'None' 字符串 | 使用 {{ value or '' }} 或 {% if value %} |
评论
支持 Markdown