Odoo カスタムフィールドとORM操作の実践ガイド

関連フィールド(Related Fields)の活用

関連フィールドは、他のモデルや同一モデル内の既存フィールドを参照して値を導出するための仕組みです。データベースに物理的に保存されず、アクセス時に動的に解決されます。

検索やソートを可能にするには store=True を指定し、値を永続化できます。ただし、ストア設定は依存先フィールドの変更を自動で追跡し、再計算をトリガーするため、@api.depends の明示的な宣言は不要です。

from odoo import models, fields

class ProjectTask(models.Model):
    _name = 'project.task'
    _description = 'タスク'

    project_name = fields.Char(related='project_id.name', string='プロジェクト名', store=True)
    project_id = fields.Many2one('project.project', string='所属プロジェクト')

計算フィールド(Computed Fields)の設計

計算フィールドは、1つ以上の入力フィールドに基づき、カスタムロジックで値を生成します。デフォルトでは読み取り専用ですが、inverse パラメータで逆変換ロジックを定義することで編集可能にできます。

依存関係は @api.depends で明示し、値の変更タイミングを正確に制御します。検索対応には search メソッドの実装が必須です。

from odoo import models, fields, api
from odoo.exceptions import ValidationError

class SaleOrderLine(models.Model):
    _name = 'sale.order.line'
    _description = '販売明細'

    unit_price = fields.Float(string='単価')
    quantity = fields.Float(string='数量')
    total_amount = fields.Float(
        compute='_compute_total',
        inverse='_inverse_total',
        search='_search_by_total',
        store=True,
        string='合計金額'
    )

    @api.depends('unit_price', 'quantity')
    def _compute_total(self):
        for line in self:
            line.total_amount = line.unit_price * line.quantity or 0.0

    def _inverse_total(self):
        for line in self:
            if line.total_amount and line.quantity:
                line.unit_price = line.total_amount / line.quantity

    def _search_by_total(self, operator, value):
        # 単純な等価検索のみサポート(例:total_amount = 1000)
        if operator == '=':
            return [('unit_price', '=', value)]
        raise NotImplementedError('この演算子はtotal_amountの検索でサポートされていません')

主要なAPIデコレータの役割

  • @api.model:モデルレベルの静的メソッド。レコードセットではなく、モデルクラス自体を対象とします。
  • @api.depends('f1', 'f2'):計算フィールドの再評価条件を定義。依存フィールドの変更時に自動実行されます。
  • @api.onchange('f1'):UI上での即時反映用。フォーム編集中に値変更を検知し、他のフィールドを動的に更新します。
  • @api.constrains('f1', 'f2'):保存時の整合性チェック。不正な値に対して ValidationError を送出できます。

ORMコアメソッドの使い分け

メソッド 用途 備考
create(vals) 新規レコード作成 複数レコードの一括作成も可能(create([{}, {}])
write(vals) 既存レコードの更新 レコードセット全体に一括適用される
unlink() レコード削除 削除前に unlink メソッドがオーバーライド可能
search(domain) 条件検索(レコードセット返却) limit=1 で単一レコード取得も可
search_read(domain, fields) 検索+読み取り(辞書リスト返却) パフォーマンス重視の簡易取得に適す
browse(ids) IDリストからレコードセット構築 DBアクセスなしで既知IDからオブジェクトを取得

環境とコンテキストの制御

self.env は現在の実行環境へのゲートウェイです。

  • env.cr:データベース接続カーソル(SQL直接実行やトランザクション制御に使用)
  • env.user:ログイン中のユーザーのレコード
  • env.context:不変の辞書型コンテキスト。ビュー間で値を引き渡すのに利用
  • env.sudo():セキュリティルールを無視して実行(例:self.env['res.partner'].sudo().search([])

ドメイン式の構文パターン

検索条件はタプルのリストで表現されます:

  • [('state', '=', 'done')] — 等価比較
  • [('name', 'ilike', 'odoo')] — 大文字小文字を無視した部分一致
  • [('id', 'in', [1, 2, 3])] — IDリスト内検索
  • [('&', ('active', '=', True), ('amount', '>=', 1000))] — AND条件(& はエスケープ済み)

レコードセット操作の基本

OdooのレコードセットはPythonの集合操作を自然にサポートします:

# 合併(OR)
all_records = records_a | records_b

# 共通要素(AND)
common = records_a & records_b

# 差分(Aのみ)
only_in_a = records_a - records_b

# フィルタリング
high_value = records.filtered(lambda r: r.amount > 5000)

# マッピング(IDリスト取得)
ids_list = records.mapped('id')

# ソート(名前昇順)
sorted_by_name = records.sorted('name')

リレーションフィールドの選択基準

  • Many2one:一方のレコードが他方の単一レコードを参照(外部キー)
  • One2many:親モデル側に定義。inverse_name で関連フィールドを指定
  • Many2many:中間テーブルを介した双方向多対多関係

タグ: odoo Python ORM computed-field related-field

6月6日 20:49 投稿