イントロダクション
多くのOdoo開発者はレコード同士をつなぐ際にまずMany2oneを選びます。これは一般的なケースで最適ですが、結びつけたい相手が状況によって複数種のドキュメント(例:受注、発注、製造指示など)になり得る場面では柔軟性が足りません。そうした“相手が流動的”なリンクを実現するために用意されているのがReferenceフィールドです。
ReferenceはOdooの中でも特に柔軟なフィールド種別で、あらかじめ決めた複数のモデル群から任意のモデルを選び、そのモデル内のレコードを指すことができます。つまり、ユーザーがまずドキュメント種別を選び、それから該当レコードを指定するという二段階で参照先を決めることで、ワークフローに応じた多態的(ポリモーフィック)な関連付けを実現します。
本稿ではReferenceフィールドが内部的にどんな値を保持するか、Odooのデータモデル上でどのように振る舞うか、StudioやPythonでの作り方、そして現場で役立つ具体的な利用例をわかりやすく整理して解説します。
OdooのReferenceフィールドとは何か
OdooのORMにおけるReferenceは特別な型で、フィールド定義のselectionに列挙した任意のモデルのうちから参照先を選べる点がMany2oneと決定的に異なります。Many2oneは固定の単一モデルにしか向きませんが、Referenceは複数モデルを横断して使えるよう設計されています。
データベース上では、Referenceはテキストで「モデル名,レコードID」というフォーマットで保存されます。たとえば受注42番を参照している場合は sale.order,42 のような文字列になります。直接SQLで検索や集計を行う必要がある場面では、このフォーマットを意識した取り扱いが求められます。
UI上はユーザーが最初にドロップダウンからドキュメントの種類を選び、次にそのモデル内から検索でレコードを選ぶ二段階の入力フォームとして表示されます。慣れれば直感的ですが、意図を説明しないと初動で迷うこともあるため現場教育が重要です。
Pythonでの基本的な定義イメージは次のようになります。
from odoo import fields, models
class HelpDeskTicket(models.Model):
_inherit = 'helpdesk.ticket'
related_document = fields.Reference(
selection=[
('sale.order', 'Sale Order'),
('purchase.order', 'Purchase Order'),
('account.move', 'Invoice'),
('project.task', 'Project Task'),
],
string='Related Document',
)
ここで重要なのはselectionに渡すリストで、各要素は技術的なモデル名と画面に表示するラベルの組です。どのモデルを選択肢に含めるかは貼り付け先の業務要件に合わせて厳選します。
また、選択肢を実行時に動的に生成してir.modelから拾ってくる設計も可能です。柔軟性は高まりますが、利用者視点では選択肢が多すぎて混乱するリスクもあるためフィルタリングが重要です。
Odoo StudioでもReferenceはフィールドタイプとして存在し、コードを書かずにどのモデルを許可するかをUIから設定できます。開発者がいない現場では手早く使える反面、細かなロジックや動的選択肢の実装には向きません。
フィールドの動作仕組み
Referenceの保存・取得の仕組みを正しく理解しておくことは、開発や運用で不具合を防ぐうえで非常に重要です。
データベースでの保存形式
Many2oneが数値(外部キー)のみを保持するのと対照的に、Referenceは文字列(例: sale.order,15)をVARCHARカラムに保存します。このためDBの外部キー制約は働かず、多態的参照を可能にするための設計上の仕様です。
外部キーがないということは、参照先レコードが削除されてもDB側で自動的にその値が消えることはありません。参照先が消えた状態(オーファン)が残る可能性があるため、後述するクリーンアップ策やコード上での存在確認が必要です。
Pythonから参照先を扱う
PythonでReferenceフィールドを参照すると、Odooはその文字列を解決して実際のレコードオブジェクトを返します。Many2oneと同様にレコードのフィールドへアクセスできます。未設定の場合はFalseが返ります。
ticket = self.env['helpdesk.ticket'].browse(1)
doc = ticket.related_document
if doc:
print(doc._name) # e.g. 'sale.order'
print(doc.name) # e.g. 'S00042'
print(doc.id) # e.g. 15
内部は文字列ですが、ORMがレコードオブジェクトへ変換してくれる点は扱いやすいポイントです。ただし参照先が削除されているとFalseになる点は常に意識してください。
主要なフィールド属性
Referenceフィールドでよく使う設定項目は以下の通りです。
- selection: 選択可能なモデルのタプル一覧。メソッド名(文字列)を指定して動的に返すことも可能です。
- string: 画面に表示するラベル名。
- required: 必須にする場合はTrueにします(種類とレコードの両方を選ぶ必要があります)。
- readonly: UIからの変更を禁止し、プログラムでのみ設定する用途に使います。
- help: フィールドラベルに表示されるツールチップ。入力指示や注意書きを置くのに便利です。
- compute: Pythonメソッドで動的に値を算出して設定することも可能です。業務ルールに基づく自動参照付与に有効です。
検索・絞り込みの扱い方
値が文字列保存されるため、ドメイン検索ではそのフォーマットに合わせた条件指定が必要です。単純にIDでフィルタすると期待通りに動きません。
tickets = self.env['helpdesk.ticket'].search([
('related_document', '=', 'sale.order,15')
])
モデル種別だけで絞りたい場合はlikeを使って部分一致検索ができます。
tickets = self.env['helpdesk.ticket'].search([
('related_document', 'like', 'sale.order,')
])
レポートや算出フィールドでReferenceを扱うときは、Many2oneとは異なる検索や集計ロジックが必要になる点を設計段階で考慮してください。
実務での活用シーン
Referenceが特に役立つのは、同じ“関係”が状況に応じて異なるモデルを指す必要がある業務です。以下に現場で使える実例を示します。
1. サポートチケットから任意のドキュメントへ紐付け
カスタマーサポートでは請求書トラブル、配送問題、契約確認、製品不良など多様な背景を持つチケットが発生します。各種ドキュメントごとに別フィールドを作るより、チケットに1つのReferenceを持たせて必要なモデルを選んで紐付ける方が運用がシンプルになります。
2. CRMの活動が複数の起点に紐づく場合
営業活動はリード、見積、既存契約、サポートケースなど多様な起点から発生します。活動やメモにReferenceを持たせておけば、起点を柔軟に記録でき、どのドキュメントから来たのかを束ねて管理できます。
3. モジュール横断のメモや注釈管理
社内の共通的なノートや注釈を1モデルで管理したい場合、Referenceを使えば同じノートが顧客、プロジェクトタスク、製造指示、発注いずれにも紐づけられます。ドキュメントタイプごとにノートモデルを複数作らずに済むのが利点です。
4. 汎用承認ワークフロー
承認要求は発注書、経費精算、休暇申請、契約などさまざまなドキュメント対象になります。承認リクエストにReferenceを持たせれば、一本化した承認ロジックで複数のドキュメントを扱え、モデルの重複を避けられます。
5. 経費精算をプロジェクトや受注に紐付けるケース
経費がプロジェクトに紐づくこともあれば受注に紐づくこともある専門職やコンサル業界では、Referenceでproject.projectとsale.orderの両方を許可することで柔軟に経費の帰属を記録できます。
Referenceフィールドの作成・カスタマイズ方法
Referenceフィールドを追加する方法は大きく分けてStudioを使うノーコード手法と、Pythonで直接定義する開発手法があります。
Odoo Studioでの追加
Studioでは対象フォームを開いてフィールドパネルからReferenceを追加し、UIで選択肢となるモデルを指定できます。Studioで作成されたフィールドはx_プレフィックスのカスタムフィールドとして管理され、短時間で展開できるのが利点です。
急ぎのカスタマイズや非開発者向けの拡張には適しますが、選択肢を動的に切り替えたい場合や詳細なビジネスロジックがあるケースではStudioだけでは不足することがあります。
Pythonによる技術実装
本格的な拡張ではモデル定義内にReferenceを定義します。動的な選択肢を返すメソッドを使えば、インストール済みモジュールや設定に応じて候補を切り替えることが可能です。
from odoo import api, fields, models
class ApprovalRequest(models.Model):
_name = 'approval.request'
_description = 'Approval Request'
name = fields.Char(string='Request Name', required=True)
@api.model
def _get_document_types(self):
return [
('purchase.order', 'Purchase Order'),
('hr.expense.sheet', 'Expense Report'),
('hr.leave', 'Time Off Request'),
('sale.order', 'Sale Order'),
]
document_ref = fields.Reference(
selection='_get_document_types',
string='Document',
help='Select the document this approval relates to.',
)
selectionにメソッド名(文字列)を渡すことで返却ロジックに条件を入れられます。モジュールの有無で候補を増減したり、設定テーブルから取得したりと柔軟に実装できます。
XML-RPC経由での作成
リモートでフィールド定義を配備したい場合、XML-RPC APIを用いてReferenceフィールドをreference型で作成できます。選択肢は文字列として渡す点に注意が必要です。
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_related_document',
'field_description': 'Related Document',
'model_id': model_id,
'ttype': 'reference',
'selection': "[('sale.order', 'Sale Order'), ('purchase.order', 'Purchase Order')]",
'state': 'manual',
}]
)
API経由で作る場合、selectionはPythonで評価できる文字列として渡す必要があります。これはOdooがir.model.fieldsに保存する仕組みに合わせた扱いです。
導入時のベストプラクティス
Referenceを使う際に押さえておきたい設計上の指針を紹介します。
- 選択肢は短く、意味のある候補に絞ること。利用できる全モデルを無意識に入れるのではなく、実業務で使うドキュメント種別だけを掲載してください。長すぎるリストはユーザーの混乱を招きます。
- 常にリンク先が固定ならMany2oneを選ぶこと。Referenceは多態性が必要な場合に有効です。固定モデルならMany2oneの方が検索性能やレポート互換性で有利です。
- 算出フィールドや処理で参照先にアクセスする場合はnull(空)チェックを忘れないこと。未設定なら
Falseが返るため、無条件でアクセスすると例外になります。 - 参照切れ(オーファン)対策を自動化すること。DB側で整合性が担保されないため、定期ジョブや自動アクションで古い参照を検出・クリーンアップする仕組みを用意すると安全です。
- 選択肢のラベルは業務に即した表現にする。ユーザーが見るのはタプルの第二要素なので、技術名ではなく「顧客請求書」「受注」などわかりやすい名称を付けてください。
- 仕様書で明確に説明を書くこと。ReferenceはMany2oneと挙動が異なるため、なぜReferenceを採用したか、どのモデルに紐づくかを技術ドキュメントに残しておくと将来の継承時に役立ちます。
よくある落とし穴
初めてReferenceを扱うときによくあるミスをまとめます。
Many2oneと同じドメイン構文で扱ってしまう
ドメインで[('document_ref', '=', 15)]のようにIDだけで比較すると失敗します。Referenceは文字列sale.order,15を保持するため、完全一致で文字列を作る必要があります。
削除されたレコードの残骸を忘れること
参照先が削除されてもReferenceの文字列は残ります。その場合ORMで参照するとFalseが返るため、参照が常に有効である前提のコードは壊れます。存在確認と例外処理を必ず入れてください。
全モデルを返す動的選択肢を多用すること
ir.modelからすべてのインストール済みモデルを候補にするのは技術的には可能でもユーザーの観点ではほとんど有害です。数百の候補があると誤登録を招くため、用途に応じて厳選したリストに絞り込みましょう。
ネイティブのグルーピング期待はできない
Referenceは文字列保存のため、Many2oneのようにそのままグルーピングやピボット集計に使うことはできません。モデル名だけを別途抽出する算出フィールドを作るなど、集計用途に合わせた前処理が必要です。
StudioでReferenceとMany2oneを混同すること
両者ともレコードへのリンクに見えますが、Many2oneは作成時に固定した単一モデルしか参照できないのに対し、Referenceは入力時にモデルを選べます。用途を誤るとフィールドを作り直す必要が出るため要注意です。
まとめ
まとめると、ReferenceはMany2oneでは表現できない“状況に応じて変わる参照”を自然に扱える有用なツールです。Studioでのノーコード追加やPythonでの柔軟実装に対応しており、適切に使えばデータモデルをシンプルに保てます。
運用上留意すべきは、文字列ベースで保存されること、DBレベルで参照整合性が担保されないこと、検索やフィルタがIDベースではなく文字列として扱う必要があることです。これらを理解すれば予測可能に使いこなせます。
承認ワークフローの共通化やサポートチケットの汎用的紐付け、複数モジュールにまたがる注釈管理など、モデルを重複させずに柔軟な関連付けを実現したい場面ではReferenceが最適解になります。
Odoo導入の支援が必要ですか?
私たちDasoloは、企業の実際の業務フローに合わせたOdooの設計・カスタマイズ・最適化支援を行っています。フィールド設計からデータモデル構築、既存環境への機能追加まで技術的にサポートします。
Odooプロジェクトでフィールド設計、データアーキテクチャ、開発方針に関する相談があればぜひご連絡ください。状況に応じた最適なアプローチをご提案します。