实现pagetype类自动继承Page类,测试成功
This commit is contained in:
parent
885a13df70
commit
38136f59bf
@ -7,7 +7,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from jingrow.model.page import Page
|
from jingrow.model.page import Page, get_page_instance
|
||||||
from jingrow.config import Config
|
from jingrow.config import Config
|
||||||
from jingrow.utils.jingrow_api import upload_file_to_jingrow, get_record_count
|
from jingrow.utils.jingrow_api import upload_file_to_jingrow, get_record_count
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ for _lvl in list(_ANSI.keys()):
|
|||||||
|
|
||||||
def get_pg(pagetype: str, name: str):
|
def get_pg(pagetype: str, name: str):
|
||||||
"""获取单条记录并转为可属性访问的对象,失败返回 None。"""
|
"""获取单条记录并转为可属性访问的对象,失败返回 None。"""
|
||||||
pg = Page(pagetype)
|
pg = get_page_instance(pagetype)
|
||||||
res = pg.get(name)
|
res = pg.get(name)
|
||||||
if not isinstance(res, dict) or not res.get('success'):
|
if not isinstance(res, dict) or not res.get('success'):
|
||||||
return None
|
return None
|
||||||
@ -46,7 +46,7 @@ def get_pg(pagetype: str, name: str):
|
|||||||
|
|
||||||
def create_pg(pagetype: str, data: Dict[str, Any]):
|
def create_pg(pagetype: str, data: Dict[str, Any]):
|
||||||
"""创建记录,返回创建后的数据对象或 None。"""
|
"""创建记录,返回创建后的数据对象或 None。"""
|
||||||
pg = Page(pagetype)
|
pg = get_page_instance(pagetype)
|
||||||
res = pg.create(data)
|
res = pg.create(data)
|
||||||
if not isinstance(res, dict) or not res.get('success'):
|
if not isinstance(res, dict) or not res.get('success'):
|
||||||
return None
|
return None
|
||||||
@ -56,7 +56,7 @@ def create_pg(pagetype: str, data: Dict[str, Any]):
|
|||||||
|
|
||||||
def update_pg(pagetype: str, name: str, data: Dict[str, Any]):
|
def update_pg(pagetype: str, name: str, data: Dict[str, Any]):
|
||||||
"""更新记录,成功返回更新后的数据对象或 True,失败返回 False。"""
|
"""更新记录,成功返回更新后的数据对象或 True,失败返回 False。"""
|
||||||
pg = Page(pagetype)
|
pg = get_page_instance(pagetype)
|
||||||
res = pg.update(name, data)
|
res = pg.update(name, data)
|
||||||
if not isinstance(res, dict) or not res.get('success'):
|
if not isinstance(res, dict) or not res.get('success'):
|
||||||
return False
|
return False
|
||||||
@ -101,14 +101,14 @@ def get_hook_source() -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def delete_pg(pagetype: str, name: str) -> bool:
|
def delete_pg(pagetype: str, name: str) -> bool:
|
||||||
pg = Page(pagetype)
|
pg = get_page_instance(pagetype)
|
||||||
res = pg.delete(name)
|
res = pg.delete(name)
|
||||||
return bool(isinstance(res, dict) and res.get('success'))
|
return bool(isinstance(res, dict) and res.get('success'))
|
||||||
|
|
||||||
|
|
||||||
def get_list(pagetype: str, filters: List[List[Any]] = None, fields: List[str] = None, limit: int = None):
|
def get_list(pagetype: str, filters: List[List[Any]] = None, fields: List[str] = None, limit: int = None):
|
||||||
"""获取记录列表,返回对象列表;失败返回空列表。"""
|
"""获取记录列表,返回对象列表;失败返回空列表。"""
|
||||||
pg = Page(pagetype)
|
pg = get_page_instance(pagetype)
|
||||||
res = pg.list(filters=filters, fields=fields, limit=limit)
|
res = pg.list(filters=filters, fields=fields, limit=limit)
|
||||||
if not isinstance(res, dict) or not res.get('success'):
|
if not isinstance(res, dict) or not res.get('success'):
|
||||||
return []
|
return []
|
||||||
@ -135,7 +135,7 @@ def get_single(pagetype: str):
|
|||||||
|
|
||||||
def get_module_app(pagetype: str):
|
def get_module_app(pagetype: str):
|
||||||
"""获取指定 pagetype 的模块应用信息,返回后端适配器的原始结果结构。"""
|
"""获取指定 pagetype 的模块应用信息,返回后端适配器的原始结果结构。"""
|
||||||
return Page(pagetype).get_module_app()
|
return get_page_instance(pagetype).get_module_app()
|
||||||
|
|
||||||
|
|
||||||
def get_pg_id(
|
def get_pg_id(
|
||||||
@ -146,22 +146,22 @@ def get_pg_id(
|
|||||||
site: Optional[str] = None,
|
site: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""根据过滤条件或字段值获取文档 ID,返回后端适配器的原始结果结构。"""
|
"""根据过滤条件或字段值获取文档 ID,返回后端适配器的原始结果结构。"""
|
||||||
return Page(pagetype).get_pg_id(filters=filters, field=field, value=value, site=site)
|
return get_page_instance(pagetype).get_pg_id(filters=filters, field=field, value=value, site=site)
|
||||||
|
|
||||||
|
|
||||||
def get_meta(pagetype: str):
|
def get_meta(pagetype: str):
|
||||||
"""获取 pagetype 的元数据,返回后端适配器的原始结果结构。"""
|
"""获取 pagetype 的元数据,返回后端适配器的原始结果结构。"""
|
||||||
return Page(pagetype).get_meta()
|
return get_page_instance(pagetype).get_meta()
|
||||||
|
|
||||||
|
|
||||||
def get_field_mapping_from_jingrow(pagetype: str):
|
def get_field_mapping_from_jingrow(pagetype: str):
|
||||||
"""获取字段的label到fieldname的映射,返回 {label: fieldname} 的映射字典。"""
|
"""获取字段的label到fieldname的映射,返回 {label: fieldname} 的映射字典。"""
|
||||||
return Page(pagetype).get_field_mapping_from_jingrow()
|
return get_page_instance(pagetype).get_field_mapping_from_jingrow()
|
||||||
|
|
||||||
|
|
||||||
def get_field_value_from_jingrow(pagetype: str, name: str, fieldname: str):
|
def get_field_value_from_jingrow(pagetype: str, name: str, fieldname: str):
|
||||||
"""从Jingrow获取字段的当前值,返回字段的当前值,如果为空则返回None。"""
|
"""从Jingrow获取字段的当前值,返回字段的当前值,如果为空则返回None。"""
|
||||||
return Page(pagetype).get_field_value_from_jingrow(name, fieldname)
|
return get_page_instance(pagetype).get_field_value_from_jingrow(name, fieldname)
|
||||||
|
|
||||||
|
|
||||||
def upload_file(file_data: bytes, filename: str, attached_to_pagetype: Optional[str] = None, attached_to_name: Optional[str] = None, attached_to_field: Optional[str] = None):
|
def upload_file(file_data: bytes, filename: str, attached_to_pagetype: Optional[str] = None, attached_to_name: Optional[str] = None, attached_to_field: Optional[str] = None):
|
||||||
|
|||||||
123
apps/jingrow/jingrow/ai/pagetype/local_ai_node/local_ai_node.py
Normal file
123
apps/jingrow/jingrow/ai/pagetype/local_ai_node/local_ai_node.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Copyright (c) 2025, jingrow and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from typing import Dict, Any
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import jingrow
|
||||||
|
from jingrow.model.page import Page
|
||||||
|
from jingrow.utils.path import get_jingrow_root
|
||||||
|
from jingrow.utils.fs import atomic_write_json
|
||||||
|
|
||||||
|
|
||||||
|
class LocalAiNode(Page):
|
||||||
|
"""
|
||||||
|
Local Ai Node 页面类型
|
||||||
|
在保存时自动同步节点数据到 JSON 文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""创建节点记录,并同步到 JSON 文件"""
|
||||||
|
result = super().create(data)
|
||||||
|
if result.get('success'):
|
||||||
|
created_data = result.get('data', {})
|
||||||
|
if created_data.get('node_type'):
|
||||||
|
_sync_node_to_json_file(created_data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update(self, name: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""更新节点记录,并同步到 JSON 文件"""
|
||||||
|
result = super().update(name, data)
|
||||||
|
if result.get('success'):
|
||||||
|
# 获取更新后的完整数据
|
||||||
|
updated_res = self.get(name)
|
||||||
|
if updated_res.get('success'):
|
||||||
|
updated_data = updated_res.get('data', {})
|
||||||
|
if updated_data.get('node_type'):
|
||||||
|
_sync_node_to_json_file(updated_data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_node_to_json_file(node_data: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
将节点数据同步到 JSON 文件(LocalAiNode 类的辅助函数)
|
||||||
|
当保存 Local Ai Node 时,自动更新对应的 JSON 文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node_data: 节点数据字典,包含 node_type, node_label, node_icon 等字段
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 同步是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
node_type = node_data.get('node_type')
|
||||||
|
if not node_type:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取节点目录路径
|
||||||
|
jingrow_root = get_jingrow_root()
|
||||||
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
|
node_dir = nodes_root / node_type
|
||||||
|
json_file = node_dir / f"{node_type}.json"
|
||||||
|
|
||||||
|
# 确保目录存在
|
||||||
|
node_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 解析 node_schema(可能是字典或 JSON 字符串)
|
||||||
|
node_schema = node_data.get('node_schema') or {}
|
||||||
|
if isinstance(node_schema, str):
|
||||||
|
try:
|
||||||
|
node_schema = json.loads(node_schema)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
node_schema = {}
|
||||||
|
if not isinstance(node_schema, dict):
|
||||||
|
node_schema = {}
|
||||||
|
|
||||||
|
# 确保 node_schema 中不包含 metadata(如果存在则移除)
|
||||||
|
node_schema = {k: v for k, v in node_schema.items() if k != 'metadata'}
|
||||||
|
|
||||||
|
# 构建完整的 JSON 文件内容
|
||||||
|
json_content = {
|
||||||
|
"metadata": {
|
||||||
|
"type": node_type,
|
||||||
|
"label": node_data.get('node_label') or node_type,
|
||||||
|
"icon": node_data.get('node_icon') or "fa-cube",
|
||||||
|
"color": node_data.get('node_color') or "#6b7280",
|
||||||
|
"description": node_data.get('node_description') or "",
|
||||||
|
"group": node_data.get('node_group') or "其他",
|
||||||
|
"component_type": node_data.get('node_component') or "GenericNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 合并 node_schema 的所有字段(properties, required, _layout 等)
|
||||||
|
# 确保包含完整的 schema 配置
|
||||||
|
json_content.update(node_schema)
|
||||||
|
|
||||||
|
# 确保必需的字段存在(即使 node_schema 中没有)
|
||||||
|
json_content.setdefault("properties", {})
|
||||||
|
json_content.setdefault("required", [])
|
||||||
|
json_content.setdefault("_layout", {
|
||||||
|
"tabs": [{
|
||||||
|
"id": "tab_1",
|
||||||
|
"label": "基本设置",
|
||||||
|
"sections": [{
|
||||||
|
"id": "section_1",
|
||||||
|
"label": "",
|
||||||
|
"columns": [{
|
||||||
|
"id": "column_1",
|
||||||
|
"label": "",
|
||||||
|
"fields": []
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"activeTab": "tab_1"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 使用原子写入确保数据完整性
|
||||||
|
atomic_write_json(json_file, json_content)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
# 静默失败,不影响主流程
|
||||||
|
jingrow.log_error("同步节点到 JSON 文件失败", str(e), exc=e)
|
||||||
|
return False
|
||||||
@ -7,6 +7,39 @@ from jingrow.core.hooks import execute_hook, execute_hook_async
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
def get_page_instance(pagetype: str) -> 'Page':
|
||||||
|
"""
|
||||||
|
获取 Page 实例,支持使用自定义 Page 子类
|
||||||
|
|
||||||
|
自动发现并加载 pagetype 对应的 Page 子类:
|
||||||
|
- 命名规则:pagetype 转换为类名,如 "Local Ai Node" -> "LocalAiNode"
|
||||||
|
- 模块路径:jingrow.ai.pagetype.{pagetype_snake_case}.{pagetype_snake_case}
|
||||||
|
- 如果找不到子类,回退到默认 Page 类
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pagetype: 页面类型名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Page 实例(可能是自定义子类或默认 Page 类)
|
||||||
|
"""
|
||||||
|
# 转换命名: "Local Ai Node" -> "LocalAiNode"
|
||||||
|
class_name = pagetype.replace(" ", "").replace("_", "")
|
||||||
|
|
||||||
|
# 尝试从 pagetype 目录导入
|
||||||
|
try:
|
||||||
|
pagetype_snake = pagetype.lower().replace(' ', '_')
|
||||||
|
module_path = f"jingrow.ai.pagetype.{pagetype_snake}.{pagetype_snake}"
|
||||||
|
module = __import__(module_path, fromlist=[class_name])
|
||||||
|
page_class = getattr(module, class_name, None)
|
||||||
|
if page_class and issubclass(page_class, Page):
|
||||||
|
return page_class(pagetype)
|
||||||
|
except (ImportError, AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 如果找不到子类,使用默认 Page
|
||||||
|
return Page(pagetype)
|
||||||
|
|
||||||
|
|
||||||
class Page:
|
class Page:
|
||||||
"""基于适配器的通用 Page 模型,提供常用 CRUD 与钩子机制。
|
"""基于适配器的通用 Page 模型,提供常用 CRUD 与钩子机制。
|
||||||
钩子事件命名与 API 对齐:after_insert, on_update, on_trash 等。
|
钩子事件命名与 API 对齐:after_insert, on_update, on_trash 等。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user