Files
yuthon_base/yuthon_brain/controllers/main.py
T
2026-09-21 16:26:36 +08:00

135 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
import json
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request, Response
def _json(data, status=200):
"""统一的 JSON 响应。"""
return Response(json.dumps(data, ensure_ascii=False),
mimetype='application/json', status=status)
class BrainController(http.Controller):
"""前端脑图页面与数据接口。除“新建节点”外均为只读。"""
# ---------------- 只读查询接口(JSON)----------------
@http.route('/yuthon_brain/api/<string:key>/graph', type='http',
auth='user', methods=['GET'], csrf=False)
def api_graph(self, key, **kw):
"""返回某数据源的脑图结构:{root, nodes, edges, theme, layout}。"""
source = request.env['yuthon.brain.source'].search(
[('key', '=', key), ('active', '=', True)], limit=1)
if not source:
return Response(json.dumps({'error': 'not_found'}, ensure_ascii=False),
mimetype='application/json', status=404)
nodes = []
for n in source.node_ids:
try:
extra = json.loads(n.extra or '{}') if n.extra else {}
except (ValueError, TypeError):
extra = {}
nodes.append({
'id': n.id,
'label': n.label,
'parent_id': n.parent_id.id if n.parent_id else None,
'rel_type': n.rel_type,
'color': n.color or '#1E6FFF',
'icon': n.icon or '',
'level': n.level,
'extra': extra,
})
edges = [{
'id': e.id,
'from': e.from_id.id,
'to': e.to_id.id,
'rel_type': e.rel_type,
'label': e.label or '',
} for e in source.edge_ids]
payload = {
'root': source.root_id.id if source.root_id else None,
'theme': source.theme,
'layout': source.layout,
'source_name': source.name,
'nodes': nodes,
'edges': edges,
}
return Response(json.dumps(payload, ensure_ascii=False),
mimetype='application/json')
@http.route('/yuthon_brain/api/<string:key>/node', type='http',
auth='user', methods=['POST'], csrf=False)
def api_create_node(self, key, **kw):
"""新建脑图节点,body = {"parent_id": <id|null>, "label": "..."}。
只有系统管理员对 yuthon.brain.node 有 create 权限,其他账号返回 403。
前端拖拽端口后调用此接口把新节点落库。
"""
source = request.env['yuthon.brain.source'].search(
[('key', '=', key), ('active', '=', True)], limit=1)
if not source:
return _json({'error': 'not_found'}, 404)
try:
payload = json.loads(request.httprequest.get_data(as_text=True) or '{}')
except (ValueError, TypeError):
payload = {}
if not isinstance(payload, dict):
payload = {}
label = (payload.get('label') or '').strip()[:120]
if not label:
return _json({'error': 'empty_label'}, 400)
Node = request.env['yuthon.brain.node']
parent = None
pid = payload.get('parent_id')
if pid is not None and pid != '':
try:
cand = Node.browse(int(pid))
except (ValueError, TypeError):
cand = Node.browse([])
# 只允许挂到同一数据源下已存在的节点上
if cand.exists() and cand.source_id.id == source.id:
parent = cand.id
if parent is None:
parent = source.root_id.id or False
try:
node = Node.create({
'source_id': source.id,
'parent_id': parent,
'rel_type': 'child',
'label': label,
'auto': False, # 手动节点:重同步时不会被清理
})
except AccessError:
return _json({'error': 'forbidden'}, 403)
except Exception as exc:
msg = str(exc).lower()
if 'unique' in msg or 'duplicate' in msg:
return _json({'error': 'duplicate'}, 409)
raise
return _json({
'id': node.id,
'label': node.label,
'parent_id': node.parent_id.id or None,
'rel_type': node.rel_type,
})
@http.route('/yuthon_brain/api/sources', type='http',
auth='user', methods=['GET'], csrf=False)
def api_sources(self, **kw):
"""已启用数据源列表。"""
sources = request.env['yuthon.brain.source'].search(
[('active', '=', True)], order='sequence, id')
data = [{'key': s.key, 'name': s.name} for s in sources]
return Response(json.dumps(data, ensure_ascii=False),
mimetype='application/json')