关于 frappe-impl-customapp
面向 Frappe 开发者的自定义应用实施指南,覆盖从创建应用到测试和发布的流程。强调先判断是否确实需要自定义应用,并提供 v14、v15、v16 的相关做法。
主要功能
- 01评估是否需要自定义应用
- 02创建应用并配置模块
- 03实现钩子、补丁与 Fixtures
- 04迁移、测试和发布应用
- 05参考 v14 至 v16 差异
适用场景
- 01从头搭建 Frappe 自定义应用
- 02为 DocType 添加模块与控制器
- 03迁移数据并管理应用配置
- 04测试并将应用安装到其他站点
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-impl-customapp安装后在 Claude Code 中输入/frappe-impl-customapp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mddecision-tree.mdexamples.mdworkflows.md详细介绍
Frappe 自定义应用 - 实施
从头构建自定义 Frappe 应用的工作流。关于准确语法,请参阅 frappe-syntax-customapp。
版本:兼容 v14/v15/v16
主要决策:你需要自定义应用吗?
WHAT CHANGES DO YOU NEED?
|
+-- Add fields to existing DocType?
| +-- NO APP NEEDED: Custom Field + Property Setter
|
+-- Simple automation/validation (<50 lines)?
| +-- NO APP NEEDED: Server Script or Client Script
|
+-- Complex business logic, new DocTypes, or Python code?
| +-- YES: Create custom app
|
+-- Integration with external system (needs imports)?
| +-- YES: Custom app REQUIRED (Server Scripts block imports)
|
+-- Custom reports with complex queries?
| +-- Script Report (no app) vs Query Report (app optional)
规则:始终从最简单的解决方案开始。Server Scripts + Custom Fields 无需自定义应用即可满足 70% 的需求。
步骤 1:创建应用结构
cd ~/frappe-bench
bench new-app my_app
# Prompts: Title, Description, Publisher, Email, License
务必立即验证:
# my_app/my_app/__init__.py MUST have:
__version__ = "0.0.1"
步骤 2:配置 pyproject.toml(v15+)
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_app"
authors = [{ name = "Your Company", email = "[email protected]" }]
description = "Your app description"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
dependencies = [
"requests>=2.28.0" # Only PyPI packages here
]
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
# erpnext = ">=15.0.0,<16.0.0" # Only if needed
规则:绝不要将 frappe 或 erpnext 放入 [project].dependencies——它们不在 PyPI 上。
步骤 3:配置 hooks.py
app_name = "my_app"
app_title = "My App"
app_publisher = "Your Company"
app_description = "Description"
app_email = "[email protected]"
app_license = "MIT"
required_apps = ["frappe"] # Or ["frappe", "erpnext"]
fixtures = [] # Configured later
规则:务必声明所有依赖项的 required_apps。
步骤 4:定义模块
# my_app/my_app/modules.txt
My App
| 应用规模 | 模块策略 |
|---|---|
| 1-5 个 DocType | 使用一个以应用名称命名的模块 |
| 6-15 个 DocType | 按功能领域划分为 2-4 个模块 |
| 15 个以上 DocType | 按业务领域划分模块 |
规则:每个 DocType 必须且只能属于一个模块。modules.txt 中的模块名称映射到目录:My Custom App --> my_custom_app/。
添加模块
mkdir -p my_app/my_app/new_module/doctype
touch my_app/my_app/new_module/__init__.py
# Add "New Module" to modules.txt
bench --site mysite migrate
步骤 5:安装并创建 DocType
# Install app on site
bench --site mysite install-app my_app
# Create DocType (via UI recommended, or CLI)
bench --site mysite new-doctype "My Document" --module "My App"
这会创建:
my_app/my_app/doctype/my_document/
+-- my_document.json # DocType definition
+-- my_document.py # Controller
+-- my_document.js # Client script
+-- test_my_document.py # Tests
步骤 6:添加钩子
doc_events(v14/v15/v16)
doc_events = {
"Sales Invoice": {
"validate": "my_app.events.sales_invoice.validate",
"on_submit": "my_app.events.sales_invoice.on_submit"
}
}
extend_doctype_class(仅限 v16——首选)
extend_doctype_class = {
"Sales Invoice": "my_app.overrides.sales_invoice.CustomSalesInvoice"
}
规则:在 v16 中重写生命周期方法时,务必调用 super().method()。
Scheduler Events
scheduler_events = {
"daily": ["my_app.tasks.daily_cleanup"],
"cron": {"0 9 * * 1-5": ["my_app.tasks.morning_report"]}
}
完整模式请参阅 frappe-impl-hooks 和 frappe-impl-scheduler。
步骤 7:添加补丁
创建补丁文件
mkdir -p my_app/my_app/patches/v1_0
touch my_app/my_app/patches/__init__.py
touch my_app/my_app/patches/v1_0/__init__.py
# my_app/my_app/patches/v1_0/populate_defaults.py
import frappe
def execute():
if not frappe.db.has_column("My DocType", "target_field"):
return # Skip if not applicable
batch_size = 1000
offset = 0
while True:
records = frappe.get_all("My DocType",
limit_page_length=batch_size, limit_start=offset)
if not records:
break
for r in records:
frappe.db.set_value("My DocType", r.name,
"target_field", "default", update_modified=False)
frappe.db.commit()
offset += batch_size
在 patches.txt 中注册
[pre_model_sync]
# Patches that run BEFORE schema changes (backup data from deleted fields)
[post_model_sync]
# Patches that run AFTER schema changes (populate new fields)
my_app.patches.v1_0.populate_defaults
规则:
- 务必检查是否需要运行补丁(守卫子句)
- 处理 1000 条以上记录时,务必分批处理
- 每批处理后务必提交
- 绝不要在生产环境运行未经测试的补丁
步骤 8:管理 Fixtures
在 hooks.py 中配置
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "My App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My App"]]},
{"dt": "Role", "filters": [["name", "in", ["My App User", "My App Manager"]]]},
{"dt": "Workflow", "filters": [["document_type", "=", "My DocType"]]},
"My Category", # All records of your own config DocType
]
导出并验证
bench --site mysite export-fixtures --app my_app
ls my_app/my_app/fixtures/
# custom_field.json, property_setter.json, etc.
规则:
- 务必只筛选与你的应用自定义项相关的 Fixtures
- 绝不要包含事务数据(发票、订单)
- 对于共享 DocType(Custom Field、Workflow),绝不要在未设置筛选条件的情况下导出
- Fixtures 会在
bench migrate期间自动导入
步骤 9:开发工作流
必备命令
# After schema changes (DocType fields, hooks.py, patches)
bench --site mysite migrate
# After JS/CSS changes
bench build --app my_app
# After Python changes (controllers, events)
bench --site mysite clear-cache
# Full restart (production)
bench restart
# Watch mode (development)
bench watch # Auto-rebuilds on file changes
开发周期
1. Edit code/DocType
2. bench --site mysite migrate (if schema changed)
3. bench build --app my_app (if JS/CSS changed)
4. bench --site mysite clear-cache (if Python changed)
5. Test in browser
6. Repeat
步骤 10:测试应用
# Run all tests
bench --site mysite run-tests --app my_app
# Run specific test
bench --site mysite run-tests --module my_app.my_module.doctype.my_doctype.test_my_doctype
# Run with verbose output
bench --site mysite run-tests --app my_app -v
有关编写测试用例,请参阅 frappe-testing-unit。
步骤 11:打包以供分发
通过 Git(标准方法)
cd apps/my_app
git init && git add . && git commit -m "Initial commit"
git remote add origin https://github.com/org/my_app.git
git push -u origin main
在另一个站点上安装
# On target bench
bench get-app https://github.com/org/my_app.git
bench --site target-site install-app my_app
bench --site target-site migrate
版本管理
# my_app/my_app/__init__.py
__version__ = "1.0.0" # Semantic versioning: MAJOR.MINOR.PATCH
| 变更类型 | 版本递增类型 | 示例 |
|---|---|---|
| 破坏性变更 | MAJOR | 1.x -> 2.0.0 |
| 新功能 | MINOR | 1.1.x -> 1.2.0 |
| Bug 修复 | PATCH | 1.2.0 -> 1.2.1 |
步骤 12:应用依赖项
Frappe/ERPNext 依赖项
# hooks.py
required_apps = ["frappe", "erpnext"] # Install order matters
# pyproject.toml
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
erpnext = ">=15.0.0,<16.0.0"
Python 包依赖项
[project]
dependencies = ["requests>=2.28.0", "pandas>=1.5.0"]
规则:绝不要在应用之间创建循环依赖。
特定版本的注意事项
| 方面 | v14 | v15 | v16 |
|---|---|---|---|
| 构建配置 | setup.py | pyproject.toml | pyproject.toml |
| DocType 扩展 | doc_events | doc_events | 首选 extend_doctype_class |
| Python 最低版本 | 3.10 | 3.10 | 3.11 |
| 补丁格式 | INI sections | INI sections | INI sections |
需要了解的 v16 破坏性变更
extend_doctype_class钩子:通过 mixin 实现更简洁的扩展- 数据脱敏:字段级隐私配置
- UUID 命名:新增命名规则选项
- Chrome PDF:wkhtmltopdf 已弃用
关键规则摘要
务必遵守
- 从
bench new-app开始——绝不要手动创建结构 - 在
__init__.py中定义__version__ - 在 pyproject.toml 中使用
dynamic = ["version"] - 在生产环境使用前,先在数据库副本上测试补丁
- 仅筛选与你的应用自定义项相关的 Fixtures
- 为补丁进行版本管理(v1_0、v2_0 目录)
- 在全新站点上测试安装
切勿
- 将 frappe/erpnext 放入
[project].dependencies - 在 fixtures 中包含事务数据
- 硬编码特定站点的值(使用设置 DocType)
- 在大型补丁中跳过
frappe.db.commit() - 未备份就删除字段
- 直接修改 ERPNext 核心文件
参考文件
| 文件 | 内容 |
|---|---|
| workflows.md | 8 个分步实施指南 |
| decision-tree.md | 完整的决策流程图 |
| examples.md | 5 个完整可运行的应用示例 |
| anti-patterns.md | 常见错误及规避方法 |
另请参阅
frappe-syntax-customapp- 精确语法参考frappe-syntax-hooks- Hooks 配置语法frappe-impl-hooks- Hook 实现模式frappe-core-database- 补丁的数据库操作frappe-impl-scheduler- 定时任务实现frappe-ops-bench- Bench 命令参考frappe-ops-app-lifecycle- 应用版本管理与发布管理frappe-testing-unit- 为应用编写测试frappe-testing-cicd- 应用测试的 CI/CD 流水线
评论
支持 Markdown