mcpField
F
frappe-syntax-customapp

frappe-syntax-customapp

github.com/impertio-studio开发198116 次安装30 周前更新
代码仓库

关于 frappe-syntax-customapp

面向 Frappe 开发者的自定义应用语法参考,说明应用结构、配置、模块、补丁和 Fixtures。内容区分 v14 与 v15 及以上版本,并列出安装和迁移相关规则。

主要功能

  1. 01查阅应用创建与安装命令
  2. 02了解应用目录和关键配置文件
  3. 03配置模块、DocType 与补丁
  4. 04设置、导出和导入 Fixtures
  5. 05对照不同版本的配置差异

适用场景

  1. 01查找新建或获取应用的命令
  2. 02规划应用模块和 DocType 目录
  3. 03编写数据迁移补丁
  4. 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.md
examples.md
fixtures.md
methods.md
modules.md
patches.md
patterns.md
pyproject-toml.md
structure.md
syntax.md
templates.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 工作流
  1. 执行 before_migrate 钩子
  2. 执行 [pre_model_sync] 补丁
  3. 同步数据库架构(DocType JSON → 表)
  4. 执行 [post_model_sync] 补丁
  5. 同步固定数据
  6. 执行 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
]

版本差异

方面v14v15+v16+
构建配置setup.pypyproject.tomlpyproject.toml
构建后端setuptoolsflit_coreflit_core
依赖文件requirements.txtpyproject.tomlpyproject.toml
Python 最低版本>=3.10>=3.10>=3.14
INI 补丁是是是
从 v14 迁移到 v15
  1. 使用 flit_core 构建系统创建 pyproject.toml
  2. 将依赖项从 requirements.txt 移至 [project] dependencies
  3. 确认 __init__.py 中的 __version__
  4. 可选择移除:setup.py、MANIFEST.in、requirements.txt
  5. 使用 bench get-app 和 bench install-app 进行测试

重要规则

始终
  1. 在 __init__.py 中定义 __version__ — 没有它,flit 构建会失败
  2. 在 pyproject.toml 中添加 dynamic = ["version"]
  3. 在 modules.txt 中注册每个模块
  4. 在每个 Python 目录中包含 __init__.py
  5. 将 Frappe 依赖项放在 [tool.bench.frappe-dependencies] 中,绝不能放在 [project] dependencies 中
  6. 在补丁中使用批处理并处理错误
  7. 在自定义字段和属性设置器上设置 module 字段,以确保正确导出固定数据
  8. 按依赖关系排列固定数据(状态先于工作流)
绝不要
  1. 将 frappe 或 erpnext 放入 pip 依赖项中(它们不在 PyPI 上,安装会失败)
  2. 创建没有 try/except 和日志记录的补丁
  3. 在 fixtures 中包含用户数据或交易数据(Sales Invoice、User)
  4. 在补丁中硬编码特定站点的值
  5. 不分批处理大型数据集,也不定期调用 frappe.db.commit()
  6. 在目录名称中使用空格(只有 modules.txt 中可以使用空格)
  7. 在生产环境中创建 DocType 后更改模块名称

参考文件

文件内容
structure.mdv14 和 v15 的完整目录结构
pyproject-toml.md完整的 pyproject.toml 和 setup.py 配置
modules.md模块组织、命名和工作区
patches.md补丁语法、模型同步前后处理、批量处理
fixtures.mdFixture 配置、筛选条件和常见 DocType
examples.md完整的最小示例和 ERPNext 扩展应用示例
anti-patterns.md10 个常见错误及其修正方法

另请参阅

  • frappe-syntax-hooks — 完整的 hooks.py 参考
  • frappe-syntax-controllers — DocType 控制器方法
  • frappe-impl-customapp — 实现模式和工作流

评论

支持 Markdown