1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Odooモデルの設計パターンとテーブル構造

Last updated at Posted at 2025-06-30

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.partnercustom_field
似た構造の別モデルを作りたい _inherit + _name my.partner
親のフィールドを透過的に使いたい _inherits my.model
単に関連付けたいだけ Many2one my.refmodel
双方向に複数レコードを関連付けたい Many2many studentcourse、及びstudent_course_rel

Odooモデルの定義

_inherit(既存モデルの拡張)

コード例

class ResPartner(models.Model):
    _inherit = 'res.partner'

    custom_field = fields.Char(string="追加項目")

テーブル構造(res_partner)

id name email 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 email
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 email
3 Carol carol@example.com
  • my_model.nameres.partner.name を透過的に参照できる(実態はJOINで構成される)

実態はJOINで構成される
_inherits(委譲継承)には、次の特徴があり、my_modelにアクセスするとres_partnerとのJOINが常に発生します。

【特徴】

  • 透過的に親モデルのフィールドを使える
    my_model.nameres.partner.name にアクセス可能(ビューでも同様)
  • ビューやフォームで親モデルのフィールドが自動展開される
  • DB的には JOIN が必要(my_modelres_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 email
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への影響 フィールドを透過的に表示したいか?

本記事で、 「自分のユースケースに適したモデリングはどれか?」 を判断しやすくなれば、幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?