xls2ddl/xls2ddl/tmpls.py
Hermes Agent 66bd6339df fix: InlineForm search bind uses actiontype script to pass form values to Tabular.render()
actiontype:method calls render() without args, losing search params.
Changed to actiontype:script with getWidgetById + await tbl.render(params)
which correctly receives merged InlineForm submit data as params argument.
2026-06-22 12:09:25 +08:00

596 lines
15 KiB
Python

data_browser_tmpl = """
{
"widgettype":"VBox",
"options":{"height":"100%","width":"100%"},
"subwidgets":[
{% if data_filter and data_filter.fields %}
{
"widgettype":"InlineForm",
"id":"{{tblname}}_search",
"options":{
"css":"card",
"padding":"8px",
"show_label":false,
"submit_label":"搜索",
"submit_css":"primary",
"fields":[
{%- for f in data_filter.fields %}
{%- if f.uitype == 'code' %}
{{json.dumps(filter_fields[loop.index0], ensure_ascii=False)}}{% if not loop.last %},{% endif %}
{%- else %}
{"name":"{{f.field}}","uitype":"{{f.uitype}}","placeholder":"{{f.title}}","cwidth":15}{% if not loop.last %},{% endif %}
{%- endif %}
{%- endfor %}
]
},
"binds":[{
"wid":"self",
"event":"submit",
"actiontype":"script",
"target":"{{tblname}}_tbl",
"script":"var tbl = bricks.getWidgetById('{{tblname}}_tbl', bricks.app.root); if(tbl) await tbl.render(params);"
}]
},
{% endif %}
{
"id":"{{tblname}}_tbl",
"widgettype":"Tabular",
"options":{
"width":"100%",
"height":"100%",
{% if not notitle %}
{% if title %}
"title":"{{title}}",
{% else %}
"title":"{{summary[0].title}}",
{% endif %}
{% endif %}
{% if description %}
"description":"{{description}}",
{% endif %}
{% if toolbar %}
"toolbar":{{json.dumps(toolbar, indent=4, ensure_ascii=False)}},
{% endif %}
"css":"card",
{% if editor %}
"editor":{{json.dumps(editor, indent=4, ensure_ascii=False)}},
{% endif %}
{% if not noedit %}
"editable":{
{% if new_data_url %}
"new_data_url": "{{new_data_url}}",
{% else %}
"new_data_url":{%- raw -%}"{{entire_url('add_{%- endraw -%}{{summary[0].name}}{%- raw -%}.dspy')}}",{%- endraw %}
{% endif %}
{% if delete_data_url %}
"delete_data_url": "{{delete_data_url}}",
{% else %}
"delete_data_url":{%- raw -%}"{{entire_url('delete_{%- endraw -%}{{summary[0].name}}{%- raw -%}.dspy')}}",{%- endraw %}
{% endif %}
{% if update_data_url %}
"update_data_url": "{{update_data_url}}"
{% else %}
"update_data_url":{%- raw -%}"{{entire_url('update_{%- endraw -%}{{summary[0].name}}{%- raw -%}.dspy')}}"{%- endraw %}
{% endif %}
},
{% endif %}
{% if data_url %}
"data_url": "{{data_url}}",
{% else %}
"data_url":"{%- raw -%}{{entire_url('./get_{%- endraw -%}{{summary[0].name}}{%- raw -%}.dspy')}}",{%- endraw %}
{% endif %}
"data_method":"{{data_method or 'GET'}}",
"data_params":{%- raw -%}{{json.dumps(params_kw, indent=4, ensure_ascii=False)}},{%- endraw %}
"row_options":{
{% if idField %}
"idField":"{{idField}}",
{% endif %}
{% if checkField %}
"checkField":"{{checkField}}",
{% endif %}
{% if browserfields %}
"browserfields": {{json.dumps(browserfields, indent=4, ensure_ascii=Fasle)}},
{% endif %}
{% if editexclouded %}
"editexclouded":{{json.dumps(editexclouded, indent=4, ensure_ascii=False)}},
{% endif %}
"fields":{{fieldliststr}}
},
{% if subtables_condition %}
{%- raw -%}{% {%- endraw %}if {{subtables_condition}} {%- raw -%} %}{%- endraw -%}
{% endif %}
{% if content_view %}
"content_view":{{json.dumps(content_view, indent=4, ensure_ascii=False)}},
{% endif %}
{% if data_filter %}
"data_filter":{{json.dumps(data_filter, indent=4, ensure_ascii=False)}},
{% endif %}
{% if filter_labels %}
"filter_labels":{{json.dumps(filter_labels, indent=4, ensure_ascii=False)}},
{% endif %}
{% if filter_title %}
"filter_title":"{{filter_title}}",
{% endif %}
{% if filter_icon %}
"filter_icon":"{{filter_icon}}",
{% endif %}
{% if subtables_condition %}
{%- raw -%}{% endif %}{%- endraw %}
{% endif %}
"page_rows":160,
"cache_limit":5
}
{% if bindsstr %}
,"binds":{{bindsstr}}
{% endif %}
}]
}
"""
get_data_tmpl = """
ns = params_kw.copy()
{% if logined_userid %}
userid = await get_user()
if not userid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userid}}'] = userid
ns['userid'] = userid
{% endif %}
{% if logined_userorgid %}
userorgid = await get_userorgid()
if not userorgid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userorgid}}'] = userorgid
ns['userorgid'] = userorgid
{% endif %}
debug(f'get_{{tblname}}.dspy:{ns=}')
if not ns.get('page'):
ns['page'] = 1
if not ns.get('sort'):
{% if sortby %}
{% if type(sortby) == type("") %}
ns['sort'] = '{{sortby}}'
{% else %}
ns['sort'] = {{json.dumps(sortby)}}
{% endif %}
{% else %}
ns['sort'] = 'id'
{% endif %}
{% if relation %}
ns['sort'] = '{{relation.outter_field}}_text'
{% endif %}
sql = '''{{sql}}'''
{% if not relation %}
filterjson = params_kw.get('data_filter')
if filterjson and isinstance(filterjson, str):
\ttry:
\t\tfilterjson = json.loads(filterjson)
\texcept (json.JSONDecodeError, TypeError):
\t\tfilterjson = None
# data_filter可能是CRUD字段定义({"fields":[...]}),不是过滤条件,忽略
if filterjson and isinstance(filterjson, dict) and 'fields' in filterjson:
filterjson = None
fields_str=r'''{{json.dumps(fields, indent=4, ensure_ascii=False)}}'''
ori_fields = json.loads(fields_str)
if not filterjson:
fields = [ f['name'] for f in ori_fields ]
filterjson = default_filterjson(fields, ns)
{% if logined_userorgid or logined_userid %}
# 确保 logined 过滤条件始终生效
if filterjson:
if not isinstance(filterjson, dict) or 'AND' not in filterjson:
filterjson = {'AND': [filterjson] if filterjson else []}
{% if logined_userorgid %}
filterjson['AND'].append({'field': '{{logined_userorgid}}', 'op': '=', 'var': '__logined_orgid__'})
ns['__logined_orgid__'] = userorgid
{% endif %}
{% if logined_userid %}
filterjson['AND'].append({'field': '{{logined_userid}}', 'op': '=', 'var': '__logined_uid__'})
ns['__logined_uid__'] = userid
{% endif %}
{% endif %}
filterdic = ns.copy()
filterdic['filterstr'] = ''
filterdic['userorgid'] = '${userorgid}$'
filterdic['userid'] = '${userid}$'
if filterjson:
dbf = DBFilter(filterjson)
conds = dbf.gen(ns)
if conds:
ns.update(dbf.consts)
conds = f' and {conds}'
filterdic['filterstr'] = conds
ac = ArgsConvert('[[', ']]')
vars = ac.findAllVariables(sql)
NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' }
filterdic.update(NameSpace)
sql = ac.convert(sql, filterdic)
{% endif %}
debug(f'{sql=}')
db = DBPools()
dbname = get_module_dbname('{{modulename}}')
async with db.sqlorContext(dbname) as sor:
r = await sor.sqlPaging(sql, ns)
return r
return {
"total":0,
"rows":[]
}
"""
data_new_tmpl = """
ns = params_kw.copy()
for k,v in ns.items():
if v == 'NaN' or v == 'null':
ns[k] = None
id = params_kw.id
if not id or len(id) > 32:
id = uuid()
ns['id'] = id
{% for f in confidential_fields or [] %}
if params_kw.get('{{f}}'):
ns['{{f}}'] = password_encode(params_kw.get('{{f}}'))
{% endfor %}
{% if logined_userid %}
userid = await get_user()
if not userid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userid}}'] = userid
{% endif %}
{% if logined_userorgid %}
userorgid = await get_userorgid()
if not userorgid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userorgid}}'] = userorgid
{% endif %}
{% if validation_rules %}
_validation_rules = json.loads(r'''{{validation_rules}}''')
import re as _re
_errors = []
for _fname, _rules in _validation_rules.items():
_val = params_kw.get(_fname, '')
if _val is None: _val = ''
_val = str(_val)
for _rule in _rules:
_rt = _rule.get('type', '')
_rm = _rule.get('message', _fname)
_rv = _rule.get('value')
if _rt == 'required':
if not _val or _val.strip() == '':
_errors.append(_rm)
break
elif _rt == 'minlength':
if _val and len(_val) < int(_rv):
_errors.append(_rm)
break
elif _rt == 'maxlength':
if len(_val) > int(_rv):
_errors.append(_rm)
break
elif _rt in ('min', 'max'):
if _val:
try:
_n = float(_val)
if _rt == 'min' and _n < float(_rv): _errors.append(_rm); break
if _rt == 'max' and _n > float(_rv): _errors.append(_rm); break
except (ValueError, TypeError):
_errors.append(_rm)
break
elif _rt == 'pattern':
if _val and not _re.match(_rv, _val):
_errors.append(_rm)
break
elif _rt == 'email':
if _val and not _re.match(r'^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$', _val):
_errors.append(_rm)
break
elif _rt == 'number':
if _val:
try: float(_val)
except (ValueError, TypeError): _errors.append(_rm); break
if _errors:
return {"widgettype":"Error","options":{"title":"Validation Failed","cwidth":16,"cheight":9,"timeout":3,"message":"; ".join(_errors)}}
{% endif %}
db = DBPools()
dbname = get_module_dbname('{{modulename}}')
async with db.sqlorContext(dbname) as sor:
r = await sor.C('{{summary[0].name}}', ns.copy())
return {
"widgettype":"Message",
"options":{
"cwidth":16,
"cheight":9,
"title":"Add Success",
"timeout":3,
"message":"ok"
}
}
return {
"widgettype":"Error",
"options":{
"title":"Add Error",
"cwidth":16,
"cheight":9,
"timeout":3,
"message":"failed"
}
}
"""
data_update_tmpl = """
ns = params_kw.copy()
for k,v in ns.items():
if v == 'NaN' or v == 'null':
ns[k] = None
{% if logined_userid %}
userid = await get_user()
if not userid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userid}}'] = userid
{% endif %}
{% if logined_userorgid %}
userorgid = await get_userorgid()
if not userorgid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userorgid}}'] = userorgid
{% endif %}
{% for f in confidential_fields or [] %}
if params_kw.get('{{f}}'):
ns['{{f}}'] = password_encode(params_kw.get('{{f}}'))
{% endfor %}
{% if validation_rules %}
_validation_rules = json.loads(r'''{{validation_rules}}''')
import re as _re
_errors = []
for _fname, _rules in _validation_rules.items():
\t_val = params_kw.get(_fname, '')
\tif _val is None: _val = ''
\t_val = str(_val)
\tfor _rule in _rules:
\t\t_rt = _rule.get('type', '')
\t\t_rm = _rule.get('message', _fname)
\t\t_rv = _rule.get('value')
\t\tif _rt == 'required':
\t\t\tif not _val or _val.strip() == '':
\t\t\t\t_errors.append(_rm)
\t\t\t\tbreak
\t\telif _rt == 'minlength':
\t\t\tif _val and len(_val) < int(_rv):
\t\t\t\t_errors.append(_rm)
\t\t\t\tbreak
\t\telif _rt == 'maxlength':
\t\t\tif len(_val) > int(_rv):
\t\t\t\t_errors.append(_rm)
\t\t\t\tbreak
\t\telif _rt in ('min', 'max'):
\t\t\tif _val:
\t\t\t\ttry:
\t\t\t\t\t_n = float(_val)
\t\t\t\t\tif _rt == 'min' and _n < float(_rv): _errors.append(_rm); break
\t\t\t\t\tif _rt == 'max' and _n > float(_rv): _errors.append(_rm); break
\t\t\t\texcept (ValueError, TypeError):
\t\t\t\t\t_errors.append(_rm)
\t\t\t\t\tbreak
\t\telif _rt == 'pattern':
\t\t\tif _val and not _re.match(_rv, _val):
\t\t\t\t_errors.append(_rm)
\t\t\t\tbreak
\t\telif _rt == 'email':
\t\t\tif _val and not _re.match(r'^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$', _val):
\t\t\t\t_errors.append(_rm)
\t\t\t\tbreak
\t\telif _rt == 'number':
\t\t\tif _val:
\t\t\t\ttry: float(_val)
\t\t\t\texcept (ValueError, TypeError): _errors.append(_rm); break
if _errors:
\treturn {"widgettype":"Error","options":{"title":"Validation Failed","cwidth":16,"cheight":9,"timeout":3,"message":"; ".join(_errors)}}
{% endif %}
db = DBPools()
dbname = get_module_dbname('{{modulename}}')
async with db.sqlorContext(dbname) as sor:
{% if logined_userid or logined_userorgid %}
ns1 = {
{% if logined_userorgid %}
"{{logined_userorgid}}": userorgid,
{% endif %}
{% if logined_userid %}
"{{logined_userid}}": userid,
{% endif %}
"id": params_kw.id
}
recs = await sor.R('{{summary[0].name}}', ns1)
if len(recs) < 1:
return {
"widgettype":"Error",
"options":{
"title":"Update Error",
"cwidth":16,
"cheight":9,
"timeout":3,
"message":"Record no exist or with wrong ownership"
}
}
{% endif %}
r = await sor.U('{{summary[0].name}}', ns)
debug('update success');
return {
"widgettype":"Message",
"options":{
"title":"Update Success",
"cwidth":16,
"cheight":9,
"timeout":3,
"message":"ok"
}
}
return {
"widgettype":"Error",
"options":{
"title":"Update Error",
"cwidth":16,
"cheight":9,
"timeout":3,
"message":"failed"
}
}
"""
data_delete_tmpl = """
ns = {
'id':params_kw['id'],
}
{% if logined_userid %}
userid = await get_user()
if not userid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userid}}'] = userid
{% endif %}
{% if logined_userorgid %}
userorgid = await get_userorgid()
if not userorgid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['{{logined_userorgid}}'] = userorgid
{% endif %}
db = DBPools()
dbname = get_module_dbname('{{modulename}}')
async with db.sqlorContext(dbname) as sor:
r = await sor.D('{{summary[0].name}}', ns)
debug('delete success');
return {
"widgettype":"Message",
"options":{
"title":"Delete Success",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"ok"
}
}
debug('Delete failed');
return {
"widgettype":"Error",
"options":{
"title":"Delete Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"failed"
}
}
"""
check_changed_tmpls = """
is_checked = params_kw.get('has_{{relation.param_field}}')
debug(f'{params_kw=}, {is_checked=}')
dbname = get_module_dbname('{{modulename}}')
if is_checked == 'true':
ns = {
"id":uuid(),
"{{relation.param_field}}":params_kw.{{relation.param_field}},
"{{relation.outter_field}}":params_kw.{{relation.outter_field}}
}
db = DBPools();
async with db.sqlorContext(dbname) as sor:
await sor.C('{{tblname}}', ns)
return {
"widgettype":"Message",
"options":{
"title":"Success",
"message":"record add success",
"timeout":2
}
}
else:
ns = {
"{{relation.param_field}}":params_kw.{{relation.param_field}},
"{{relation.outter_field}}":params_kw.{{relation.outter_field}}
}
sql = "delete from {{tblname}} where {{relation.param_field}}=" + "${" + "{{relation.param_field}}" + "}$" + " and {{relation.outter_field}}=" + "${" + "{{relation.outter_field}}" + "}$"
db = DBPools()
async with db.sqlorContext(dbname) as sor:
await sor.sqlExe(sql, ns)
return {
"widgettype":"Message",
"options":{
"title":"Success",
"message":"delete record success",
"timeout":3
}
}
"""