導入 — エラーに悩まされないOdoo開発へ
Odooを使っていると、ある日ログに次のメッセージを見つけることがよくあります:
ValueError: Expected singleton
このメッセージはOdooのORMでよく見られるエラーの一つです。本質はシンプルで、メソッドが“ちょうど1件”のレコードを期待しているのに、実際には複数件が渡されてしまったときに起きます。表面的には技術的に見えても、Odooのレコードセットの性質を理解すれば原因は明確になります。
この記事では“Expected Singleton”が何を意味するのか、その原因、そして業務ロジックや外部連携を壊さずに安全に修正する方法を解説します。
Odooでの「Expected Singleton」とは何を示すのか?
OdooのORM(Object Relational Mapping)はオブジェクト単体ではなく、レコードセット単位で動作する点が重要です。レコードセットは次のいずれかになります:
- 1件のレコード
- 複数のレコード
- レコードが存在しない(空)
メソッドが1件のレコードを前提に設計されているのに、実行時に複数件のレコードセットが渡されると、次のエラーが投げられます:
ValueError: Expected singleton
平たく言えば:
Odooは1件を期待していたが、複数が届いた、ということです。
このエラーはよく次の場所で見つかります:
- サーバーログ
- カスタムモジュール内のメソッド
- 計算フィールド(computed fields)
- ボタンアクション(server actions含む)
- 自動処理(Automated Actions)
- バルク更新処理(一括処理)
ここでのポイントは、レコードセットの動作を正しく理解することが問題解決の鍵だという点です。
このエラーが発生する理由
原因1 — レコードセットの誤解
Odoo内ではselfは基本的にレコードセットです。
あなたが“単一レコード”だと思っていても、実際には複数レコードでメソッドが呼ばれることがあります。例えば:
- リストビューでの一括操作
- 自動ワークフローのトリガー
- サーバーアクションやバッチ処理
- 外部APIからのインポート処理
こうした場面で単一前提のコードを書くと失敗します。
原因2 — メソッド内でループを省略している
問題になりやすいコード例:
def action_confirm(self): self.state = 'confirmed'
もしselfが複数件を含むと、どのレコードにstateを設定すべきか曖昧になります。
正しい書き方の例:
def action_confirm(self): for record in self: record.state = 'confirmed'
原因3 — ensure_one()の誤用
Odooには安全機構があります:
self.ensure_one()
これを呼ぶと“正確に1件”であることを強制し、複数件なら明示的にsingletonエラーを出します。
フォーム表示など、業務的に必ず1件で処理すべき場合にのみ利用してください。
原因4 — searchが複数件を返すケース
典型例:
partner = self.env['res.partner'].search([('name', '=', 'John')])
“John”が複数レコード存在すると、後続処理で単一前提だとエラーになり得ます。
安全策の例:
partner = self.env['res.partner'].search([('name', '=', 'John')], limit=1)
原因5 — 関連フィールドでの曖昧さ
Many2oneやOne2manyの取り扱いで混乱が起きやすいです。
典型例:
例:self.order_line.product_id.name のような参照
order_lineが複数行あると、その式は曖昧になります。
Expected Singletonエラーの具体的な直し方
対策ステップ1 — レコードセットを必ずループする
Odooの基本ルールとして、selfは複数件を含む可能性があると想定してください。
デフォルトの書き方:
for record in self: record.process_logic()
対策ステップ2 — 必要に応じてlimit=1を使う
論理的に“1件のみ”であるべきなら明示的に絞るべきです:
record = self.env['model.name'].search(domain, limit=1)
対策ステップ3 — 関連フィールドを検証する
次をチェックしてください:
- Many2oneの単一性
- One2manyの集合(コレクション)性
- ドメインフィルタの妥当性
意図せず複数行を扱っていないかを確認します。
対策ステップ4 — APIやインポート処理を見直す
連携が多い環境では一括処理が原因でこのエラーが頻発します。
外部システムからデータ同期する場合は、バッチを安全に処理する設計を徹底してください。
今後同じミスを防ぐための設計指針
- 単一前提に頼らないこと
- 複数選択でメソッドをテストすること
- ループをデフォルトにすること
- limit=1は意図的に使うこと
- 関連フィールドの設計を明確にすること
複雑な統合環境では、自動インポートや定期処理の際にこの種のエラーが表面化しやすいです。バッチ対応の設計を行うと、運用の安定性が大きく改善します。
DasoloがレコードセットとORMエラーにどう対処するか
Expected Singletonは単なるコーディングミスに留まらず、レコードセットの前提やORM利用、データフローの設計上のズレを示すことが多いエラーです。
DasoloではORM関連の不具合に対して、モジュール全体のレコードセット扱いを見直すことで対応します。特に自動処理や連携、計算フィールドで“1件前提”の設計が行き過ぎている箇所に着目します。
再発防止のために我々が重視している点:
- 明示的なレコードセットの反復パターン
- ensure_one()の安全な運用ルール
- 予測可能なドメイン定義(ドメインフィルタ)
- 関係フィールドの明確な設計方針
- 自動化トリガーの制御と境界設計
こうした設計をスケーラブルに組み込むことで、本番環境での予期せぬランタイムエラーが著しく減ります。
まとめ — 単一レコード前提を見直して堅牢なシステムを作る
要するに、Odooの“Expected Singleton”は開発現場で頻出するORM例外で、複数レコードを単一レコードとして扱おうとしたときに発生します。単なる見落としに見えて、カスタムモジュールや自動処理の設計の不整合を示す重要なサインです。
Odooのレコードセットの振る舞いを理解し、安全な反復処理や明確な検証を導入すれば、このエラーは容易に防げます。構造化されたレコード処理、厳格なバリデーション、制御された自動化が安定稼働の鍵です。
適切に対処すれば、singletonエラーはコード品質を向上させる貴重なフィードバックになります。長期的な信頼性強化につなげましょう。
よくある質問(FAQ)
いいえ。Odoo 14、15、16、17など広範なバージョンで共通して発生します。
いいえ。データベース破損ではなく、レコード取り扱いの論理的な問題です。
いいえ。業務上“必ず1件”で処理すべき場面のみで使い、安易な使用は避けてください。