frappe-app-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrappe App Development
Frappe应用开发
Scaffold, structure, and architect custom Frappe applications with production-grade patterns.
采用生产级模式搭建、构建并架构自定义Frappe应用。
When to use
使用场景
- Creating a new custom Frappe app from scratch
- Setting up app architecture (modules, services, utils)
- Configuring for events, scheduler, overrides
hooks.py - Implementing background jobs and async processing
- Building service layers and domain logic patterns
- Adding caching, logging, error handling utilities
- Preparing apps for production deployment
- Managing translations, versioning, and packaging
- 从零开始创建自定义Frappe应用
- 设置应用架构(模块、服务、工具类)
- 配置以处理事件、调度器、重写逻辑
hooks.py - 实现后台任务与异步处理
- 构建服务层与领域逻辑模式
- 添加缓存、日志、错误处理工具
- 为生产部署准备应用
- 管理翻译、版本控制与打包
Inputs required
所需输入
- App name and purpose
- Target Frappe version (v13+, v15+, v16+)
- Module structure (which DocTypes, APIs, services)
- Whether hooks/overrides of other apps are needed
- Background job requirements
- Production readiness needs
- 应用名称与用途
- 目标Frappe版本(v13+、v15+、v16+)
- 模块结构(包含哪些DocTypes、API、服务)
- 是否需要重写其他应用的hooks
- 后台任务需求
- 生产环境就绪要求
Procedure
实施步骤
0) Scaffold the app
0) 搭建应用脚手架
bash
undefinedbash
undefinedCreate the app
创建应用
bench new-app my_app
bench new-app my_app
Install on site
在站点安装应用
bench --site mysite.local install-app my_app
bench --site mysite.local install-app my_app
Verify developer mode
验证开发者模式
bench --site mysite.local console
frappe.conf.developer_mode # Must be True
undefinedbench --site mysite.local console
frappe.conf.developer_mode # 必须为True
undefined1) Plan app structure
1) 规划应用结构
Follow the domain architecture pattern — keep DocType controllers thin and business logic in service modules:
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.mdUse the mini-app-template in as a starting scaffold.
assets/mini-app-template/遵循领域架构模式——保持DocType控制器精简,将业务逻辑放在服务模块中:
my_app/
├── my_app/
│ ├── __init__.py
│ ├── hooks.py # 应用hooks与配置
│ ├── api.py # 公开API接口(RPC端点)
│ ├── services/ # 业务逻辑模块
│ │ └── billing.py
│ ├── utils/ # 横切工具类
│ │ ├── cache.py
│ │ ├── errors.py
│ │ ├── logging.py
│ │ ├── permissions.py
│ │ └── validation.py
│ ├── background_jobs/ # 异步任务处理器
│ │ └── export_job.py
│ ├── integrations/ # 外部系统连接器
│ │ └── 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/2) Configure hooks.py
2) 配置hooks.py
python
undefinedpython
undefinedhooks.py
hooks.py
app_name = "my_app"
app_title = "My App"
app_publisher = "My Company"
app_name = "my_app"
app_title = "My App"
app_publisher = "My Company"
DocType lifecycle events
DocType生命周期事件
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",
}
}
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"
]
}
}
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"
}
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 = {
override_doctype_class = {
"ToDo": "my_app.overrides.custom_todo.CustomToDo"
"ToDo": "my_app.overrides.custom_todo.CustomToDo"
}
}
Extend controller without full replacement (v16+, preferred)
无需完全替换即可扩展控制器(v16+,推荐方式)
extend_doctype_class = {
extend_doctype_class = {
"ToDo": "my_app.overrides.todo_extension.TodoExtension"
"ToDo": "my_app.overrides.todo_extension.TodoExtension"
}
}
Override whitelisted methods
重写白名单方法
override_whitelisted_methods = {
override_whitelisted_methods = {
"frappe.client.get_list": "my_app.overrides.custom_get_list"
"frappe.client.get_list": "my_app.overrides.custom_get_list"
}
}
undefinedundefined3) Implement service layer
3) 实现服务层
Keep DocType controllers thin — delegate business logic to services:
python
undefined保持DocType控制器精简——将业务逻辑委托给服务模块:
python
undefinedmy_app/services/billing.py
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
undefinedimport frappe
def on_order_submit(doc, method):
"""处理订单提交——通过doc_events钩子调用。"""
if doc.grand_total > 10000:
create_approval_request(doc)
generate_invoice(doc)
def generate_invoice(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
undefined4) Set up background jobs
4) 设置后台任务
python
undefinedpython
undefinedmy_app/background_jobs/export_job.py
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)})
undefinedimport frappe
def enqueue_export(filters):
"""将耗时较长的导出任务加入队列。"""
frappe.enqueue(
"my_app.background_jobs.export_job.run_export",
filters=filters,
queue="long",
timeout=600,
is_async=True
)
def run_export(filters):
"""执行导出任务——在后台工作进程中运行。"""
data = frappe.get_all("Sales Order", filters=filters, fields=["*"])
# 处理数据...
frappe.publish_realtime("export_complete", {"count": len(data)})
undefined5) Add cross-cutting utilities
5) 添加横切工具类
python
undefinedpython
undefinedmy_app/utils/cache.py
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}")
```pythonimport frappe
def get_cached_settings(key):
"""缓存耗时的配置查询。"""
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}")
```pythonmy_app/utils/errors.py
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)
undefinedimport frappe
def api_error(message, status_code=400, exc=None):
"""为API端点提供一致的错误响应。"""
frappe.local.response["http_status_code"] = status_code
frappe.throw(message, exc or frappe.ValidationError)
undefined6) Handle translations
6) 处理翻译
python
undefinedpython
undefinedIn Python code
在Python代码中
frappe._("Hello World") # Mark string for translation
frappe._("Hello World") # 标记字符串以支持翻译
In JavaScript
在JavaScript中
__("Hello World") # Mark string for translation
```bash__("Hello World") # 标记字符串以支持翻译
```bashTranslation CSV files go in my_app/translations/
翻译CSV文件放在my_app/translations/目录下
e.g., my_app/translations/fr.csv:
例如,my_app/translations/fr.csv:
Hello World,Bonjour le monde
Hello World,Bonjour le monde
undefinedundefined7) Version compatibility
7) 版本兼容性
| Feature | Minimum Version |
|---|---|
| Frappe v16+ |
REST API v2 ( | Frappe v15+ |
| Token-based auth | Frappe v11.0.3+ |
When targeting multiple versions, guard version-specific features:
python
import frappe
if hasattr(frappe, 'extend_doctype_class'):
# v16+ pattern
pass
else:
# Fallback for older versions
pass| 功能 | 最低版本 |
|---|---|
| Frappe v16+ |
REST API v2 ( | Frappe v15+ |
| 基于令牌的认证 | Frappe v11.0.3+ |
当适配多个版本时,需对版本特定功能进行防护:
python
import frappe
if hasattr(frappe, 'extend_doctype_class'):
# v16+模式
pass
else:
# 旧版本兼容方案
passVerification
验证
- App installs without errors:
bench --site <site> install-app my_app - Hooks fire correctly (check scheduler logs, doc events)
- Background jobs enqueue and complete
- succeeds
bench --site <site> migrate - Tests pass:
bench --site <site> run-tests --app my_app
- 应用安装无错误:
bench --site <site> install-app my_app - Hooks触发正常(检查调度器日志、文档事件)
- 后台任务能正常加入队列并完成
- 执行成功
bench --site <site> migrate - 测试通过:
bench --site <site> run-tests --app my_app
Failure modes / debugging
故障模式与调试
- App not found: Check and
apps.txtsites/<site>/site_config.json - Hooks not firing: Verify syntax; restart bench
hooks.py - Background jobs stuck: Check worker status with ; verify Redis
bench doctor - Import errors: Ensure module paths in hooks match actual Python paths
- Developer mode off: DocType changes won't export to files
- 应用未找到:检查和
apps.txtsites/<site>/site_config.json - Hooks未触发:验证语法;重启bench
hooks.py - 后台任务停滞:使用检查工作进程状态;验证Redis
bench doctor - 导入错误:确保hooks中的模块路径与实际Python路径匹配
- 开发者模式关闭:DocType变更不会导出到文件
Escalation
问题升级
- For DocType creation details →
frappe-doctype-development - For API endpoint patterns →
frappe-api-development - For Desk UI customization →
frappe-desk-customization - For Frappe UI frontends →
frappe-frontend-development - For print formats and Jinja →
frappe-printing-templates - For reports →
frappe-reports - For web forms →
frappe-web-forms - For testing →
frappe-testing - For enterprise architecture →
frappe-enterprise-patterns - For Docker/FM environments →
frappe-manager
- DocType创建细节 →
frappe-doctype-development - API端点模式 →
frappe-api-development - Desk UI自定义 →
frappe-desk-customization - Frappe UI前端 →
frappe-frontend-development - 打印格式与Jinja →
frappe-printing-templates - 报表 →
frappe-reports - Web表单 →
frappe-web-forms - 测试 →
frappe-testing - 企业架构 →
frappe-enterprise-patterns - Docker/FM环境 →
frappe-manager
References
参考资料
- references/app-development.md — End-to-end app architecture
- references/version-compat.md — Version compatibility notes
- references/translations.md — Multi-language support
- references/app-development.md — 端到端应用架构
- references/version-compat.md — 版本兼容性说明
- references/translations.md — 多语言支持
Cross-references (owned by specialized skills)
交叉参考(由专业技能模块维护)
- hooks.py and extension points → (hooks-extensions.md)
frappe-doctype-development - Python API reference → (python-api.md)
frappe-api-development
- hooks.py与扩展点 → (hooks-extensions.md)
frappe-doctype-development - Python API参考 → (python-api.md)
frappe-api-development
Guardrails
规范准则
- Use Frappe UI for custom frontends: Never use vanilla JS, jQuery, or custom frameworks. Frappe UI (Vue 3 + TailwindCSS) is the ecosystem standard. See for setup.
frappe-frontend-development - Follow CRM/Helpdesk patterns for CRUD apps: Follow skill for app shell, navigation, list views, and form layouts derived from official Frappe apps.
frappe-ui-patterns - Follow naming conventions: App name must be lowercase with underscores, valid Python identifier
- Use hooks.py for integrations: Never monkey-patch; use doc_events, scheduler_events, boot_session hooks
- Keep hooks.py clean: Only configuration, no logic; import from modules
- Maintain backwards compatibility: Use checks for cross-version support
frappe.version - Export fixtures properly: Use in hooks.py for data that should sync with app
fixtures
- 使用Frappe UI开发自定义前端:禁止使用原生JS、jQuery或其他自定义框架。Frappe UI(Vue 3 + TailwindCSS)是生态系统标准。详见的设置说明。
frappe-frontend-development - CRUD应用遵循CRM/Helpdesk模式:遵循技能模块的规范,采用官方Frappe应用衍生的应用外壳、导航、列表视图和表单布局。
frappe-ui-patterns - 遵循命名规范:应用名称必须为小写并使用下划线,符合Python标识符规则
- 使用hooks.py进行集成:禁止使用猴子补丁;使用doc_events、scheduler_events、boot_session等hooks
- 保持hooks.py简洁:仅存放配置,不包含逻辑;从模块导入逻辑
- 维护向后兼容性:使用检查实现跨版本支持
frappe.version - 正确导出固定数据:使用hooks.py中的配置需随应用同步的数据
fixtures
Common Mistakes
常见错误
| Mistake | Why It Fails | Fix |
|---|---|---|
App not in | App code not loaded | Run |
| Wrong module path in hooks | Events don't fire | Verify path matches actual |
| Duplicate hook registrations | Events fire multiple times | Check hooks.py for duplicates; use list not repeated keys |
| Editing hooks.py without restart | Changes not picked up | Run |
Missing | Module import errors | Ensure every directory has |
| Logic in hooks.py | Hard to test, import errors | Move logic to separate modules, import in hooks |
| Building frontend with vanilla JS/jQuery | Inconsistent with ecosystem | Use Frappe UI (Vue 3); see |
| Custom app shell for CRUD apps | Inconsistent UX | Follow CRM/Helpdesk patterns for navigation and layouts |
| 错误 | 失败原因 | 修复方案 |
|---|---|---|
应用不在 | 应用代码未加载 | 执行 |
| hooks中的模块路径错误 | 事件无法触发 | 验证路径与实际 |
| hook重复注册 | 事件多次触发 | 检查hooks.py是否存在重复项;使用列表而非重复键 |
| 修改hooks.py后未重启 | 变更未生效 | 修改hooks.py后执行 |
缺少 | 模块导入错误 | 确保每个目录都包含 |
| 在hooks.py中编写逻辑 | 难以测试、易出现导入错误 | 将逻辑移至独立模块,在hooks中导入 |
| 使用原生JS/jQuery开发前端 | 与生态系统不一致 | 使用Frappe UI(Vue 3);详见 |
| 为CRUD应用自定义应用外壳 | 用户体验不一致 | 遵循CRM/Helpdesk模式设计导航与布局 |