関連フィールド(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:中間テーブルを介した双方向多対多関係