モデルの量がかなり多い、該当するモデルをどう探すかが重要になってくる
検索&コマンドパレットは “モデルのみ” 対象に絞ります。
100–200件規模で“最短でモデルに飛べる”設計を、実装しやすい形でまとめます。
コマンドパレット(モデル専用)
入力 → 候補
- 対象: モデル技術名(
account.move)、表示名(Journal Entry)、アプリ名/モジュール名(補助) - プレフィクスや疑似クエリは最小限:
app:account,module:sale,model:stock(任意)
- 結果アイテム(1行):
- 左: モデル技術名(太字)
- 下: 表示名(薄字)
- 右: バッジ(
MENU/FORM/M2O/O2M/M2M)※一覧と同じ判定でOK
操作
Enter→ モデル詳細ページへ遷移(既存の詳細画面)Alt+Enter→ 新規タブで詳細を開く↑/↓選択、Esc閉じる- 履歴: 直近アクセス上位5件を先頭にピン留め(学習なしでも体感が速い)
検索スコア(軽量・実用)
優先度の高い順に加点し、上位から表示:
- 前方一致(
account.で始まる等) - 完全一致(技術名 or 表示名)
- 部分一致(n-gram/fuzzyは任意)
- タイブレーク用ブースト(同点時)
MENUあり +1FORMあり +1refs_in高いほど微加点(親になりやすい)- 最近開いた/最近更新 +微加点
200件なら Postgres の
ILIKE+ 簡易前方一致で十分高速です。後から trigram に差し替え可能。
一覧との役割分担(モデルのみ)
- 一覧(中央テーブル): 絞り込み・俯瞰
- コマンドパレット: 直行・ジャンプ
必要列(一覧と共通化し、同じメタでパレットも描画)
model(技術名)display_name(表示名)app/module- バッジ:
has_menu,has_form,has_m2o,has_o2m,has_m2m refs_in/refs_outupdated_at
最小API設計(モデル専用)
検索API(パレット/一覧共通)
GET /search/models?q=move&app=account&module=account,account_payment&limit=20
レスポンス(例)
[
{
"model": "account.move",
"display_name": "Journal Entry",
"app": "Accounting",
"module": "account",
"badges": ["MENU","FORM","M2O","O2M"],
"refs": {"in": 12, "out": 7},
"updated_at": "2025-08-20T08:30:00Z",
"score": 8.7
}
]
一覧API(ページ初期表示)
GET /models?app=account&has_menu=true&sort=menu,refs_in,updated_at
取得ロジック(簡潔SQLの芯)
モデル索引(マテビュー推奨)
CREATE MATERIALIZED VIEW IF NOT EXISTS portal_model_index AS
WITH owners AS (
SELECT im.model, im.name AS display_name, imd.module AS module
FROM ir_model im
JOIN ir_model_data imd ON imd.model='ir.model' AND imd.res_id=im.id
),
apps AS (
SELECT name AS module, shortdesc AS app
FROM ir_module_module
WHERE state='installed'
),
views AS (
SELECT iv.model,
COUNT(*) FILTER (WHERE iv.type='form') AS form_views
FROM ir_ui_view iv
GROUP BY iv.model
),
menus AS (
SELECT aw.res_model AS model, COUNT(*) AS menu_cnt
FROM ir_ui_menu m
JOIN ir_act_window aw ON aw.id = split_part(m.action, ',', 2)::int
WHERE m.action LIKE 'ir.actions.act_window,%'
GROUP BY aw.res_model
),
rels_out AS (
SELECT m.model, COUNT(*) AS refs_out
FROM ir_model m JOIN ir_model_fields f ON f.model_id=m.id
WHERE f.relation IS NOT NULL GROUP BY m.model
),
rels_in AS (
SELECT f.relation AS model, COUNT(*) AS refs_in
FROM ir_model_fields f
WHERE f.relation IS NOT NULL GROUP BY f.relation
)
SELECT
o.model,
o.display_name,
COALESCE(a.app, o.module) AS app,
o.module,
(COALESCE(m.menu_cnt,0) > 0) AS has_menu,
(COALESCE(v.form_views,0) > 0) AS has_form,
EXISTS(SELECT 1 FROM ir_model_fields f JOIN ir_model m2 ON m2.id=f.model_id
WHERE m2.model=o.model AND f.ttype='many2one') AS has_m2o,
EXISTS(SELECT 1 FROM ir_model_fields f JOIN ir_model m2 ON m2.id=f.model_id
WHERE m2.model=o.model AND f.ttype='one2many') AS has_o2m,
EXISTS(SELECT 1 FROM ir_model_fields f JOIN ir_model m2 ON m2.id=f.model_id
WHERE m2.model=o.model AND f.ttype='many2many') AS has_m2m,
COALESCE(ri.refs_in,0) AS refs_in,
COALESCE(ro.refs_out,0) AS refs_out,
NOW()::timestamp AS updated_at -- 必要なら write_date 集計に差し替え
FROM owners o
LEFT JOIN apps a ON a.module = o.module
LEFT JOIN menus m ON m.model = o.model
LEFT JOIN views v ON v.model = o.model
LEFT JOIN rels_in ri ON ri.model = o.model
LEFT JOIN rels_out ro ON ro.model = o.model;
SELECT *,
(CASE WHEN model ILIKE $q||'%' THEN 3
WHEN display_name ILIKE $q||'%' THEN 2
WHEN model ILIKE '%'||$q||'%' THEN 1
WHEN display_name ILIKE '%'||$q||'%' THEN 1
ELSE 0 END)
+ (CASE WHEN has_menu THEN 0.5 ELSE 0 END)
+ (CASE WHEN has_form THEN 0.5 ELSE 0 END)
+ LEAST(refs_in/10.0, 1.0)*0.5 AS score
FROM portal_model_index
WHERE (model ILIKE '%'||$q||'%' OR display_name ILIKE '%'||$q||'%')
AND ($app IS NULL OR app = $app)
AND ($modules IS NULL OR module = ANY($modules))
ORDER BY score DESC
LIMIT 20;
小さなUXルール
- 候補の最大表示は7件(速く決めやすい)
- 最近アクセス3件は常に上に(薄い区切り線で)
- 候補の右端に 「↗」 アイコン(新タブで開くヒント)
- ヒット件数が0なら即「フィルタ解除」ショートカットを提案
まとめ
- 検索&コマンドパレットはモデルのみを対象に最小設計
- 前方一致と小さなブーストで“体感最速”を実現
- 一覧と同じ軽量メタを使いまわし、実装と運用を簡素化
「タイブレーク用ブースト」というのは、検索結果でスコアが同点になったときに、より“重要そうなモデル”を上に出すための追加評価のことです。
もう少し具体的に、なぜ必要でどう使うかを解説します。
1. 何を解決するためのブーストか
- モデルは 100~200件 程度 → 検索結果の重複は多い
- 例えば
moveで検索した場合:account.movestock.movehr.expense.move
- 単純な部分一致だとどれを上に出すべきか判断できない
→ そこで、重要度に応じたスコア加点を入れて、モデルの並び順を最適化します。
2. ブースト項目の意味
① MENUあり +1
- 意味:Odoo画面のメニューから直接アクセスできるモデルは“ユーザー操作で重要”。
- 理由:
ir_ui_menu→ir_act_window.res_modelに存在するモデル- 例:
account.move(仕訳入力画面がある)→ +1点account.partial.reconcile(内部処理用モデル)→ +0点
- 効果:
- “画面で使うモデル”を上に出すので、開発者も探しやすい
② FORMあり +1
- 意味:formビューがあるモデルは“UIの中心”。
- 理由:
ir_ui_viewでtype='form'があるモデルは、編集・表示画面がある- 例:
sale.order→ フォームビューがある → +1点sale.order.line→ 明細モデルだがフォームビューなし → +0点
- 効果:
- “操作対象”のモデルを優先的に出せる
③ refs_in 高いほど微加点(親になりやすい)
- 意味:他のモデルから参照されている数(Many2oneの“受け側”)が多いモデルは“親モデル”。
- 理由:
ir_model_fields.relationで 何回参照されているかをカウント- 例:
res.partner→ 20以上のモデルから参照 → +0.5点res.country.state→ 3件だけ参照 → +0.1点
- 効果:
- “基幹モデル”を優先して表示できる
- 例:
res.partner、account.move、product.productなど
④ 最近開いた / 最近更新 +微加点
- 意味:ユーザーがよく使っている or 最近変わったモデルを上に出す。
- 2種類ある:
- 最近開いた
- ポータルで開いたモデルをローカルにキャッシュ
- ユーザーごとによく使うモデルを上位表示
- 最近更新
ir_model.write_date、ir_model_fields.write_dateのMAXを利用- 最近ビューやフィールドが更新されたモデルを上に出す
- 最近開いた
- 効果:
- “今まさに触っているモデル”がすぐ出てくる
3. 実際のスコア例
例:ユーザーが move で検索した場合
| モデル | 部分一致 | MENU | FORM | refs_in | 最近開いた | 合計スコア |
|---|---|---|---|---|---|---|
| account.move | 3 | +1 | +1 | +0.4 | +0.3 | 5.7 |
| stock.move | 3 | +1 | +1 | +0.2 | +0.0 | 5.2 |
| hr.expense.move | 3 | 0 | 0 | +0.1 | +0.0 | 3.1 |
→ 検索キーワードでは3モデルとも同点だけど、実務上もっとも重要な account.move が1位に来ます。
SELECT *,
-- 検索マッチ度(3点満点)
(CASE WHEN model ILIKE $q||'%' THEN 3
WHEN display_name ILIKE $q||'%' THEN 2
WHEN model ILIKE '%'||$q||'%' THEN 1
WHEN display_name ILIKE '%'||$q||'%' THEN 1
ELSE 0 END)
-- MENU + FORMブースト
+ (CASE WHEN has_menu THEN 1 ELSE 0 END)
+ (CASE WHEN has_form THEN 1 ELSE 0 END)
-- refs_in比率(最大0.5点)
+ LEAST(refs_in / 20.0, 0.5)
-- 最近開いたモデル(ユーザー単位のキャッシュ)
+ COALESCE(recent_score, 0.3)
AS score
FROM portal_model_index
WHERE model ILIKE '%'||$q||'%'
OR display_name ILIKE '%'||$q||'%'
ORDER BY score DESC
LIMIT 20;
5. まとめ
- ブースト=同点時に“より重要そう”なモデルを上げる仕組み
- 優先度の判断基準:
- MENUあり → UIで使うモデルを優先
- FORMあり → 操作対象モデルを優先
- refs_in多い → 基幹モデルを優先
- 最近開いた/更新 → “今触る可能性が高い”モデルを優先
- 100~200件規模なら、このブーストを足すと**「欲しいモデルが上に来る」**体験になる
コメントを残す