关于 frappe-syntax-print
面向 Frappe 开发者,提供 v14、v15、v16 打印格式、信头和 PDF 生成的参考。涵盖 Jinja、报表 JS 模板及 v15+ 的 Print Designer,并说明 PDF 引擎与版本差异。
主要功能
- 01标准、Jinja 和报表 JS 打印格式
- 02Print Designer 的版本与引擎说明
- 03信头及页眉页脚配置
- 04PDF 生成 API 与下载端点
- 05分页、打印 CSS 和模板过滤器
适用场景
- 01为单据创建自定义打印格式
- 02为报表配置 JS 打印模板
- 03生成带信头的 PDF
- 04调整打印布局和分页
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-syntax-print安装后在 Claude Code 中输入/frappe-syntax-print确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdjinja-print-formats.mdpdf-api.md详细介绍
Frappe 打印格式与 PDF 生成
关于 Frappe v14/v15/v16 中打印格式、Letter Head 和 PDF 生成的确定性参考。
何时使用此技能
使用场景:
- 创建或修改打印格式(Jinja 或 JS)
- 以编程方式生成 PDF(
get_pdf、下载端点) - 为打印输出配置 Letter Head(页眉/页脚)
- 使用 Print Designer(v15+)
- 实现分页、打印 CSS 或横向布局
- 构建报表打印格式(
{%= %}语法)
请勿用于:
- 通用 Jinja 模板语法(电子邮件、门户)——请参阅
frappe-syntax-jinja - 客户端脚本 UI 逻辑——请参阅
frappe-syntax-clientscripts - Web 视图或门户页面——请参阅
frappe-syntax-jinja
决策树:选择哪种打印格式类型?
Need a printable/PDF document?
├─ YES → Is it a Query/Script Report?
│ ├─ YES → Use JS Template ({%= %} microtemplate)
│ │ Set print_format_for = "Report"
│ └─ NO → Need visual drag-and-drop editor?
│ ├─ YES → On v15+?
│ │ ├─ YES → Use Print Designer (WeasyPrint)
│ │ └─ NO → NOT available on v14. Use Jinja.
│ └─ NO → Need full layout control?
│ ├─ YES → Use Jinja Print Format (custom_format=1)
│ └─ NO → Use Standard Print Format (auto layout)
└─ NO → This skill does not apply.
打印格式类型
| 类型 | 引擎 | 版本 | 使用场景 |
|---|---|---|---|
| 标准 | 根据 DocType 字段布局自动生成 | v14+ | 无需自定义 |
| Jinja | 服务端 Jinja2(wkhtmltopdf) | v14+ | 完全控制布局 |
| JS 模板 | 客户端微模板 | v14+ | 仅用于报表打印格式 |
| Print Designer | WeasyPrint / Chrome | v15+ | 可视化拖放构建器 |
标准打印格式
始终为默认格式。Frappe 根据 DocType 字段自动生成布局。无需编写代码。通过 Print Settings 和字段的 print_hide 属性进行控制。
Jinja 打印格式
在 Print Format 文档上设置 custom_format = 1。使用完整的 Jinja2 进行服务端渲染。
每个 Jinja 打印格式中可用的上下文变量:
| 变量 | 类型 | 内容 |
|---|---|---|
doc | Document | 正在打印的文档 |
meta | Meta | DocType 元数据 |
layout | list | 字段布局分区 |
letter_head | str | 渲染后的 Letter Head HTML |
footer | str | 渲染后的页脚 HTML |
print_settings | dict | Print Settings 配置 |
frappe | module | 完整的 frappe 模块访问权限 |
示例 — 最简 Jinja 打印格式:
<h1>{{ doc.name }}</h1>
<p>Customer: {{ doc.customer_name }}</p>
<p>Date: {{ doc.posting_date | global_date_format }}</p>
<table class="table table-bordered">
<thead>
<tr><th>Item</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
</thead>
<tbody>
{% for row in doc.items %}
<tr>
<td>{{ row.item_name }}</td>
<td>{{ row.qty }}</td>
<td>{{ frappe.utils.fmt_money(row.rate, currency=doc.currency) }}</td>
<td>{{ frappe.utils.fmt_money(row.amount, currency=doc.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p><strong>Grand Total:</strong> {{ frappe.utils.fmt_money(doc.grand_total, currency=doc.currency) }}</p>
JS 模板(报表打印格式)
仅用于 Query Reports 和 Script Reports。使用 {%= %} 微模板语法,而非 Jinja。
// In report's .js file
{%= row.item_name %}
{% if (row.qty > 10) { %}
<strong>Bulk order</strong>
{% } %}
{% for (var i = 0; i < rows.length; i++) { %}
<tr>
<td>{%= rows[i].item_name %}</td>
<td>{%= rows[i].qty %}</td>
</tr>
{% } %}
**关键:**绝不要混用 Jinja {{ }} 和 JS {%= %} 语法。它们是完全独立的模板引擎。
Print Designer(仅 v15+)
- 独立应用:
bench get-app print_designer - 使用 WeasyPrint(而非 wkhtmltopdf)
- 在浏览器中使用可视化拖放构建器
- 绝不要尝试在 v14 上使用 Print Designer——该版本中不存在此功能
Letter Head
Letter Head 为所有打印格式提供统一的页眉/页脚。
配置
| 字段 | 用途 |
|---|---|
source | "Image" 或 "HTML" |
content | 页眉 HTML(使用 doc 上下文进行 Jinja 渲染) |
footer | 页脚 HTML(使用 Jinja 渲染,仅限 PDF) |
image | 页眉图像(当 source = "Image" 时) |
align | 图像对齐方式:Left、Center、Right |
重要:footer 字段仅在 PDF 输出中显示,不会显示在浏览器打印预览中。
Jinja 模板中的 Letter Head
# Server-side: render Letter Head programmatically
from frappe.utils.print_format import render_letterhead_for_print
letterhead_html = render_letterhead_for_print(
letter_head_name="My Company",
doc=doc
)
动态 Letter Head 内容
Letter Head 的 content 和 footer 字段支持使用 doc 上下文进行 Jinja 渲染:
<div style="text-align: right;">
<strong>{{ doc.company }}</strong><br>
Date: {{ doc.posting_date | global_date_format }}
</div>
PDF 生成 API
完整 API 参考请参阅
references/pdf-api.md。
快速参考
# Generate PDF bytes from HTML
from frappe.utils.pdf import get_pdf
pdf_bytes = get_pdf(html_string, options=None)
# Generate PDF from a specific document + print format
from frappe.utils.print_format import download_pdf
download_pdf(doctype, name, format=None, doc=None, no_letterhead=0)
下载端点
# Single document PDF
GET /api/method/frappe.utils.print_format.download_pdf
?doctype=Sales Invoice
&name=SINV-00001
&format=My Print Format
&no_letterhead=0
# Multiple documents in one PDF
GET /api/method/frappe.utils.print_format.download_multi_pdf
?doctype=Sales Invoice
&name=["SINV-00001","SINV-00002"]
&format=My Print Format
PDF 引擎选择(v15+)
| 引擎 | 使用场景 | 配置 |
|---|---|---|
| wkhtmltopdf | v14 上的默认引擎,v15+ 上的备用引擎 | 默认 |
| Chrome | v15+ 且已安装 Chromium | 在 Print Format 上设置 pdf_generator = "chrome" |
| WeasyPrint | 仅限 Print Designer 格式 | Print Designer 自动使用 |
在 v14 上始终使用 wkhtmltopdf。在 v15+ 上,Chrome 对 CSS3 的支持更好。
分页与打印 CSS
分页类
<div class="page-break"></div>
<div style="page-break-after: always;"></div>
<div style="page-break-before: always;"></div>
打印 CSS 类(Frappe 内置)
| 类 | 效果 |
|---|---|
.print-format | 容器:最大宽度 8.3in,最小高度 11.69in(A4 纵向) |
.print-format.landscape | 宽度 11.69in(A4 横向) |
.page-break | page-break-after: always |
.print-heading | 打印标题样式 |
.hidden-pdf | 仅在 PDF 输出中隐藏 |
.visible-pdf | 仅在 PDF 输出中显示 |
PDF 页眉/页脚 HTML
# In hooks.py — inject header/footer into every PDF
pdf_header_html = "myapp.utils.get_pdf_header"
pdf_body_html = "myapp.utils.get_pdf_body"
pdf_footer_html = "myapp.utils.get_pdf_footer"
<div id="header-html">
<span class="page"></span> of <span class="topage"></span>
</div>
<div id="footer-html">
<p style="text-align: center; font-size: 9px;">
Printed on {{ frappe.utils.nowdate() }}
</p>
</div>
打印 CSS 最佳实践
/* ALWAYS use relative units for print widths */
@media print {
.print-format {
max-width: 100%;
margin: 0;
padding: 15mm;
}
/* Prevent table rows from splitting across pages */
tr {
page-break-inside: avoid;
}
/* Constrain images */
img {
max-width: 100%;
height: auto;
}
}
自定义应用打印格式
随应用发布打印格式
myapp/
└── mymodule/
└── print_format/
└── my_custom_format/
├── my_custom_format.json # Print Format doc
└── my_custom_format.html # Jinja template
在 JSON 文件中,务必设置:
{
"doctype": "Print Format",
"name": "My Custom Format",
"doc_type": "Sales Invoice",
"module": "My Module",
"standard": "Yes",
"custom_format": 1,
"print_format_type": "Jinja"
}
对于随应用发布的打印格式,务必设置 standard = "Yes" 和 module。这样可确保系统将其识别为应用的一部分,而非站点级自定义项。
打印格式的 Jinja 过滤器
| 过滤器 | 用途 | 示例 |
|---|---|---|
global_date_format | 按系统设置格式化日期 | {{ doc.posting_date | global_date_format }} |
json | 序列化为 JSON 字符串 | {{ doc.items | json }} |
len | 获取长度 | {{ doc.items | len }} |
int | 转换为整数 | {{ value | int }} |
flt | 转换为浮点数 | {{ value | flt }} |
markdown | 将 Markdown 渲染为 HTML | {{ doc.description | markdown }} |
abs | 绝对值 | {{ value | abs }} |
注册自定义 Jinja 过滤器/方法
# In hooks.py
jinja = {
"methods": [
"myapp.utils.jinja.my_custom_method"
],
"filters": [
"myapp.utils.jinja.my_custom_filter"
]
}
# myapp/utils/jinja.py
def my_custom_method(value):
"""Available as {{ my_custom_method(doc.field) }} in templates."""
return value.upper()
def my_custom_filter(value, arg=None):
"""Available as {{ doc.field | my_custom_filter }} in templates."""
return f"[{value}]"
版本兼容性矩阵
| 功能 | v14 | v15 | v16 |
|---|---|---|---|
| Jinja 打印格式 | 是 | 是 | 是 |
| JS 报表模板 | 是 | 是 | 是 |
| 标准打印格式 | 是 | 是 | 是 |
| 信笺抬头(图像/HTML) | 是 | 是 | 是 |
| wkhtmltopdf | 默认 | 回退 | 回退 |
| Chrome PDF 引擎 | 否 | 是 | 是 |
| WeasyPrint | 否 | 是 | 是 |
| Print Designer 应用 | 否 | 是 | 是 |
pdf_header_html hook | 是 | 是 | 是 |
download_multi_pdf | 是 | 是 | 是 |
常见反模式
请参阅
references/anti-patterns.md,查看完整列表及修复方法。
- 绝不在报表打印格式中使用
{{ }}—— 它们使用{%= %}(JS 微模板) - 绝不在模板的
{% for %}循环中调用frappe.get_doc()—— 这会导致 N+1 查询 - 绝不在 Jinja 模板中放置复杂的业务逻辑 —— 将其移至 Python 并传入结果
- 绝不在 CSS 中硬编码页面尺寸 —— 使用
.print-format类或相对单位 - 绝不在以编程方式生成 PDF 时忽略
no_letterhead参数 - 绝不在 v14 上使用 Print Designer —— 它需要 v15 或更高版本
- 绝不在打印模板中嵌入大型 base64 图像 —— 使用 URL 并设置
max-width: 100%
参考文件
- Jinja 打印格式 —— Jinja 语法、变量、过滤器、宏
- PDF API —— get_pdf()、下载端点、分页符、钩子
- 反模式 —— 常见打印格式错误及修复方法