C’est un premier jet. Le code est perfectible ! (EDIT: bugfix 2025-11-04)
La récupération du template se fait aux lignes 6 et 7 :
model = Models.lookupOne(id=1)
template = model.Model
Il y aussi la possiblité de référencé une URL d’image, dans la class FindData
super().init({« Logo »: model.Logo})
Voici le code complet :
import datetime
import numbers
# Retrieve Template
model = Models.lookupOne(id=1)
template = model.Model
def get(data, field):
if isinstance(data, dict):
return data[field]
else:
return getattr(data, field, None)
# Get attribute or related attribute
def getattrpath(record, key):
if '.' in key:
table_name, field = key.split('.', 1)
related_record = get(record, table_name)
if related_record:
return getattrpath(related_record, field)
else:
return get(record, key)
return None
# Data retrieval class
class FindData(dict):
def __init__(self,record):
super().__init__({"Logo": model.Logo})
self.record = record
def __missing__(self, key):
value = getattrpath(self.record, key)
# If still no value, return the placeholder
return value if value is not None else f"{{{key}}}"
# Syntax parser
def parse(context, str):
waitelse = False
ifcond = False
out=""
while str:
beg = str.find("{#")
if beg>-1:
if context:
out = out + str[:beg].format_map(FindData(context))
orig = str[beg:]
str = str[beg+2:]
end = str.find("#}")
expr = str[:end]
tokens = expr.split(" ")
str = str[end+2:]
match tokens[0]:
case "if":
ifcond = parse_expression(context, expr[3:])
(do, str) = parse(context if ifcond else None, str)
if ifcond:
out = out + do
waitelse = True
case "else":
if waitelse:
waitelse = False
(do, str) = parse(context if not ifcond else None, str)
if not ifcond:
out = out + do
else:
return out, orig
case "each":
if context:
coll = get(context, tokens[1])
if len(tokens) > 2:
if tokens[2] == "sortby":
for key in (tokens[3::])[::-1]:
reverse = key[0] == "-"
if reverse:
key = key[1::]
key = key.format_map(FindData(context))
if key != "":
coll = sorted(coll, reverse=reverse, key=lambda data: getattrpath(data, key))
else:
out = out + "## EACH SYNTAX ERROR ##"
if len(coll):
for item in coll:
(data, nextstr) = parse(item, str)
out = out + data
else:
(_, nextstr) = parse(None, str)
else:
(_, nextstr) = parse(None, str)
str = nextstr
case "end":
return out, str
elif context:
return out + str.format_map(FindData(context)), ""
return out, ""
# Expression parser
def parse_expression(context, str):
tokens = list(map(lambda tok: parse_value(context, tok), str.split(" ")))
match tokens:
case [val]:
try:
len(val)
except:
return val
else:
return len(val)>1
case [v1, "==", v2]:
return v1 == v2
case [v1, "!=", v2]:
return v1 != v2
case [v1, ">", v2]:
return v1 > v2
case [v1, ">=", v2]:
return v1 >= v2
case [v1, "<", v2]:
return v1 < v2
case [v1, "<=", v2]:
return v1 <= v2
case _:
raise NameError("Syntax Error")
def parse_value(context, str):
end = len(str)-1
if str[0] == '{' and str[end]=='}':
return getattrpath(context, str[1:end])
if str[0] == '"' and str[end]=='"':
return str[1:end]
match str:
case "True":
return True
case "False":
return False
case "None":
return None
case _:
try:
return float(str)
except:
return str
# Parse
(tree, _) = parse(rec, template)
return tree