mcpField
F
frappe-syntax-hooks-events

frappe-syntax-hooks-events

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

关于 frappe-syntax-hooks-events

面向 Frappe 开发者的 doc_events 生命周期钩子参考,说明事件执行顺序、注册方式与处理器选择,重点涵盖跨应用扩展和事务行为。

主要功能

  1. 01插入、保存、提交等事件顺序
  2. 02hooks.py 中注册处理器
  3. 03按需求选择生命周期事件
  4. 04doc_events 与控制器事件对比
  5. 05多应用事件排序与事务规则

适用场景

  1. 01在保存前验证文档数据
  2. 02提交后创建关联文档
  3. 03为其他应用的 DocType 添加事件处理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/frappe_claude_skill_package --skill frappe-syntax-hooks-events

安装后在 Claude Code 中输入/frappe-syntax-hooks-events确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
.gitkeep
anti-patterns.md
event-order.md
examples.md

详细介绍

文档生命周期钩子(doc_events)

快速参考:事件执行顺序

插入(新文档)
顺序事件用途可抛出异常?
1before_insert命名之前设置默认值是
2before_naming修改命名逻辑是
3autoname设置 name 属性是
4before_validate自动设置缺失的值是
5validate验证逻辑——抛出异常以中止是
6before_save写入数据库之前进行最终修改是
7db_insert内部——将行写入数据库—
8after_insert插入后逻辑(仅运行一次)是
9on_update保存后逻辑(每次保存时运行)是
10on_change任意字段值发生变化时触发是
保存(现有文档)
顺序事件用途
1before_validate自动设置缺失的值
2validate验证逻辑——抛出异常以中止
3before_save写入数据库之前进行最终修改
4db_update内部——更新数据库中的行
5on_update保存后逻辑
6on_change任意字段值发生变化时触发
提交
顺序事件用途
1before_validate自动设置缺失的值
2validate验证逻辑
3before_save写入数据库之前进行最终修改
4before_submit提交前逻辑——抛出异常以中止
5db_update内部——更新数据库中的行
6on_submit提交后逻辑(GL 分录等)
7on_update保存后逻辑
8on_change任意字段值发生变化时触发
取消
顺序事件用途
1before_cancel取消前验证
2db_update内部——更新数据库中的行
3on_cancel取消后逻辑(冲销 GL 等)
4on_change任意字段值发生变化时触发
删除
顺序事件用途
1on_trash删除前清理
2after_delete删除后逻辑
其他操作
操作事件(按顺序)
重命名before_rename → after_rename
修订新修订文档会执行 before_insert 链
提交后更新before_update_after_submit → db_update → on_update_after_submit → on_change

hooks.py 中的 doc_events:语法

基本结构
# hooks.py
doc_events = {
    "Sales Invoice": {
        "on_submit": "myapp.events.sales_invoice.on_submit",
        "on_cancel": "myapp.events.sales_invoice.on_cancel",
    },
    "Purchase Order": {
        "validate": "myapp.events.purchase_order.validate",
    }
}
通配符:应用于所有 DocType
doc_events = {
    "*": {
        "after_insert": "myapp.events.global_handler.after_insert_all",
        "on_update": "myapp.events.global_handler.track_changes",
    }
}

务必使用 "*"(包含星号的字符串)作为键。这样会为每个 DocType 触发处理函数。

每个事件使用多个处理函数
doc_events = {
    "Sales Invoice": {
        "on_submit": [
            "myapp.events.accounting.create_gl_entries",
            "myapp.events.notifications.send_invoice_email",
        ]
    }
}
处理函数签名
# myapp/events/sales_invoice.py
def on_submit(doc, method=None):
    """
    doc    — the Document instance (e.g., Sales Invoice)
    method — string name of the event (e.g., "on_submit"), or None
    """
    if doc.grand_total > 10000:
        frappe.sendmail(...)

务必将 method 作为第二个参数(默认值为 None)。Frappe 会自动传入该参数。


决策树:使用哪个事件

“我需要在保存前验证数据”

→ 使用 validate。此处始终调用 frappe.throw(),以阻止无效保存。

“我需要自动设置默认值”

→ 使用 before_validate。它会在 validate 之前运行,因此可在验证检查前设置默认值。

“我需要仅在首次创建时运行逻辑”

→ 使用 after_insert。它仅在插入时触发,不会在后续保存时触发。

“我需要在每次保存时运行逻辑(插入 + 更新)”

→ 使用 on_update。插入和保存操作都会触发它。

“我需要在提交后创建关联文档”

→ 使用 on_submit。切勿在 validate 中创建关联文档——此时文档尚未提交。

“我需要在取消时撤销关联文档”

→ 使用 on_cancel。务必在此处清理 GL 分录、库存分类账分录和关联文档。

“我需要修改文档名称”

→ 在控制器中使用 autoname,或使用 before_naming 实现条件逻辑。

“我需要在特定条件下阻止删除”

→ 使用 on_trash。调用 frappe.throw() 以阻止删除。

“我需要更新已提交文档的字段”

→ 使用 before_update_after_submit 进行验证,并使用 on_update_after_submit 执行副作用。

“我需要仅在值实际发生变化时运行逻辑”

→ 使用 on_change。只有至少一个字段值与数据库状态不同时才会触发。


doc_events 与控制器事件

这两种机制都会触发相同的事件。区别在于注册位置。

方面控制器(类方法)doc_events(hooks.py)
位置{doctype}.py 控制器文件应用中的 hooks.py
适用场景你拥有该 DocType你要扩展其他应用的 DocType
执行顺序先运行(控制器)在控制器方法之后运行
多个应用每个 DocType 只能有一个控制器多个应用可以注册处理程序

为不属于自己的 DocType 添加钩子时,务必使用 doc_events。切勿直接修改其他应用的控制器文件。

单个事件中的执行顺序

对于给定事件(例如 validate):

  1. 首先运行控制器方法(def validate(self))
  2. doc_events 处理程序按照应用安装顺序运行
  3. 通配符 "*" 处理程序在特定 DocType 处理程序之后运行

extend_doctype_class [v16+]

在 Frappe v16+ 中,extend_doctype_class 是一种更简洁的替代方案,可用于向现有 DocType 添加方法,而无需使用 doc_events。

hooks.py
extend_doctype_class = {
    "Sales Invoice": [
        "myapp.overrides.sales_invoice.SalesInvoiceExtension"
    ]
}
扩展类(混入类)
# myapp/overrides/sales_invoice.py
import frappe

class SalesInvoiceExtension:
    def validate(self):
        """This is called as part of the controller chain."""
        if self.grand_total < 0:
            frappe.throw("Grand total cannot be negative")

    def custom_method(self):
        """Custom methods are also available on the doc instance."""
        return self.items
重要规则
  • 在 v16+ 中,如果可能有多个应用扩展同一个 DocType,始终优先使用 extend_doctype_class,而非 override_doctype_class。
  • 多个应用可以扩展同一个 DocType——扩展会通过 MRO 叠加。
  • 类解析顺序遵循钩子优先级:class Final(App2Mixin, App1Mixin, Original)。
  • 扩展方法(如 validate)作为控制器的一部分运行,而不是作为独立的 doc_events 处理程序运行。

override_doctype_class [v14+]

完全替换控制器类。使用时务必格外谨慎。

# hooks.py
override_doctype_class = {
    "ToDo": "myapp.overrides.todo.CustomToDo"
}
# myapp/overrides/todo.py
from frappe.desk.doctype.todo.todo import ToDo

class CustomToDo(ToDo):
    def validate(self):
        super().validate()  # ALWAYS call super() to preserve original logic
        # Your additions here

如果可以使用 extend_doctype_class(v16+),切勿使用 override_doctype_class。一个 DocType 只能由一个应用覆盖——最后安装的应用会生效,且会悄无声息地破坏其他应用。


多应用事件顺序

当多个应用为同一个 DocType 和事件注册 doc_events 时:

  1. 处理程序按照应用安装顺序执行(顺序见 sites/{site}/site_config.json → installed_apps)。
  2. 可通过设置 > 已安装应用程序 > 更新钩子解析顺序更改顺序。
  3. 对于 override_doctype_class,最后安装的应用会生效(仅应用一个覆盖)。
  4. 对于 extend_doctype_class(v16+),所有扩展都会累积叠加。

事务行为

从 before_validate 到 on_change 的所有文档事件都在同一个数据库事务中运行。

  • 如果任意事件引发异常,整个操作都会回滚(包括 db_insert/db_update)。
  • after_insert、on_update、on_submit、on_cancel 均在事务提交前运行。
  • 只有所有事件都成功完成后,事务才会提交。
  • after_delete 在 DELETE 语句执行后运行,但仍处于请求事务中。

绝不要假设事件处理程序中的数据已提交到数据库。其他并发请求要等到整个请求完成后才能看到你的更改。


重要规则

  1. 始终使用 frappe.throw() 中止操作,绝不要使用 raise Exception。
  2. 绝不要在 autoname 或 before_naming 之外修改 doc.name。
  3. 在子类中重写控制器方法时,始终调用 super().{event}()。
  4. 绝不要在 validate 或 before_save 中使用 doc.save(),否则会导致无限递归。
  5. 如果钩子需要绕过权限,始终显式设置 doc.flags.ignore_permissions = True,绝不要假设钩子以 Administrator 身份运行。
  6. 绝不要在 validate 中执行耗时操作(API 调用、文件 I/O),应改用 after_insert 或 on_update 配合 frappe.enqueue()。
  7. 始终使用 doc.flags 在同一请求中的事件之间传递信息(例如,doc.flags.skip_notification = True)。
  8. 绝不要依赖 on_change 执行关键逻辑——只有当值与数据库状态实际不同时,它才会触发。

另请参阅

评论

支持 Markdown