本格拡張はアドオンで — Odoo の拡張開発の基本
Odoo Community で機能を足す本筋は、アドオンを作ることです。マニフェスト、_inherit、ビューの継承、super()、データファイル、upgrade script を、Odoo 19 で実際に動かした例で説明します。
目次
目次
このページには目次がありません

Odoo Community で「標準の動きを変えたい」「日本の業務の画面を足したい」となったとき、本筋はアドオン(モジュール)を作ることです。画面の上で項目を足す道具(Studio)は Enterprise 側にあり、Community には入っていません。Community では、足したい物をコードでアドオンにまとめます。
この記事では、アドオンの作りを、Odoo 19 Community に仮のアドオンを入れて確かめた結果(11 の実験・28 項目)から抜いて説明します。
Odoo そのものがアドオンの集まり
開発者モード(URL の末尾に ?debug=1)で「アプリ」を開くと、販売は sale_management、在庫は stock、製造は mrp という技術名で並んでいます。どれも同じ形のアドオンです。「アップグレード」と出ている物(Studio の web_studio など)は Enterprise 側の物で、Community では入れられません。
開発者モードの「アプリ」。カードの下の文字がアドオンの技術名です。
日本の勘定科目と税の雛形 l10n_jp も、アドオンの1つです。作成者は Odoo 社ではなく別の会社で、ライセンスと版の番号がアドオンごとに付いています。自社で作るアドオンも、入れるとこの一覧に同じ形で並びます。
「日本 - 会計」(l10n_jp)を開いたところ。版の番号は upgrade script と結びつきます(後で説明します)。
アドオンの中身
1つのアドオンは、1つのフォルダです。実験で使った最小の形はこうでした。
labA/
__manifest__.py … 名前・版・依存・読み込むファイル
__init__.py … Python のファイルを読み込む
models.py … モデル(データの形と処理)
security/ir.model.access.csv … 誰が読めて書けるか
views.xml … 画面
migrations/19.0.1.0.1/post-migrate.py … 版を上げたときに1回だけ走る処理
__manifest__.py は辞書を1つ書くだけです。下は、上の labA に処理を重ねる2つ目のアドオン labB(フォルダの形は同じ)のマニフェストです。
{
'name': 'labB',
'version': '19.0.1.0.0',
'depends': ['labA'], # 先に入っている必要があるアドオン
'data': ['security/ir.model.access.csv', 'views.xml'],
'license': 'LGPL-3',
'installable': True,
}
入れるのは odoo -i labB、更新は odoo -u labB です。depends に書いたアドオンは先に入り、処理を重ねる順番もこれで決まります。
既存のモデルに項目を足す:_inherit
既存のモデルを広げるときは、_inherit で「どのモデルに足すか」を指します。
from odoo import models, fields
class Thing(models.Model):
_inherit = 'lab.thing'
b_extra = fields.Char()
これで元のテーブルに b_extra の列が足されます。元のアドオンのファイルには1行も触れていません。
_inherit を書き忘れて、同じ _name だけを書くと危険です。実験では、別のアドオンが同じモデル名を _inherit なしで定義したところ、エラーも警告も出ずにインストールが通り、元の項目(name・note)が画面と処理から見えなくなりました。DB の列は残りますが、気づく手がかりがありません。他のアドオンと同じ _name を持つクラスが無いことを、レビューや自動の検査で数えておくのが確実です。
処理を差し込む:super()
保存や確定のような処理を変えるときは、同じ名前のメソッドを書いて super() を呼びます。
class Thing(models.Model):
_inherit = 'lab.thing'
def chain(self):
return super().chain() + ['B']
元が ['A'] を返すメソッドに、B と C の2つのアドオンが同じように重ねると、結果は ['A', 'B', 'C'] になりました。両方が効きます。C を B の depends に入れると ['A', 'C', 'B'] になり、順番を決められます。
逆に、super() を呼ばずに上書きするアドオン D を後から入れると、結果は ['D'] だけになりました。先に入っていた B と C の処理が、黙って消えたということです。その後に super() を呼ぶ E を入れても ['D', 'E'] で、B と C は戻りませんでした。止めたい処理があるときは、super() を外すのではなく、設定のスイッチを用意してそれを見る形にします。
もう1つ確かめたのは、元のアドオンの内部から呼ばれる処理です。モデルのメソッドは内部からの呼び出しでも差し替わり、モジュール直下の関数は差し替わりませんでした。業務の処理をモデルのメソッドに細かく切っておくと、それがそのまま後から差し込める場所になります。
画面に項目を足す:ビューの継承と XPath
画面も、元の XML を書き換えずに「どこに何を足すか」だけを書きます。
<record id="v_c" model="ir.ui.view">
<field name="name">v_c</field>
<field name="model">lab.thing</field>
<field name="inherit_id" ref="labA.view_thing"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='note']" position="after">
<field name="display_name"/>
</xpath>
</field>
</record>
inherit_id で元の画面を指し、xpath で場所を探し、position で前後・中・属性の変更を選びます。
標準の画面もこの形でできています。製品のフォームには、在庫のアドオンが「手持在庫」「再オーダールール」のボタンや「在庫管理」のタブを、製造のアドオンが「部品表」のボタンを、販売のアドオンが「販売」のタブを足しています。
製品のフォーム。上のボタンと「販売」「在庫管理」のタブは、それぞれ別のアドオンが継承で足した物です(画面は見本のデータです)
開発者モードでビューを開き「継承ビュー」の欄を見ると、製品フォームの基本の画面を直接継承している画面が 8 つ並んでいました(会計・在庫・経費・購買・販売ほか)。
製品フォームの基本の画面を継承している画面の一覧。外部 ID の先頭がアドオンの技術名です。
position="replace" は避けます。実験では、項目を replace で消すアドオン B と、その項目の後ろに足すアドオン C を用意しました。B→C の順に入れると C のインストールが止まり、C→B の順なら両方入りました。入れる順番で結果が変わるので、消したいときは attributes で非表示にする方が安全です。
データファイルと noupdate
初期のデータ(区分の値・既定の設定など)は XML で配ります。ここで気をつけたいのは、現場で変えた値が、更新で元に戻ることです。
<record id="s_upd" model="lab.thing"><field name="note">5</field></record>
<data noupdate="1">
<record id="s_keep" model="lab.thing"><field name="note">5</field></record>
</data>
両方の値を現場で 5 から 7 に変えて -u すると、noupdate の無い方は 5 に戻り、ある方は 7 のままでした。ただし noupdate="1" の値は、後で XML を 9 に直しても入りません。現場で変える値は noupdate="1" で配り、配った後に直したいときは次の upgrade script で1回だけ書き換えます。
版を上げるときの upgrade script
既存のデータを直す処理は、migrations/<版>/post-migrate.py に書きます。
def migrate(cr, version):
cr.execute("insert into lab_log(name) values ('m1')")
実験で分かったのは次の4つです。
| やったこと | 結果 |
|---|---|
マニフェストの版を上げて -u | 1回だけ走る |
同じ版のまま、もう一度 -u | 走らない |
| script を足したが、版を上げ忘れた | 走らない |
| 一度走った script を、新しい版のフォルダへ移した | もう一度走り、処理が二重になった |
版の番号を必ず上げること、走らせた script は動かさないこと。この2つを守れば、何度更新しても同じ結果になります。
外すときに消えるもの
_inherit で項目を足したアドオンをアンインストールすると、足した列はデータごと消えました(行そのものは残ります)。外すかもしれない機能の項目に大事なデータを持たせない、外す前にバックアップを取る、の2つを決めておきます。
まとめ
| 道具 | 使い方 | 避けること |
|---|---|---|
_inherit | 既存のモデルに項目・処理を足す | _inherit なしで同じ _name を書く |
super() | 元の処理の前後に差し込む | super() を呼ばずに上書きする |
| ビューの継承 | inherit_id と XPath で場所を指して足す | position="replace" で消す |
| データファイル | 初期の値を配る | 現場で変える値を noupdate なしで配る |
| upgrade script | 版を上げて既存のデータを1回だけ直す | 版の上げ忘れ・走らせた script の移動 |
Odoo のアドオンは、元のコードに触れずに大きく広げられます。その分、他のアドオンの処理を消す・権限を緩める、といった変更も同じ手軽さでできてしまいます。当社が日本の業務向けに足すアドオンでは、上の「避けること」を決まりにして、レビューと自動の検査で数えながら作ります。決まりの全体と、Odoo 19 で測った結果は「アップデートしても壊れない — Odoo の拡張の作り方」にまとめています。



