feat: CRUD auto-generates validation code from alters.rules
- extract_validation_rules() extracts rules from browserfields.alters - data_new_tmpl: adds runtime validation before DB insert - data_update_tmpl: adds runtime validation before DB update - Supports: required, minlength, maxlength, min, max, pattern, email, number - Backend returns Error widget with validation messages on failure
This commit is contained in:
parent
ebd4b4a99e
commit
3f8a0ce876
@ -1,8 +1,12 @@
|
|||||||
Metadata-Version: 2.4
|
Metadata-Version: 2.1
|
||||||
Name: xls2ddl
|
Name: xls2ddl
|
||||||
Version: 1.1.3
|
Version: 1.1.3
|
||||||
Summary: a xlsx file to database ddl converter
|
Summary: a xlsx file to database ddl converter
|
||||||
|
Home-page: UNKNOWN
|
||||||
Author: "yu moqing"
|
Author: "yu moqing"
|
||||||
Author-email: "yumoqing@gmail.com"
|
Author-email: "yumoqing@gmail.com"
|
||||||
License: "MIT"
|
License: "MIT"
|
||||||
Requires-Dist: apppublic
|
Platform: UNKNOWN
|
||||||
|
|
||||||
|
UNKNOWN
|
||||||
|
|
||||||
|
|||||||
@ -3,3 +3,4 @@ json2ddl = xls2ddl.json2ddl:main
|
|||||||
json2xlsx = xls2ddl.json2xlsx:main
|
json2xlsx = xls2ddl.json2xlsx:main
|
||||||
xls2ddl = xls2ddl.xls2ddl:main
|
xls2ddl = xls2ddl.xls2ddl:main
|
||||||
xls2ui = xls2ddl.xls2ui:main
|
xls2ui = xls2ddl.xls2ui:main
|
||||||
|
|
||||||
|
|||||||
BIN
xls2ddl/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/json2ddl.cpython-310.pyc
Normal file
BIN
xls2ddl/__pycache__/json2ddl.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/json2ddl.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/json2ddl.cpython-312.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/singletree.cpython-310.pyc
Normal file
BIN
xls2ddl/__pycache__/singletree.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/singletree.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/singletree.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
xls2ddl/__pycache__/tmpls.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/tmpls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/xls2crud.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/xls2crud.cpython-312.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/xls2ddl.cpython-310.pyc
Normal file
BIN
xls2ddl/__pycache__/xls2ddl.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/xls2ui.cpython-310.pyc
Normal file
BIN
xls2ddl/__pycache__/xls2ui.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xls2ddl/__pycache__/xlsxData.cpython-312.pyc
Normal file
BIN
xls2ddl/__pycache__/xlsxData.cpython-312.pyc
Normal file
Binary file not shown.
@ -244,6 +244,54 @@ if not userorgid:
|
|||||||
}
|
}
|
||||||
ns['{{logined_userorgid}}'] = userorgid
|
ns['{{logined_userorgid}}'] = userorgid
|
||||||
{% endif %}
|
{% 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()
|
db = DBPools()
|
||||||
dbname = get_module_dbname('{{modulename}}')
|
dbname = get_module_dbname('{{modulename}}')
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
@ -309,6 +357,54 @@ ns['{{logined_userorgid}}'] = userorgid
|
|||||||
if params_kw.get('{{f}}'):
|
if params_kw.get('{{f}}'):
|
||||||
ns['{{f}}'] = password_encode(params_kw.get('{{f}}'))
|
ns['{{f}}'] = password_encode(params_kw.get('{{f}}'))
|
||||||
{% endfor %}
|
{% 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()
|
db = DBPools()
|
||||||
dbname = get_module_dbname('{{modulename}}')
|
dbname = get_module_dbname('{{modulename}}')
|
||||||
|
|||||||
@ -263,9 +263,23 @@ def build_data_browser(pat: str, desc: dict):
|
|||||||
with codecs.open(os.path.join(pat, f'index.ui'), 'w', "utf-8") as f:
|
with codecs.open(os.path.join(pat, f'index.ui'), 'w', "utf-8") as f:
|
||||||
f.write(filter_backslash(s))
|
f.write(filter_backslash(s))
|
||||||
|
|
||||||
|
def extract_validation_rules(desc) -> str:
|
||||||
|
"""Extract validation rules from field alters. Returns JSON string or empty."""
|
||||||
|
rules = {}
|
||||||
|
alters = desc.get('browserfields', {}).get('alters', {})
|
||||||
|
if not alters:
|
||||||
|
return ''
|
||||||
|
for fname, alter in alters.items():
|
||||||
|
if 'rules' in alter and alter['rules']:
|
||||||
|
rules[fname] = alter['rules']
|
||||||
|
if not rules:
|
||||||
|
return ''
|
||||||
|
return json.dumps(rules, ensure_ascii=False)
|
||||||
|
|
||||||
def build_data_new(pat: str, desc: dict):
|
def build_data_new(pat: str, desc: dict):
|
||||||
e = MyTemplateEngine([])
|
e = MyTemplateEngine([])
|
||||||
desc = desc.copy()
|
desc = desc.copy()
|
||||||
|
desc['validation_rules'] = extract_validation_rules(desc)
|
||||||
s = e.renders(data_new_tmpl, desc)
|
s = e.renders(data_new_tmpl, desc)
|
||||||
with codecs.open(os.path.join(pat, f'add_{desc.tblname}.dspy'), 'w', "utf-8") as f:
|
with codecs.open(os.path.join(pat, f'add_{desc.tblname}.dspy'), 'w', "utf-8") as f:
|
||||||
f.write(filter_backslash(s))
|
f.write(filter_backslash(s))
|
||||||
@ -273,6 +287,7 @@ def build_data_new(pat: str, desc: dict):
|
|||||||
def build_data_update(pat: str, desc: dict):
|
def build_data_update(pat: str, desc: dict):
|
||||||
e = MyTemplateEngine([])
|
e = MyTemplateEngine([])
|
||||||
desc = desc.copy()
|
desc = desc.copy()
|
||||||
|
desc['validation_rules'] = extract_validation_rules(desc)
|
||||||
s = e.renders(data_update_tmpl, desc)
|
s = e.renders(data_update_tmpl, desc)
|
||||||
with codecs.open(os.path.join(pat, f'update_{desc.tblname}.dspy'), 'w', "utf-8") as f:
|
with codecs.open(os.path.join(pat, f'update_{desc.tblname}.dspy'), 'w', "utf-8") as f:
|
||||||
f.write(filter_backslash(s))
|
f.write(filter_backslash(s))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user