关于 app-development
面向 Frappe 开发者的应用构建 Skill,涵盖应用结构、钩子、后台任务和生产准备。强调将业务逻辑拆分到服务模块,并遵循 Frappe 约定。
主要功能
- 01应用脚手架与架构规划
- 02钩子配置模式
- 03服务层组织方式
- 04后台任务设置
- 05缓存与错误处理工具
- 06版本兼容指导
适用场景
- 01启动自定义 Frappe 应用
- 02将业务逻辑移出 DocType 控制器
- 03添加事件钩子和定时任务
- 04准备应用部署
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ lubusin/ frappe-skills --skill app-development安装后在 Claude Code 中输入/app-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/mini-app-template/your_app/api.pyhooks.pyREADME.mdassets/mini-app-template/your_app/background_jobs/sample_job.pyassets/mini-app-template/your_app/dashboard/sample_doc_dashboard.jsonassets/mini-app-template/your_app/doctype/sample_doc_item/sample_doc_item.jsonassets/mini-app-template/your_app/doctype/sample_doc/sample_doc_dynamic_link.jsonsample_doc_naming_series.jsonsample_doc.jssample_doc.jsonsample_doc.pyassets/mini-app-template/your_app/doctype/sample_single/sample_single.jsonassets/mini-app-template/your_app/doctype/sample_submittable/sample_submittable.jsonassets/mini-app-template/your_app/doctype/sample_tree/sample_tree.jsonassets/mini-app-template/your_app/integrations/sample_connector.pyassets/mini-app-template/your_app/list_view/sample_doc_list.jsassets/mini-app-template/your_app/report/sample_report/sample_report.jssample_report.jsonsample_report.pyassets/mini-app-template/your_app/services/sample_service.pyassets/mini-app-template/your_app/utils/cache.pyerrors.pylogging.pypermissions.pyvalidation.pyassets/mini-app-template/your_app/workflow/sample_doc_workflow.jsonreferences/app-development.mdtranslations.mdversion-compat.md详细介绍
Frappe 应用开发
使用生产级模式搭建脚手架、规划结构并设计自定义 Frappe 应用。
适用场景
- 从头开始创建新的自定义 Frappe 应用
- 设置应用架构(模块、服务、工具函数)
- 为事件、调度器和覆盖项配置
hooks.py - 实现后台任务和异步处理
- 构建服务层和领域逻辑模式
- 添加缓存、日志记录和错误处理工具
- 为生产部署准备应用
- 管理翻译、版本控制和打包
所需输入
- 应用名称和用途
- 目标 Frappe 版本(v13+、v15+、v16+)
- 模块结构(涉及哪些 DocType、API 和服务)
- 是否需要其他应用的钩子或覆盖项
- 后台任务需求
- 生产就绪要求
流程
0) 搭建应用脚手架
# Create the app
bench new-app my_app
# Install on site
bench --site mysite.local install-app my_app
# Verify developer mode
bench --site mysite.local console
>>> frappe.conf.developer_mode # Must be True
1) 规划应用结构
遵循领域架构模式:保持 DocType 控制器精简,并将业务逻辑放在服务模块中:
my_app/
├── my_app/
│ ├── __init__.py
│ ├── hooks.py # App hooks and configuration
│ ├── api.py # Public API surface (RPC endpoints)
│ ├── services/ # Business logic modules
│ │ └── billing.py
│ ├── utils/ # Cross-cutting utilities
│ │ ├── cache.py
│ │ ├── errors.py
│ │ ├── logging.py
│ │ ├── permissions.py
│ │ └── validation.py
│ ├── background_jobs/ # Async job handlers
│ │ └── export_job.py
│ ├── integrations/ # External system connectors
│ │ └── payment_gateway.py
│ ├── my_module/
│ │ ├── doctype/
│ │ │ └── my_doc/
│ │ │ ├── my_doc.json
│ │ │ ├── my_doc.py
│ │ │ ├── my_doc.js
│ │ │ └── test_my_doc.py
│ │ ├── report/
│ │ └── dashboard/
│ └── translations/
│ ├── en.csv
│ └── fr.csv
├── setup.py
└── README.md
使用 assets/mini-app-template/ 中的 mini-app-template 作为初始脚手架。
2) 配置 hooks.py
# hooks.py
app_name = "my_app"
app_title = "My App"
app_publisher = "My Company"
# DocType lifecycle events
doc_events = {
"Sales Order": {
"on_submit": "my_app.services.billing.on_order_submit",
"on_cancel": "my_app.services.billing.on_order_cancel",
},
"*": {
"after_insert": "my_app.utils.logging.log_creation",
}
}
# Scheduled tasks
scheduler_events = {
"daily": [
"my_app.background_jobs.cleanup.run_daily_cleanup"
],
"cron": {
"0 */6 * * *": [
"my_app.background_jobs.sync.sync_external_data"
]
}
}
# Client-side script injection
doctype_js = {
"Sales Order": "public/js/sales_order.js"
}
doctype_list_js = {
"Sales Order": "public/js/sales_order_list.js"
}
# Override another app's controller (use sparingly)
# override_doctype_class = {
# "ToDo": "my_app.overrides.custom_todo.CustomToDo"
# }
# Extend controller without full replacement (v16+, preferred)
# extend_doctype_class = {
# "ToDo": "my_app.overrides.todo_extension.TodoExtension"
# }
# Override whitelisted methods
# override_whitelisted_methods = {
# "frappe.client.get_list": "my_app.overrides.custom_get_list"
# }
3) 实现服务层
保持 DocType 控制器精简,将业务逻辑委托给服务:
# my_app/services/billing.py
import frappe
def on_order_submit(doc, method):
"""Handle order submission — called via doc_events hook."""
if doc.grand_total > 10000:
create_approval_request(doc)
generate_invoice(doc)
def generate_invoice(order):
"""Create invoice from submitted order."""
invoice = frappe.get_doc({
"doctype": "Sales Invoice",
"customer": order.customer,
"items": [
{"item_code": i.item_code, "qty": i.qty, "rate": i.rate}
for i in order.items
]
})
invoice.insert()
invoice.submit()
return invoice
4) 设置后台任务
# my_app/background_jobs/export_job.py
import frappe
def enqueue_export(filters):
"""Enqueue a long-running export job."""
frappe.enqueue(
"my_app.background_jobs.export_job.run_export",
filters=filters,
queue="long",
timeout=600,
is_async=True
)
def run_export(filters):
"""Execute the export — runs in background worker."""
data = frappe.get_all("Sales Order", filters=filters, fields=["*"])
# Process data...
frappe.publish_realtime("export_complete", {"count": len(data)})
5) 添加横切工具
# my_app/utils/cache.py
import frappe
def get_cached_settings(key):
"""Cache expensive settings lookups."""
value = frappe.cache().get_value(f"my_app:{key}")
if value is None:
value = frappe.db.get_single_value("My Settings", key)
frappe.cache().set_value(f"my_app:{key}", value)
return value
def invalidate_cache(key):
frappe.cache().delete_value(f"my_app:{key}")
# my_app/utils/errors.py
import frappe
def api_error(message, status_code=400, exc=None):
"""Consistent error response for API endpoints."""
frappe.local.response["http_status_code"] = status_code
frappe.throw(message, exc or frappe.ValidationError)
6) 处理翻译
# In Python code
frappe._("Hello World") # Mark string for translation
# In JavaScript
__("Hello World") # Mark string for translation
# Translation CSV files go in my_app/translations/
# e.g., my_app/translations/fr.csv:
# Hello World,Bonjour le monde
7) 版本兼容性
| 功能 | 最低版本 |
|---|---|
extend_doctype_class | Frappe v16+ |
REST API v2 (/api/v2/) | Frappe v15+ |
| 基于令牌的身份验证 | Frappe v11.0.3+ |
面向多个版本时,请为特定版本的功能添加保护:
import frappe
if hasattr(frappe, 'extend_doctype_class'):
# v16+ pattern
pass
else:
# Fallback for older versions
pass
验证
- 应用安装无错误:
bench --site <site> install-app my_app - 钩子正常触发(检查调度器日志和文档事件)
- 后台任务成功入队并完成
-
bench --site <site> migrate成功 - 测试通过:
bench --site <site> run-tests --app my_app
故障模式 / 调试
- 找不到应用:检查
apps.txt和sites/<site>/site_config.json - 钩子未触发:检查
hooks.py语法;重启 bench - 后台任务卡住:使用
bench doctor检查工作进程状态;检查 Redis - 导入错误:确保钩子中的模块路径与实际 Python 路径一致
- 开发者模式已关闭:DocType 更改不会导出到文件
升级处理
- 有关 DocType 创建详情 →
doctype-development - 有关 API 端点模式 →
api-development - 有关 Desk UI 自定义 →
desk-customization - 有关 Frappe UI 前端 →
frontend-development - 有关打印格式和 Jinja →
printing-templates - 有关报表 →
reports - 有关网页表单 →
web-forms - 有关测试 →
testing - 有关企业架构 →
enterprise-patterns - 有关 Docker/FM 环境 →
frappe-manager
参考资料
- references/app-development.md — 端到端应用架构
- references/version-compat.md — 版本兼容性说明
- references/translations.md — 多语言支持
交叉引用(由专用技能维护)
- hooks.py 和扩展点 →
doctype-development(hooks-extensions.md) - Python API 参考 →
api-development(python-api.md)
防护规则
- 自定义前端使用 Frappe UI:绝不使用原生 JS、jQuery 或自定义框架。Frappe UI(Vue 3 + TailwindCSS)是生态标准。设置请参阅
frontend-development。 - CRUD 应用遵循 CRM/Helpdesk 模式:遵循
ui-patterns技能中的应用外壳、导航、列表视图和表单布局,这些模式源自官方 Frappe 应用。 - 遵循命名规范:应用名称必须全部小写并使用下划线,且是有效的 Python 标识符
- 使用 hooks.py 进行集成:绝不使用猴子补丁;使用 doc_events、scheduler_events、boot_session 钩子
- 保持 hooks.py 简洁:仅放置配置,不包含逻辑;从模块导入
- 保持向后兼容性:使用
frappe.version检查以支持不同版本 - 正确导出 fixtures:对于应与应用同步的数据,在 hooks.py 中使用
fixtures
常见错误
| 错误 | 失败原因 | 修复方法 |
|---|---|---|
应用不在 installed_apps 中 | 应用代码未加载 | 运行 bench --site <site> install-app my_app |
| hooks 中的模块路径错误 | 事件不会触发 | 确认路径与实际的 my_app/module/file.py 结构一致 |
| 重复注册钩子 | 事件会多次触发 | 检查 hooks.py 中是否有重复项;使用列表,不要重复键 |
| 编辑 hooks.py 后未重启 | 更改不会生效 | 修改 hooks.py 后运行 bench restart |
缺少 __init__.py 文件 | 模块导入错误 | 确保每个目录都有 __init__.py |
| 在 hooks.py 中编写逻辑 | 难以测试,且可能导致导入错误 | 将逻辑移至独立模块,并在 hooks 中导入 |
| 使用原生 JS/jQuery 构建前端 | 与生态不一致 | 使用 Frappe UI(Vue 3);参阅 frontend-development |
| 为 CRUD 应用构建自定义应用外壳 | 用户体验不一致 | 遵循 CRM/Helpdesk 的导航和布局模式 |
评论
支持 Markdown