Odoo ORMの特徴
OdooのORM(Object-Relational Mapping)は、他の一般的なORMと比べていくつかユニークな特徴があります。特にERP向けに最適化されているため、業務ロジックとの統合性が非常に高いのがポイントです。
Odoo ORM API
「業務アプリケーションを素早く構築・拡張する」ことに特化しています。
Odoo ORMの主な特徴と他ORMとの違い
特徴 | Odoo ORM | 他の一般的なORM(例:Django ORM, SQLAlchemy) |
---|---|---|
目的 | ERPアプリケーションに特化 | 汎用的なWeb/アプリ開発向け |
モデル定義 |
models.Model を継承し、ビジネスロジックと密接に連携 |
データ構造中心で、業務ロジックは別途実装 |
リレーション管理 | Many2one, One2many, Many2manyが直感的に扱える | 同様の機能はあるが、Odooほど統合されていない |
ビューとの連携 | モデル定義がUI(フォーム、リスト)に自動反映される | UIは別途定義が必要 |
セキュリティとアクセス制御 | アクセスルールやレコードルールがORMレベルで統合 | 通常はアプリケーション側で制御 |
nullの扱い | nullをFalseや空のrecordsetとして扱うことが多い | nullはそのままNoneやnullとして返る |
拡張性 | モジュール単位での継承・オーバーライドが容易 | 継承は可能だが、Odooほど柔軟ではない場合が多い |
Odoo独自の便利な機能
-
@api.depends
,@api.onchange
などのデコレータで、依存関係やUIの動的変化を簡単に記述 -
search
,browse
,write
,unlink
などのメソッドが統一されたインターフェースで提供 - モデルの継承(_inherit)等により、既存機能の拡張が非常に簡単
→ コードの設計がそのままテーブル構造に反映される
Odooモデルの設計パターン比較
コードの設計がそのままテーブル構造に反映されるのがOdoo ORMの魅力でもあり、注意点でもあります。
プロジェクトで「どの構造を選ぶべきか」迷う場面が多々あるため、代表的な5つのパターンを整理し、比較できるようにしました。
パターン | テーブル数 | 親のフィールドの扱い | JOINの必要性 | データの独立性 | 主な用途 |
---|---|---|---|---|---|
_inherit のみ |
1(既存テーブル) | 同じテーブルに追加 | ❌ 不要 | 同一モデル | 既存モデルの拡張 |
_inherit + _name
|
2(新テーブル) | コピー(独立) | ❌ 不要 | 独立モデル | 似た構造の別モデル |
_inherits (委譲) |
2(新+親テーブル) | 透過的に使用 | ✅ 必要 | 部分依存 | 親モデルの機能を再利用 |
Many2one(外部参照) | 2(新+親テーブル) | 間接参照 | ✅ 必要なときだけ | 疎結合 | 関連付けのみ |
Many2many(多対多) | 3(中間テーブル含む) | 双方向リンク | ✅ 必須 | 疎結合 | タグ・カテゴリ・複数選択 |
例:テーブル構造
選び方のヒント
目的 | 推奨パターン | 例 |
---|---|---|
既存モデルに機能を追加したい | _inherit |
res.partner の custom_field
|
似た構造の別モデルを作りたい |
_inherit + _name
|
my.partner |
親のフィールドを透過的に使いたい | _inherits |
my.model |
単に関連付けたいだけ | Many2one | my.refmodel |
双方向に複数レコードを関連付けたい | Many2many |
student とcourse 、及びstudent_course_rel
|
Odooモデルの定義
_inherit
(既存モデルの拡張)
コード例
class ResPartner(models.Model):
_inherit = 'res.partner'
custom_field = fields.Char(string="追加項目")
テーブル構造(res_partner)
id | name | custom_field | |
---|---|---|---|
1 | Alice | alice@example.com | VIP |
- 既存の
res_partner
テーブルにcustom_field
が追加される
_inherit
+ _name
(別モデルとして再利用)
コード例
class MyPartner(models.Model):
_name = 'my.partner'
_inherit = 'res.partner'
テーブル構造(my_partner)
id | name | |
---|---|---|
1 | Bob | bob@example.com |
-
res.partner
の構造を引き継ぎつつ、独立したテーブルとして存在する
_inherits
(委譲継承)
コード例
class MyModel(models.Model):
_name = 'my.model'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True)
extra_info = fields.Char()
テーブル構造
my_model
id | partner_id | extra_info |
---|---|---|
1 | 3 | Sensor A |
res_partner
id | name | |
---|---|---|
3 | Carol | carol@example.com |
-
my_model.name
でres.partner.name
を透過的に参照できる(実態はJOINで構成される)
実態はJOINで構成される
_inherits
(委譲継承)には、次の特徴があり、my_model
にアクセスするとres_partner
とのJOINが常に発生します。
【特徴】
- 透過的に親モデルのフィールドを使える
→my_model.name
でres.partner.name
にアクセス可能(ビューでも同様) - ビューやフォームで親モデルのフィールドが自動展開される
- DB的には JOIN が必要(
my_model
とres_partner
) - 削除時の連動制御が可能(
ondelete='cascade'
など)
Many2one(外部参照)
コード例
class MyModel(models.Model):
_name = 'my.model'
partner_id = fields.Many2one('res.partner')
extra_info = fields.Char()
テーブル構造
my_model
id | partner_id | extra_info |
---|---|---|
1 | 2 | Note A |
res_partner
id | name | |
---|---|---|
2 | Dave | dave@example.com |
-
partner_id.name
のように階層的なアクセスができる(JOINは明示的)
JOINは明示的
Many2oneで単に外部参照している場合は、JOINは必要なときだけ行われます:
record.partner_id.name # このときだけJOINが発生
一方、_inherits
を使うと、常にJOINが発生します。なぜなら、親モデルのフィールドが「自分のもの」として振る舞うからです。
Many2one(外部参照)とOne2many(逆外部参照)
注文モデル(Order
)と注文明細モデル(OrderLine
)を定義する際、Many2one(外部参照) を使って、複数の注文明細を1つの注文に関連付けることができます。
Many2one(外部参照) に加え、注文モデル(Order
)から注文明細モデル(OrderLine
)を参照可能にするOne2many(逆外部参照) を定義すると、フォームビューで注文に対する注文明細を簡単にリスト表示できます(フレームワークに標準装備されている)。
また、注文明細の合計金額を計算することもできます(_compute_total_amount
メソッド)。
class Order(models.Model):
_name = 'shop.order'
name = fields.Char(string="注文番号")
line_ids = fields.One2many('shop.order.line', 'order_id', string="注文明細")
total_amount = fields.Float(string="合計金額", compute='_compute_total_amount', store=True)
@api.depends('line_ids.quantity', 'line_ids.price')
def _compute_total_amount(self):
for order in self:
order.total_amount = sum(line.quantity * line.price for line in order.line_ids)
class OrderLine(models.Model):
_name = 'shop.order.line'
order_id = fields.Many2one('shop.order', required=True, ondelete='cascade')
product = fields.Char(string="商品")
quantity = fields.Integer(string="数量")
price = fields.Float(string="単価")
One2many(逆外部参照) は、仮想的な外部参照のため、Many2one(外部参照) の定義が必要です。
Many2many(多対多)
コード例
class Student(models.Model):
_name = 'student'
name = fields.Char()
course_ids = fields.Many2many('course', string='Courses')
class Course(models.Model):
_name = 'course'
name = fields.Char()
テーブル構造
student
id | name |
---|---|
1 | Emma |
2 | Liam |
course
id | name |
---|---|
10 | Math |
11 | Science |
12 | History |
student_course_rel(中間テーブル)
student_id | course_id |
---|---|
1 | 10 |
1 | 11 |
2 | 11 |
2 | 12 |
- 中間テーブルで多対多の関係を管理(自動生成)
中間テーブル名とそのカラム名
fields.Many2many()
の定義により、中間テーブル(student_course_rel
)が自動生成されます。命名規約にしたがって中間テーブル名やそのカラム名が生成されますが、明示的に中間テーブル名(relation
)やカラム名(column1
, column2
)を指定することも可能です。
fields.Many2many(
comodel_name, # 関連先モデル
relation, # 中間テーブル名
column1, # 自モデル側のカラム名
column2, # 関連先モデル側のカラム名
string=..., # 表示名
)
まとめ:Odoo ORMを設計するときの視点
OdooのORMは、ERP構築に特化した高度な抽象化と柔軟な拡張性を兼ね備えています。特に継承と参照構造がそのままテーブル構造に影響する点は、設計者にとって非常に重要です。
-
既存モデルのちょい足しには
_inherit
-
構造の再利用+独立性が欲しいときは
_inherit + _name
-
親の機能を透過的に使いたいなら
_inherits
(JOINのコストに注意) -
軽量な関連を作るなら
Many2one
-
双方向に複数関連付けたいなら
Many2many
(中間テーブルを意識)
設計時には、次のような観点で選択を見直すと、長期的な保守性・拡張性に大きく寄与します。
設計で意識すること | 例 |
---|---|
DBアクセスの頻度 | JOINコストが許容できるか? |
関連の方向性 | 双方向か?片方向か? |
データの独立性 | 外部モデルと分離して扱うべきか? |
UIへの影響 | フィールドを透過的に表示したいか? |
本記事で、 「自分のユースケースに適したモデリングはどれか?」 を判断しやすくなれば、幸いです。