关于 frappe-syntax-customapp
面向 Frappe 开发者的自定义应用语法参考,说明应用结构、配置、模块、补丁和 Fixtures。内容区分 v14 与 v15 及以上版本,并列出安装和迁移相关规则。
主要功能
- 01查阅应用创建与安装命令
- 02了解应用目录和关键配置文件
- 03配置模块、DocType 与补丁
- 04设置、导出和导入 Fixtures
- 05对照不同版本的配置差异
适用场景
- 01查找新建或获取应用的命令
- 02规划应用模块和 DocType 目录
- 03编写数据迁移补丁
- 04配置自定义字段等 Fixtures
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-syntax-customapp安装后在 Claude Code 中输入/frappe-syntax-customapp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdexamples.mdfixtures.mdmethods.mdmodules.mdpatches.mdpatterns.mdpyproject-toml.mdstructure.mdsyntax.mdtemplates.md详细介绍
Frappe 自定义应用语法
用于构建 Frappe 自定义应用的确定性语法参考,涵盖脚手架、配置、模块、补丁和固定数据。
决策树
What do you need?
├─ Brand new app from scratch → bench new-app
├─ Extend existing ERPNext behavior → bench new-app + required_apps = ["frappe", "erpnext"]
├─ Install existing app from Git → bench get-app <url>
└─ Add functionality to an installed app
├─ New data model → Add module to modules.txt + create DocType
├─ New fields on existing DocType → Fixtures (Custom Field)
├─ Modify field properties → Fixtures (Property Setter)
└─ Data migration → Patch in patches.txt
New app vs extend existing?
├─ Independent functionality → New app
├─ Tightly coupled to one app → New app with required_apps dependency
└─ Small customization (fields, properties) → Extend via fixtures in existing custom app
创建应用
# Create new app (interactive prompts for title, description, publisher, etc.)
bench new-app my_custom_app
# Install on site
bench --site mysite install-app my_custom_app
# Get existing app from Git
bench get-app https://github.com/org/my_custom_app
# Build frontend assets
bench build --app my_custom_app
# Run migrations (patches + fixtures + schema sync)
bench --site mysite migrate
应用目录结构
[v15+] pyproject.toml(主要方式)
apps/my_custom_app/
├── pyproject.toml # Build configuration (flit)
├── README.md
├── my_custom_app/ # Inner Python package
│ ├── __init__.py # MUST contain __version__
│ ├── hooks.py # Frappe integration hooks
│ ├── modules.txt # Module registration
│ ├── patches.txt # Migration scripts
│ ├── patches/ # Patch files
│ │ └── __init__.py
│ ├── my_custom_app/ # Default module (same name as app)
│ │ ├── __init__.py
│ │ └── doctype/
│ ├── public/ # Static assets → /assets/my_custom_app/
│ │ ├── css/
│ │ └── js/
│ ├── templates/ # Jinja templates
│ │ └── includes/
│ └── www/ # Portal pages (URL = directory path)
└── .git/
[v14] setup.py(旧方式)
apps/my_custom_app/
├── setup.py # Build configuration (setuptools)
├── MANIFEST.in
├── requirements.txt # Python dependencies
├── dev-requirements.txt # Dev dependencies (developer_mode only)
├── package.json # Node dependencies
├── my_custom_app/
│ ├── __init__.py
│ ├── hooks.py
│ ├── modules.txt
│ ├── patches.txt
│ └── [same inner structure as v15]
└── .git/
关键文件
init.py(必需)
# my_custom_app/__init__.py
__version__ = "0.0.1"
重要:如果没有 __version__,flit 构建会失败,应用也无法安装。
pyproject.toml [v15+]
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_custom_app"
authors = [
{ name = "Your Company", email = "[email protected]" }
]
description = "Description of your app"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
dependencies = [] # Python packages ONLY — NEVER Frappe/ERPNext
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
erpnext = ">=15.0.0,<16.0.0" # Only if app extends ERPNext
pyproject.toml 的重要规则:
name必须与内层目录名称完全一致- 必须设置
dynamic = ["version"]— flit 会从__init__.py读取__version__ - 绝不能在
[project] dependencies中添加frappe或erpnext(它们不在 PyPI 上) - 始终将 Frappe 应用依赖项放在
[tool.bench.frappe-dependencies]中
setup.py [v14](旧方式)
from setuptools import setup, find_packages
setup(
name="my_custom_app",
version="0.0.1",
description="Description of your app",
author="Your Company",
author_email="[email protected]",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=[],
)
hooks.py(最小骨架)
app_name = "my_custom_app"
app_title = "My Custom App"
app_publisher = "Your Company"
app_description = "Description"
app_email = "[email protected]"
app_license = "MIT"
required_apps = ["frappe"] # Or ["frappe", "erpnext"] if extending ERPNext
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Custom App"]]},
]
模块
modules.txt
My Custom App
Integrations
Settings
Reports
规则:
- 每行一个模块名称 — 绝不能留空行或尾随空格
- 模块名称使用空格;目录名称使用下划线(
My Custom App→my_custom_app/) - 每个 DocType 都必须属于一个已注册的模块
- 始终在每个模块目录中包含
__init__.py
模块目录结构
my_custom_app/
├── my_custom_app/ # "My Custom App" module
│ ├── __init__.py
│ └── doctype/
├── integrations/ # "Integrations" module
│ ├── __init__.py
│ └── doctype/
├── settings/ # "Settings" module
│ ├── __init__.py
│ └── doctype/
└── reports/ # "Reports" module
├── __init__.py
└── report/
DocType 目录(位于模块内)
doctype/my_doctype/
├── __init__.py # Empty (REQUIRED)
├── my_doctype.json # DocType definition (generated by UI)
├── my_doctype.py # Python controller
├── my_doctype.js # Client script
├── test_my_doctype.py # Unit tests
└── my_doctype_dashboard.py # Dashboard config
补丁(迁移脚本)
使用 INI 分区的 patches.txt
[pre_model_sync]
# Runs BEFORE schema sync — old fields still available
myapp.patches.v1_0.backup_old_data
[post_model_sync]
# Runs AFTER schema sync — new fields available
myapp.patches.v1_0.populate_new_fields
myapp.patches.v1_0.cleanup_data
补丁实现
# myapp/patches/v1_0/populate_new_fields.py
import frappe
def execute():
"""Populate new fields with default values."""
batch_size = 1000
offset = 0
while True:
records = frappe.get_all(
"MyDocType",
filters={"new_field": ["is", "not set"]},
fields=["name"],
limit_page_length=batch_size,
limit_start=offset,
)
if not records:
break
for record in records:
frappe.db.set_value(
"MyDocType", record.name,
"new_field", "default_value",
update_modified=False,
)
frappe.db.commit()
offset += batch_size
模型同步前与同步后
| 情况 | 分区 | 原因 |
|---|---|---|
| 从旧字段迁移数据 | [pre_model_sync] | 旧字段仍然存在 |
| 重命名字段并保留数据 | [pre_model_sync] | 旧名称仍然可用 |
| 填充新的必填字段 | [post_model_sync] | 新字段已存在 |
| 常规数据清理 | [post_model_sync] | 不依赖架构 |
重新运行补丁
# Patches run ONCE. To re-run, make the line unique with a comment:
myapp.patches.v1_0.my_patch #2024-01-15
bench migrate 工作流
- 执行
before_migrate钩子 - 执行
[pre_model_sync]补丁 - 同步数据库架构(DocType JSON → 表)
- 执行
[post_model_sync]补丁 - 同步固定数据
- 执行
after_migrate钩子
固定数据
hooks.py 配置
fixtures = [
"Category", # All records
{"dt": "Custom Field", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Role", "filters": [["name", "like", "MyApp%"]]},
]
导出和导入
# Export fixtures to JSON files
bench --site mysite export-fixtures --app my_custom_app
# Import happens automatically during bench migrate or install-app
固定数据与补丁
| 内容 | 固定数据 | 补丁 |
|---|---|---|
| 自定义字段 | 是 | 否 |
| 属性设置器 | 是 | 否 |
| 角色、工作流 | 是 | 否 |
| 数据转换 | 否 | 是 |
| 一次性迁移 | 否 | 是 |
| 种子配置数据 | 是 | 否 |
固定数据顺序
始终按依赖关系排列固定数据,让依赖项先出现:
fixtures = [
"Workflow State", # FIRST — Workflow depends on states
"Workflow", # SECOND
]
版本差异
| 方面 | v14 | v15+ | v16+ |
|---|---|---|---|
| 构建配置 | setup.py | pyproject.toml | pyproject.toml |
| 构建后端 | setuptools | flit_core | flit_core |
| 依赖文件 | requirements.txt | pyproject.toml | pyproject.toml |
| Python 最低版本 | >=3.10 | >=3.10 | >=3.14 |
| INI 补丁 | 是 | 是 | 是 |
从 v14 迁移到 v15
- 使用 flit_core 构建系统创建
pyproject.toml - 将依赖项从
requirements.txt移至[project] dependencies - 确认
__init__.py中的__version__ - 可选择移除:
setup.py、MANIFEST.in、requirements.txt - 使用
bench get-app和bench install-app进行测试
重要规则
始终
- 在
__init__.py中定义__version__— 没有它,flit 构建会失败 - 在 pyproject.toml 中添加
dynamic = ["version"] - 在
modules.txt中注册每个模块 - 在每个 Python 目录中包含
__init__.py - 将 Frappe 依赖项放在
[tool.bench.frappe-dependencies]中,绝不能放在[project] dependencies中 - 在补丁中使用批处理并处理错误
- 在自定义字段和属性设置器上设置
module字段,以确保正确导出固定数据 - 按依赖关系排列固定数据(状态先于工作流)
绝不要
- 将
frappe或erpnext放入 pip 依赖项中(它们不在 PyPI 上,安装会失败) - 创建没有 try/except 和日志记录的补丁
- 在 fixtures 中包含用户数据或交易数据(Sales Invoice、User)
- 在补丁中硬编码特定站点的值
- 不分批处理大型数据集,也不定期调用
frappe.db.commit() - 在目录名称中使用空格(只有
modules.txt中可以使用空格) - 在生产环境中创建 DocType 后更改模块名称
参考文件
| 文件 | 内容 |
|---|---|
| structure.md | v14 和 v15 的完整目录结构 |
| pyproject-toml.md | 完整的 pyproject.toml 和 setup.py 配置 |
| modules.md | 模块组织、命名和工作区 |
| patches.md | 补丁语法、模型同步前后处理、批量处理 |
| fixtures.md | Fixture 配置、筛选条件和常见 DocType |
| examples.md | 完整的最小示例和 ERPNext 扩展应用示例 |
| anti-patterns.md | 10 个常见错误及其修正方法 |
另请参阅
frappe-syntax-hooks— 完整的 hooks.py 参考frappe-syntax-controllers— DocType 控制器方法frappe-impl-customapp— 实现模式和工作流
评论
支持 Markdown