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命名ロジックを変更するはい
3autonamename プロパティを設定するはい
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(...)

2 番目のパラメーターとして、デフォルト値を None にした method を必ず受け取るようにしてください。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 を使用します。少なくとも1つのフィールド値が DB の状態と異なる場合にのみ実行されます。


doc_events と Controller Events

どちらの仕組みも同じイベントをトリガーします。違いは登録場所です。

項目コントローラー(クラスメソッド)doc_events(hooks.py)
場所{doctype}.py コントローラーファイルアプリ内の hooks.py
使用する場面対象の DocType を所有している場合別のアプリの DocType を拡張する場合
実行順序最初に実行(コントローラー)コントローラーメソッドの後に実行
複数アプリDocType ごとにコントローラーは1つのみ複数のアプリでハンドラーを登録可能

自分が所有していない DocType にフックする場合は、必ず doc_events を使用してください。別のアプリのコントローラーファイルを直接変更してはいけません。

単一イベント内の実行順序

特定のイベント(例:validate)では、次の順序で実行されます。

  1. コントローラーメソッドが最初に実行されます(def validate(self))
  2. doc_events ハンドラーがアプリのインストール順に実行されます
  3. ワイルドカード "*" ハンドラーが、特定の DocType のハンドラーの後に実行されます

extend_doctype_class [v16+]

Frappe v16+ では、既存の DocType にメソッドを追加する場合、extend_doctype_class は doc_events よりもすっきりとした方法です。

hooks.py
extend_doctype_class = {
    "Sales Invoice": [
        "myapp.overrides.sales_invoice.SalesInvoiceExtension"
    ]
}
拡張クラス(Mixin)
# 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 を拡張する可能性がある場合は、override_doctype_class よりも必ず extend_doctype_class を使用してください。
  • 複数のアプリで同じ DocType を拡張できます。拡張は MRO を通じて積み重ねられます。
  • クラスの解決順序は hooks の優先順位に従います: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 をオーバーライドできるアプリは1つのみです。最後にインストールされたアプリが優先され、他のアプリが気付かないうちに動作しなくなるおそれがあります。


複数アプリ間のイベント実行順序

複数のアプリが同じ DocType とイベントに対して doc_events を登録している場合:

  1. ハンドラーはアプリのインストール順(sites/{site}/site_config.json の installed_apps に記載された順)に実行されます。
  2. 順序は Setup > Installed Applications > Update Hooks Resolution Order から変更できます。
  3. override_doctype_class では、最後にインストールされたアプリが優先されます(適用されるオーバーライドは1つのみ)。
  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 に依存させないでください。データベースの状態と値が実際に異なる場合にのみ実行されます。

関連項目

コメント