data_browser_tmpl = """
{
	"widgettype":"VBox",
	"options":{"height":"100%","width":"100%"},
	"subwidgets":[
{
	"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 %}
{% if editable and editable.binds %}
			,"binds":{{json.dumps(editable.binds, indent=4, ensure_ascii=False)}}
{% 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 %}
		"search_form":{
			"css":"card",
			"padding":"8px",
			"show_label":false,
			"submit_label":"搜索",
			"submit_css":"primary",
			"fields":{{json.dumps(filter_fields, 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 %}
{% if data_filter %}
filterjson = {{json.dumps(data_filter, ensure_ascii=False)}}
{% else %}
filterjson = None
fields_str=r'''{{json.dumps(fields, indent=4, ensure_ascii=False)}}'''
ori_fields = json.loads(fields_str)
fields = [ f['name'] for f in ori_fields ]
filterjson = default_filterjson(fields, ns)
{% endif %}
{% 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())
	_new_rows = await sor.sqlExe("SELECT * FROM {{summary[0].name}} WHERE {{summary[0].primary[0]}} = ${id}$", {'id': id})
	_user_data = _new_rows[0] if _new_rows else ns
	return {
		"widgettype":"Message",
		"options":{
			"cwidth":16,
			"cheight":9,
			"title":"Add Success",
			"timeout":3,
			"message":"ok",
			"user_data": _user_data
		}
	}

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 list(ns.items()):
	if v == 'NaN' or v == 'null' or v == '' or v is None:
		if k != 'id':
			del ns[k]
		else:
			ns[k] = None if v in ('NaN', 'null') else v
{% 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():
\tif _fname not in params_kw:
\t\tcontinue
\t_val = params_kw.get(_fname)
\tif _val is None or str(_val).strip() == '':
\t\tcontinue
\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 %}
	tblfields = {{json.dumps(fields, ensure_ascii=False)}}
	tblfname = [ f['name'] for f in tblfields ]
	other_keys = [ k for k in ns.keys() if k in tblfname and k != 'id' ]
	if len(other_keys) > 0:
		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
		}
	}
"""
