/** * 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'] ); } } } } } } } } Portal Viewの仕様 – Raqqa

Portal Viewの仕様


対象ディレクトリと変更観点

  • api/app/routers/views.py新エンドポイント実装の中心
  • api/app/services/views_import.pysrc→portal インポートのロジック(今回の新マッピング対応)
  • api/app/services/views_export.py … ChromaDBエクスポートのうち View 連携部分
  • api/app/services/ai_enrich.pyhelp_i18n_html をもとに OpenAI で用途推測 → help_ja_html 保存(別エンドポイント化)
  • api/app/schemas.py(or api/app/schemas/views.py)… Pydanticスキーマ更新(新DBカラムに対応)
  • 参考:translate.py / chroma.py に倣い、Views版の翻訳・Chromaを並行で持つか、views.pyに統合するかはお好みでOK

新エンドポイント設計(REST+“コマンド系”)

1) Common(グループ=action_xmlid)周り

  • GET /views/common
    クエリ: model?, q?, limit?, offset?
    返却: portal_view_common のページング一覧+子ビュー数
  • GET /views/common/{action_xmlid}
    返却: portal_view_common 本体+childrenportal_view の各 view_typeと主な設定)
  • POST /views/common(Upsert)
    入力: portal_view_common 相当(action_xmlid 必須、その他任意)
    返却: upsert結果
    用途: 新規モデル作成時、まず common を空で1行作るフローに対応
  • DELETE /views/common/{action_xmlid}
    common と配下の views を Cascade で削除

2) Views(1レコード = 1 ビュー種)

  • GET /views
    クエリ: action_xmlid?, view_type?, is_primary? など
    返却: portal_view 列+必要なら common 概要
  • POST /views/{action_xmlid}(作成 or Upsert by (common_id, view_type))
    入力: view_type該当ビュー種の設定カラムのみ(例: list系は list_inline_edit など)
    返却: 作成/更新された portal_view
    用途: 「既存モデルの追加」時、srcを読んで正規化したビュー種をまとめて作成(バルク版は下の commands に用意)
  • PATCH /views/{view_id}
    入力: 部分更新(任意のビュー種カラム)
    返却: 更新後 portal_view
  • DELETE /views/{view_id}

3) Relations(関連)コマンド

  • GET /views/commands/relations/{action_xmlid}
    返却: { "action_xmlid": "...", "model_tech": "...", "children": [ {"view_id":1,"view_type":"list","is_primary":true, "priority_num":10}, {"view_id":2,"view_type":"form","is_primary":false,"priority_num":20}, ... ] } 用途: common と配下ビューの“関連”を一望(UI連携用)

4) Position(順序・主ビュー)コマンド

  • POST /views/commands/position/{action_xmlid}/reorder
    入力例: {"orders":[{"view_id":1,"priority_num":10},{"view_id":2,"priority_num":20}]} 効果: 同一 common_id 内の priority_num を一括更新
  • POST /views/commands/position/{action_xmlid}/primary
    入力例: {"view_id": 1}
    効果: 同一 common_id 内の is_primary を単一化(DB側トリガと整合)

5) Import / Hydration(src→portal 挿入)

  • POST /views/commands/import/from-src
    パラメータ: action_xmlid?, model_tech?, dry_run?=true|false
    処理:
    1. portal_view_src(= ir_view_src)から 指定条件の行を抽出
    2. portal_view_common全カラム移行(Upsert)
    3. view_types / view_mode / primary_view_type を正規化 → portal_view を (common_id, view_type) でUpsert
      返却: inserted/updated カウント、スキップ詳細
      help_ja_html の推測はここでは行わない(後述AIエンドポイント)
  • POST /views/commands/init/model(新規モデル作成時の空行追加)
    入力例: {"action_xmlid":"...", "model_tech":"sale.order", "action_name":"...", ...}
    効果: common を空(あるいは最小値)で作成。viewsはまだ作らない or 指定があれば1種だけ作る。
  • POST /views/commands/hydrate/model(既存モデル追加=自動取り込み)
    入力: {"model_tech":"sale.order"}
    効果: portal_view_src から該当行を抽出して common + views を自動生成(今回のマッピング準拠)

6) AI 推測(用途推測は別エンドポイント

  • POST /views/ai/guess-purpose/{action_xmlid}
    クエリ: target_lang=ja_JP(既定:日本語)
    入力: オプション {"overwrite": false}
    処理: portal_view_common.help_i18n_html(または各言語help/テキスト)を要約・解釈 → 用途を推測help_ja_html(or 指定言語)へ保存
    返却: 推測結果、トークン/実行時間、変更有無
    src→portal インサート時には呼ばず、このエンドポイントで明示実行

7) 翻訳 / Chroma(Views版)

  • POST /views/translate/common/{action_xmlid}
    クエリ: target=..., only_missing=true|false
    入力: {"fields":["help_i18n_html","ai_purpose_i18n"]} など
    効果: 欠落言語を補完し、portal_view_common を更新
  • POST /views/chroma/export
    クエリ: model?, action_xmlid?, dry_run?
    効果: Common+View を “文脈テキスト+メタ” に変換して Chroma に upsert
    (既存の fields/export/chroma に合わせた設計)

スキーマ対応(抜粋)

  • portal_view_commonaction_xmlid 一意portal_view(common_id, view_type)一意
  • portal_view新カラム(form/kanban/list/calendar/search/graph/pivot/dashboard/tree/map)に対応するバリデーションは Pydantic で view_type ごとに軽く実装。

正規化ルール(CSV→View)

  • view_types / view_mode をトークナイズ(, / 空白)
  • 前後空白除去、小文字正規化
  • マッピング:treeツリー表示として view_type="tree"(今回、ListとTreeは別種
  • 許容列挙:form, kanban, list, calendar, search, graph, pivot, dashboard, tree, map
  • primary_view_type が不整合なら is_primary=false のまま(自動補填はしない方針)

期待する差分(ファイル別の“やること”)

  • routers/views.py
    • 上記エンドポイントを実装(REST+commands)
    • OpenAPIのタグ分け:Views, Views:Commands, Views:AI, Views:Chroma
  • services/views_import.py
    • src→portal のマッピング(common全カラム移行、views生成)
    • 正規化ユーティリティ(split_types, normalize_type
    • 冪等Upsert((action_xmlid) と (common_id, view_type))
  • services/views_export.py
    • Chroma投入のテキスト整形(commonの ai_purpose / help_i18n_html を活用)
  • services/ai_enrich.py
    • help用途推測(プロンプト雛形と安全策、overwrite制御)
  • schemas.py / schemas/views.py
    • 入出力用 Pydantic モデル(view_typeごとに Optional フィールド)
    • position 用の ReorderItemview_id, priority_num

Comments

コメントを残す

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