/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } Odoo メタデータ取得 – Raqqa

Odoo メタデータ取得

WITH params AS (
  SELECT 'account.move'::text AS model_name
),
-- モデル基本
mdl AS (
  SELECT im.id, im.model, im.name AS display_name, im.transient, im.write_date
  FROM ir_model im
  JOIN params p ON im.model = p.model_name
),
-- 所有モジュールとアプリ名
owner AS (
  SELECT m.model, imd.module AS module
  FROM mdl m
  JOIN ir_model_data imd ON imd.model='ir.model' AND imd.res_id=m.id
),
app AS (
  SELECT
    mod.name AS module,
    COALESCE(
      -- jsonb なら翻訳値(例: en_US)を取り出す
      (CASE WHEN pg_typeof(mod.shortdesc) = 'jsonb'::regtype
            THEN mod.shortdesc->>'en_US' END),
      -- それでも無ければ jsonb/text を文字列化
      (CASE WHEN pg_typeof(mod.shortdesc) = 'jsonb'::regtype
            THEN mod.shortdesc::text END),
      mod.shortdesc::text,   -- text の場合はこちら
      mod.name               -- 最後のフォールバック
    ) AS app_name,
    mod.application
  FROM ir_module_module mod
),
-- フィールド種別カウント
f AS (
  SELECT
    m.model,
    COUNT(*)                                        AS field_count,
    COUNT(*) FILTER (WHERE f.ttype='many2one')      AS m2o_count,
    COUNT(*) FILTER (WHERE f.ttype='one2many')      AS o2m_count,
    COUNT(*) FILTER (WHERE f.ttype='many2many')     AS m2m_count
  FROM mdl m
  JOIN ir_model_fields f ON f.model_id = m.id
  GROUP BY m.model
),
-- 参照される数(IN)/参照する数(OUT)
refs_in AS (
  SELECT f.relation AS model, COUNT(*) AS refs_in
  FROM ir_model_fields f
  JOIN params p ON f.relation = p.model_name
  GROUP BY f.relation
),
refs_out AS (
  SELECT m.model, COUNT(*) AS refs_out
  FROM mdl m
  JOIN ir_model_fields f ON f.model_id = m.id
  WHERE f.relation IS NOT NULL
  GROUP BY m.model
),
-- ビュー情報
views AS (
  SELECT
    iv.model,
    COUNT(*) FILTER (WHERE iv.type='form')   AS form_views,
    COUNT(*) FILTER (WHERE iv.type='tree')   AS tree_views,
    COUNT(*) FILTER (WHERE iv.type='kanban') AS kanban_views,
    COUNT(*) FILTER (WHERE iv.type='search') AS search_views,
    COUNT(*)                                  AS total_views,
    MAX(iv.write_date)                        AS views_write_date
  FROM ir_ui_view iv
  JOIN params p ON iv.model = p.model_name
  GROUP BY iv.model
),
-- act_window数(画面アクション数)
aw_cnt AS (
  SELECT aw.res_model AS model, COUNT(*) AS act_window_cnt
  FROM ir_act_window aw
  JOIN params p ON aw.res_model = p.model_name
  GROUP BY aw.res_model
),
-- メニュー露出(最初の3件のメニュー名をサンプル表示) ※ROW_NUMBER+FILTER 版
menus_ranked AS (
  SELECT
    aw.res_model,
    m.id,
    (m.name::text) AS menu_name,
    ROW_NUMBER() OVER (PARTITION BY aw.res_model ORDER BY m.id) AS rn
  FROM ir_ui_menu m
  JOIN ir_act_window aw
    ON m.action LIKE 'ir.actions.act_window,%'
   AND aw.id = split_part(m.action, ',', 2)::int
  JOIN params p ON aw.res_model = p.model_name
),
menus AS (
  SELECT
    mr.res_model AS model,
    COUNT(*) AS menu_cnt,
    ARRAY_AGG(mr.menu_name ORDER BY mr.id) FILTER (WHERE mr.rn <= 3) AS sample_menus
  FROM menus_ranked mr
  GROUP BY mr.res_model
),
-- 概算件数(pg_class.reltuples)
size AS (
  SELECT m.model, c.reltuples::bigint AS approx_rows
  FROM mdl m
  LEFT JOIN pg_class c ON c.relname = replace(m.model, '.', '_')
),
-- 最終更新のざっくり(モデル/フィールド/ビューのMAX)
latest AS (
  SELECT
    p.model_name AS model,
    GREATEST(
      COALESCE(mdl.write_date, '1970-01-01'::timestamp),
      COALESCE((SELECT MAX(f2.write_date) FROM ir_model_fields f2 JOIN mdl m2 ON f2.model_id=m2.id), '1970-01-01'::timestamp),
      COALESCE((SELECT MAX(iv2.write_date) FROM ir_ui_view iv2 JOIN params pp ON iv2.model=pp.model_name), '1970-01-01'::timestamp)
    ) AS last_changed_at
  FROM params p
  LEFT JOIN mdl ON mdl.model = p.model_name
)
SELECT
  m.model,
  m.display_name,
  o.module,
  a.app_name,
  a.application AS is_application,
  COALESCE(sz.approx_rows,0)         AS approx_rows,
  COALESCE(fi.field_count,0)         AS field_count,
  COALESCE(fi.m2o_count,0)           AS m2o_count,
  COALESCE(fi.o2m_count,0)           AS o2m_count,
  COALESCE(fi.m2m_count,0)           AS m2m_count,
  COALESCE(ri.refs_in,0)             AS refs_in,
  COALESCE(ro.refs_out,0)            AS refs_out,
  COALESCE(v.form_views,0)           AS form_views,
  COALESCE(v.tree_views,0)           AS tree_views,
  COALESCE(v.kanban_views,0)         AS kanban_views,
  COALESCE(v.search_views,0)         AS search_views,
  COALESCE(v.total_views,0)          AS total_views,
  COALESCE(aw.act_window_cnt,0)      AS act_window_cnt,
  COALESCE(mx.menu_cnt,0)            AS menu_cnt,
  mx.sample_menus,
  lt.last_changed_at
FROM mdl m
LEFT JOIN owner  o  ON o.model = m.model
LEFT JOIN app    a  ON a.module = o.module
LEFT JOIN size   sz ON sz.model = m.model
LEFT JOIN f      fi ON fi.model = m.model
LEFT JOIN refs_in ri ON ri.model = m.model
LEFT JOIN refs_out ro ON ro.model = m.model
LEFT JOIN views  v  ON v.model = m.model
LEFT JOIN aw_cnt aw ON aw.model = m.model
LEFT JOIN menus  mx ON mx.model = m.model
LEFT JOIN latest lt ON lt.model = m.model;
モデル単位で“使われやすさ/カスタマイズ候補度”をスコア化する、pgAdminでそのまま動くSQLを用意しました。
モジュールやapp_nameは一切見ません。各モデルだけを評価します。

評価指標(列に全部出ます)

form_views(フォーム画面の有無/数)

menu_cnt(メニューから開ける回数)

act_window_cnt(アクション数)

has_state(state selection フィールド)

has_chatter(Chatter: message_ids)

refs_in / refs_out(参照される/参照するフィールド数)

approx_rows(概算レコード数)

last_changed_at(最終更新のざっくり)

score(下の重みで合計)

デフォルト重み:FORM=+2, MENU=+2, STATE=+1, CHATTER=+1, REFS_IN=最大+1, VIEWS=最大+0.5
変更したければ weights CTE を編集してください。
WITH
-- 重み(必要なら調整)
weights AS (
  SELECT
    2.0  AS w_form,
    2.0  AS w_menu,
    1.0  AS w_state,
    1.0  AS w_chatter,
    1.0  AS w_refs_in_max,   -- 参照される数で最大この点まで加点
    0.5  AS w_views_max,     -- 総ビュー数で最大この点まで加点
    10.0 AS refs_in_k,       -- refs_in / k でスケール(上限 w_refs_in_max)
    4.0  AS views_k          -- total_views / k でスケール(上限 w_views_max)
),

-- 評価対象のモデル(必要なら WHERE で除外条件を足す)
mdl AS (
  SELECT im.id, im.model, im.name AS display_name, im.transient, im.write_date
  FROM ir_model im
  WHERE im.transient = FALSE
  -- 技術系を除きたければ↓を有効化
  -- AND im.model NOT LIKE 'ir.%'
),

-- ビュー集計
views AS (
  SELECT
    iv.model,
    COUNT(*) FILTER (WHERE iv.type='form')   AS form_views,
    COUNT(*) FILTER (WHERE iv.type='tree')   AS tree_views,
    COUNT(*) FILTER (WHERE iv.type='kanban') AS kanban_views,
    COUNT(*) FILTER (WHERE iv.type='search') AS search_views,
    COUNT(*)                                  AS total_views,
    MAX(iv.write_date)                        AS views_write_date
  FROM ir_ui_view iv
  GROUP BY iv.model
),

-- act_window と メニュー露出
aw_cnt AS (
  SELECT aw.res_model AS model, COUNT(*) AS act_window_cnt
  FROM ir_act_window aw
  GROUP BY aw.res_model
),
menu_exposure AS (
  SELECT aw.res_model AS model, COUNT(*) AS menu_cnt
  FROM ir_ui_menu m
  JOIN ir_act_window aw
    ON m.action LIKE 'ir.actions.act_window,%'
   AND aw.id = split_part(m.action, ',', 2)::int
  GROUP BY aw.res_model
),

-- ワークフローっぽさ(state selection の有無)
has_state AS (
  SELECT im.model, TRUE AS flag
  FROM ir_model im
  JOIN ir_model_fields f ON f.model_id = im.id
  WHERE f.name='state' AND f.ttype='selection'
  GROUP BY im.model
),

-- Chatter(mail.thread系)判定:message_ids → mail.message
has_chatter AS (
  SELECT im.model, TRUE AS flag
  FROM ir_model im
  JOIN ir_model_fields f ON f.model_id = im.id
  WHERE f.name='message_ids' AND f.relation='mail.message'
  GROUP BY im.model
),

-- 参照入出力
refs_out AS (
  SELECT im.model, COUNT(*) AS refs_out
  FROM ir_model im
  JOIN ir_model_fields f ON f.model_id = im.id
  WHERE f.relation IS NOT NULL
  GROUP BY im.model
),
refs_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
),

-- 概算件数(pg_class.reltuples)
size AS (
  SELECT m.model, c.reltuples::bigint AS approx_rows
  FROM mdl m
  LEFT JOIN pg_class c ON c.relname = replace(m.model, '.', '_')
),

-- 最終更新(モデル/フィールド/ビューのMAX)
latest AS (
  SELECT
    m.model,
    GREATEST(
      COALESCE(m.write_date, '1970-01-01'::timestamp),
      COALESCE( (SELECT MAX(f2.write_date) FROM ir_model_fields f2 WHERE f2.model_id = m.id), '1970-01-01'::timestamp),
      COALESCE( (SELECT MAX(iv2.write_date) FROM ir_ui_view iv2 WHERE iv2.model = m.model), '1970-01-01'::timestamp)
    ) AS last_changed_at
  FROM mdl m
)

SELECT
  m.model,
  m.display_name,

  -- 指標
  COALESCE(v.form_views,0)       AS form_views,
  COALESCE(v.tree_views,0)       AS tree_views,
  COALESCE(v.kanban_views,0)     AS kanban_views,
  COALESCE(v.search_views,0)     AS search_views,
  COALESCE(v.total_views,0)      AS total_views,
  COALESCE(aw.act_window_cnt,0)  AS act_window_cnt,
  COALESCE(mx.menu_cnt,0)        AS menu_cnt,
  COALESCE(st.flag,FALSE)        AS has_state,
  COALESCE(ch.flag,FALSE)        AS has_chatter,
  COALESCE(ri.refs_in,0)         AS refs_in,
  COALESCE(ro.refs_out,0)        AS refs_out,
  COALESCE(sz.approx_rows,0)     AS approx_rows,
  lt.last_changed_at,

  -- スコア(モデル単体の“カスタマイズ候補度”)
  (
    -- ブール系
    (CASE WHEN COALESCE(v.form_views,0)  > 0 THEN (SELECT w_form   FROM weights) ELSE 0 END) +
    (CASE WHEN COALESCE(mx.menu_cnt,0)   > 0 THEN (SELECT w_menu   FROM weights) ELSE 0 END) +
    (CASE WHEN COALESCE(st.flag,FALSE)        THEN (SELECT w_state  FROM weights) ELSE 0 END) +
    (CASE WHEN COALESCE(ch.flag,FALSE)        THEN (SELECT w_chatter FROM weights) ELSE 0 END) +
    -- 連続系(上限あり)
    LEAST( COALESCE(ri.refs_in,0)   / (SELECT refs_in_k FROM weights), (SELECT w_refs_in_max FROM weights) ) +
    LEAST( COALESCE(v.total_views,0)/ (SELECT views_k   FROM weights), (SELECT w_views_max   FROM weights) )
  ) AS score

FROM mdl m
LEFT JOIN views        v  ON v.model = m.model
LEFT JOIN aw_cnt       aw ON aw.model = m.model
LEFT JOIN menu_exposure mx ON mx.model = m.model
LEFT JOIN has_state    st ON st.model = m.model
LEFT JOIN has_chatter  ch ON ch.model = m.model
LEFT JOIN refs_in      ri ON ri.model = m.model
LEFT JOIN refs_out     ro ON ro.model = m.model
LEFT JOIN size         sz ON sz.model = m.model
LEFT JOIN latest       lt ON lt.model = m.model

-- 必要なら“UI寄りだけ”に絞る(コメント解除)
-- WHERE (COALESCE(v.form_views,0) > 0 OR COALESCE(mx.menu_cnt,0) > 0)

ORDER BY score DESC, m.model
LIMIT 200;

Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です