mcpField
F
frappe-syntax-controllers

frappe-syntax-controllers

github.com/impertio-studio開発198111 回インストール30 週間前に更新
リポジトリ

frappe-syntax-controllers について

Frappe で DocType コントローラーを実装する開発者向けの構文リファレンスです。ライフサイクル、命名、拡張方法と実装上の注意点を扱います。

主な機能

  1. 01コントローラーファイルとクラスの命名
  2. 02ドキュメントライフサイクルフックの順序
  3. 03ドキュメント命名と autoname メソッド
  4. 04コントローラー拡張方式の比較
  5. 05ホワイトリストメソッドと文書状態

活用シーン

  1. 01DocType のサーバー側ロジックの実装
  2. 02適切なライフサイクルフックの選択
  3. 03既存 DocType への独自動作の追加

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/frappe-syntax-controllersを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
anti-patterns.md
document-api-complete.md
events.md
examples.md
flags.md
hooks.md
lifecycle-methods.md
methods.md
patterns.md
syntax.md

詳細

Frappe の構文:ドキュメントコントローラー

ドキュメントコントローラーは、DocType のサーバー側ロジックをすべて定義する Python クラスです。 すべての DocType には、コントローラーファイルが必ず 1 つだけあります。コントローラークラスは frappe.model.document.Document を継承します。

クイックリファレンス

import frappe
from frappe import _
from frappe.model.document import Document

class SalesOrder(Document):
    def autoname(self):
        """Custom naming logic. Sets self.name."""
        self.name = f"SO-{self.customer_code}-{frappe.utils.now_datetime().year}"

    def validate(self):
        """MAIN validation — runs on EVERY save (insert and update).
        Changes to self ARE saved to database."""
        if not self.items:
            frappe.throw(_("Items are required"))
        self.total = sum(item.amount for item in self.items)

    def on_update(self):
        """After save — changes to self are NOT saved.
        Use frappe.db.set_value() for post-save field changes."""
        self.notify_linked_docs()

    def on_submit(self):
        """After submit (docstatus 0 -> 1). Create ledger entries here."""
        self.create_gl_entries()

    def on_cancel(self):
        """After cancel (docstatus 1 -> 2). Reverse ledger entries here."""
        self.reverse_gl_entries()

    @frappe.whitelist()
    def recalculate(self):
        """Exposed to client JS via frm.call('recalculate')."""
        self.total = sum(item.amount for item in self.items)
        return {"total": self.total}
ファイルの場所と命名規則
DocType 名クラス名ファイルパス
Sales OrderSalesOrderselling/doctype/sales_order/sales_order.py
My Custom DocMyCustomDocmodule/doctype/my_custom_doc/my_custom_doc.py

ルール:DocType 名 -> PascalCase のクラス -> snake_case のファイル名。必ず正確に一致させてください。


ライフサイクルフックの実行順序

挿入(新しいドキュメント)
before_insert -> before_naming -> autoname -> before_validate -> validate
-> before_save -> [db_insert] -> after_insert -> on_update -> on_change
保存(既存のドキュメント)
before_validate -> validate -> before_save -> [db_update]
-> on_update -> on_change
送信(docstatus 0 -> 1)
before_validate -> validate -> before_submit -> [db_update]
-> on_submit -> on_update -> on_change
キャンセル(docstatus 1 -> 2)
before_cancel -> [db_update] -> on_cancel -> on_change
送信後の更新
before_update_after_submit -> [db_update]
-> on_update_after_submit -> on_change
削除
on_trash -> [db_delete] -> after_delete
破棄 [v15+]
before_discard -> [db_set docstatus=2] -> on_discard

パラメーターを含むフックの完全なリファレンス:lifecycle-methods.md を参照してください。


フック選択の判断ツリー

What do you need to do?
|
+-- Validate data or calculate fields?
|   +-- validate (changes to self ARE saved)
|
+-- Action AFTER save (emails, sync, linked docs)?
|   +-- on_update (changes to self are NOT saved)
|
+-- Only for NEW documents?
|   +-- after_insert (runs once on first save only)
|
+-- Custom document name?
|   +-- autoname (set self.name)
|
+-- Before/after SUBMIT?
|   +-- Validate before submit? -> before_submit
|   +-- Create entries after submit? -> on_submit
|
+-- Before/after CANCEL?
|   +-- Check linked docs? -> before_cancel
|   +-- Reverse entries? -> on_cancel
|
+-- Cleanup before delete?
|   +-- on_trash
|
+-- React to ANY value change (including db_set)?
|   +-- on_change (MUST be idempotent)

重要なルール

1. on_update の後の変更は保存されません
# WRONG - change is lost after on_update
def on_update(self):
    self.status = "Completed"  # NOT saved to database

# CORRECT - use db_set or frappe.db.set_value
def on_update(self):
    self.db_set("status", "Completed")
2. コントローラー内で frappe.db.commit() を呼び出さないでください
# WRONG - breaks Frappe transaction management
def validate(self):
    frappe.db.commit()  # Can cause partial updates on error

# CORRECT - Frappe commits automatically at end of request
def validate(self):
    self.update_related()  # No commit needed
3. オーバーライドするときは必ず super() を呼び出してください
# WRONG - parent validation is skipped entirely
def validate(self):
    self.custom_check()

# CORRECT - parent logic preserved
def validate(self):
    super().validate()
    self.custom_check()
4. 再帰を防止するにはフラグを使用してください
def on_update(self):
    if self.flags.get("from_linked_doc"):
        return
    linked = frappe.get_doc("Linked Doc", self.linked_doc)
    linked.flags.from_linked_doc = True
    linked.save()
5. 検証ロジックを on_update に記述しないでください
# WRONG - document is already saved when this throws
def on_update(self):
    if self.total < 0:
        frappe.throw("Invalid total")  # Too late!

# CORRECT - validate BEFORE save
def validate(self):
    if self.total < 0:
        frappe.throw("Invalid total")  # Blocks save

ドキュメントの命名(autoname)

メソッド例結果バージョン
field:fieldnamefield:customer_nameABC Companyすべて
naming_series:naming_series:SO-2024-00001すべて
式PRE-.#####PRE-00001すべて
旧形式INV-{YYYY}-{####}INV-2024-0001v16 で非推奨
hash / randomhasha1b2c3d4e5すべて
PromptPromptユーザーが名前を入力すべて
autoincrementautoincrement1、2、3すべて
UUIDUUID550e8400-e29b-...v16+
カスタムメソッドコントローラー内の autoname()任意のパターンすべて
カスタム autoname メソッド
from frappe.model.naming import getseries

class Project(Document):
    def autoname(self):
        prefix = f"P-{self.customer[:3].upper()}-"
        self.name = getseries(prefix, 3)
        # Result: P-ACM-001, P-ACM-002, etc.
UUID による命名 [v16+]

DocType 定義で autoname = "UUID" を設定します。Frappe が UUID v4 を生成します。

When to use UUID:              When to use traditional naming:
- Cross-system sync            - User-facing references (SO-00001)
- Bulk record creation         - Sequential numbering required
- Global uniqueness needed     - Auditing requires readable names

コントローラーの拡張方法

1. override_doctype_class(完全な置き換え)[すべてのバージョン]
# hooks.py
override_doctype_class = {
    "Sales Order": "custom_app.overrides.CustomSalesOrder"
}

# custom_app/overrides.py
from erpnext.selling.doctype.sales_order.sales_order import SalesOrder

class CustomSalesOrder(SalesOrder):
    def validate(self):
        super().validate()  # ALWAYS call super()
        self.custom_validation()

警告:DocType クラスをオーバーライドできるアプリは 1 つだけです。複数のオーバーライドは競合します。

2. extend_doctype_class(ミックスイン、非破壊的)[v16+]
# hooks.py
extend_doctype_class = {
    "Address": ["custom_app.extensions.address.GeocodingMixin"],
    "Contact": [
        "custom_app.extensions.common.ValidationMixin",
        "custom_app.extensions.contact.PhoneMixin"
    ]
}

# custom_app/extensions/address.py
from frappe.model.document import Document

class GeocodingMixin(Document):
    @property
    def full_address(self):
        return f"{self.address_line1}, {self.city}, {self.country}"

    def validate(self):
        super().validate()
        self.geocode_address()

v16 以降では、override_doctype_class よりも extend_doctype_class を必ず優先してください。 複数のアプリで同じ DocType を安全に拡張できます。

3. doc_events(個々のイベントにフック)[すべてのバージョン]
# hooks.py
doc_events = {
    "Sales Order": {
        "validate": "custom_app.events.validate_sales_order",
        "on_submit": "custom_app.events.on_submit_sales_order"
    },
    "*": {  # ALL DocTypes
        "after_insert": "custom_app.events.log_creation"
    }
}

# custom_app/events.py
def validate_sales_order(doc, method=None):
    if doc.total > 100000:
        doc.requires_approval = 1
使い分け
Need full class replacement?     -> override_doctype_class [all versions]
Need to add methods/properties?  -> extend_doctype_class [v16+]
Need to hook one or two events?  -> doc_events [all versions]
Need to extend in v14/v15?       -> override_doctype_class or doc_events

ホワイトリスト登録されたメソッド

@frappe.whitelist() を使用して、コントローラーメソッドをクライアント側 JavaScript から利用できるようにします。

class SalesOrder(Document):
    @frappe.whitelist()
    def send_email(self, recipient):
        """Callable from JS: frm.call('send_email', {recipient: '...'})"""
        frappe.sendmail(recipients=[recipient], message="Order confirmed")
        return {"status": "sent"}
// Client-side call
frm.call('send_email', { recipient: '[email protected]' })
    .then(r => frappe.msgprint(r.message.status));

ルール:

  • 必ず @frappe.whitelist() デコレーターを追加してください。これがないと、メソッドをクライアントから呼び出せません。
  • メソッドはコントローラークラス上に定義する必要があります(単独で定義しないでください)。
  • 権限チェックは自動的に行われます(ユーザーにはドキュメントへの読み取りアクセス権が必要です)。

送信可能なドキュメント

is_submittable = 1 のドキュメントは、docstatus のライフサイクルに従います。

docstatus状態編集可能遷移
0下書きはい-> 1(送信)
1送信済み「送信時に許可」フィールドのみ-> 2(キャンセル)
2キャンセル済みいいえなし(修正により新しい下書きを作成)

on_submit と on_cancel は必ずセットで実装してください。 on_submit で作成したものは、必ず on_cancel で元に戻してください。


継承パターン

# Standard controller
from frappe.model.document import Document
class MyDoc(Document): pass

# Tree DocType (hierarchical)
from frappe.utils.nestedset import NestedSet
class Department(NestedSet):
    nsm_parent_field = "parent_department"

# Virtual DocType (no database table)
class ExternalData(Document):
    def load_from_db(self): ...
    def db_insert(self, *args, **kwargs): ...
    def db_update(self, *args, **kwargs): ...
    @staticmethod
    def get_list(args): ...
    @staticmethod
    def get_count(args): ...

型アノテーション [v15+]

class Person(Document):
    if TYPE_CHECKING:
        from frappe.types import DF
        first_name: DF.Data
        last_name: DF.Data
        birth_date: DF.Date
        company: DF.Link

hooks.py で自動生成を有効にします:export_python_type_annotations = True


バージョンごとの差異

機能v14v15v16
型アノテーションなし自動生成あり
before_discard / on_discardなしありあり
flags.notify_updateなしありあり
extend_doctype_classなしなしあり
UUID による自動命名なしなしあり
旧形式の命名ありあり非推奨

参考ファイル

ファイル内容
lifecycle-methods.md実行順序の図を含む、すべてのフック
document-api-complete.mdDocument API 完全版:カテゴリ別(CRUD、フィールド、DB、権限、フラグ、子テーブル、命名)のすべてのメソッド
methods.mdDocument クラスのメソッドシグネチャ
events.md順序どおりに並んだすべてのドキュメントイベント
examples.md動作するコントローラーの完全な例
anti-patterns.mdよくある間違いと修正方法
flags.mdフラグシステム(doc.flags、frappe.flags)
hooks.mdhooks.py とコントローラーの連携
patterns.mdよく使われるコントローラーパターン
syntax.mdコントローラークラスの構文リファレンス

関連スキル

  • frappe-syntax-serverscripts -- Server Scripts(サンドボックスを使う代替手段)
  • frappe-syntax-hooks -- hooks.py の設定
  • frappe-impl-controllers -- 実装ワークフロー
  • frappe-core-permissions -- 権限システム

コメント