Modell-basierte Generatoren
Anwendungsbeosiel: Generieren neuer Inhalte, deren Struktur vorgebbar ist:
Modell-Basis
Pydantic.basemodel > sqlmodel
Model
def genDiscrD(d):
discr = {
"n": "name",
"lab": "label",
# typ = number, int, text, textarea, select, radio, color, range, url
"typ": "text",
"u": "",
"size": ["20"],
"val": 0,
"step": 1,
"min": 0,
"max": 100,
"opt": [], # Titel für radio, select
"url": "https://example.com"
}
d0=d.copy()
d0.pop("dtyp")
discr.update(d0)
return discr
fieldDefs = {
"id": (Optional[int], Field(default=None, primary_key=True)),
}
DTyp = {"str":str, "int":int,"float":float}
Modelbeschreibung in separater TOML:
config.toml
# [[db_mod]]
# n = "titel"
# dtyp = "str"
# "lab"= "Gib Deinem Ort einen Titel"
# # typ = number, int, text, textarea, select, radio, color, range, url
# typ= "text"
# u= ""
# size= ["20"]
# val= "Bach-Wiese"
# step= 1
# min= 0
# max= 100
# opt= [] # Titel für radio, select
# url= "https=//example.com"
[[db_mod]]
n = "titel"
dtyp = "str"
lab = "Gib Deinem Ort einen Titel"
typ = "text"
val = "Bach-Wiese"
[[db_mod]]
n = "tarea"
dtyp = "str"
lab= "Was ist so besonders an diesem Ort für Dich?"
typ= "textarea"
size =['90%', '400px']
val= "Bach-Wiese"
[[db_mod]]
n = "Kat"
dtyp = "int"
lab= "Ortne Deinen Ort einer Kategorie zu"
typ= "select"
size =2
val= 1
opt=['Berg', 'Tal', 'Wald', 'Park', 'Wasser', 'Fluss','See', 'Meer', 'Stadt', 'Dorf', 'Land']
Einlesen der Modell-Beschreibung:
from benedict import benedict
cfg = benedict("cfg.toml", format="toml")
Modell aus TOML und obigen Defs generieren:
from configData import cfg as CD
for el in CD["db_mod"]:
fieldDefs[el.n] = ( Annotated[DTyp[el.dtyp], Field(description=genDiscrD(el))])
VOrt = create_model(
"VOrt",
__base__=SQLModel,
__cls_kwargs__={"table": True},
**fieldDefs
)
FastApi - Frontend
Jinja-Html-Template
Modellbeschreibung wird an den Jinja2-Renderer übergeben:
Endpoint GET-Form
@app.get("/capt", response_class=HTMLResponse)
async def captureImaget2(request: Request):
menue = {
'items': [
{'capt': 'Projekt', 'link': '/project'},
{'capt': 'Kontakt', 'link': '/cap/kontakt'},
],
'title': 'Capture'}
# Form aufbereiten (model_prop_lst wird beim Programmstart 1x erzeugt)
return templates.TemplateResponse(
request=request, name="/cap_T/cap_bs_frm_12.html",
context={"request":request,"id":secrets.token_urlsafe(8), "model_prop_lst":model_prop_lst , "menue":menue, "sub":CD.cfg.srv.sub}
)
html-Template
...
{% import "./cap_T/macros6.html" as my_macros %}
<!-- <form method="post" action="{{sub}}/FoPo2" id="formPO" style="display: block;"> -->
<form id="formPO" style="display: block;">
<fieldset>
<legend>Eingaben:</legend>
{% for i in model_prop_lst %}
{% set macro_name = "input_" + i.typ %}
{{ my_macros[macro_name](i) }}<br>
{% endfor %}
<div class="d-grid mt-4">
<input id="btn-send" type="button" class="btn btn-info bi bi-send" style="display: block;"
value="Senden">
</fieldset>
</form>
...
Dabei wird der Aufbau eines "Widgets" durch die macros6.html gesteuert:
...
{% macro input_number(ipar) -%}
<label for="{{ipar.n}}" class="form-label">{{ipar.lab}}</label><br>
<div class="input-group mb-3">
<input type="{{ ipar.typ }}" id="{{ ipar.n }}" name="{{ ipar.n }}" value="{{ ipar.val|e }}" size="{{ ipar.size[0] }}" step="{{ ipar.step }}" class="form-control" required>
<span class="input-group-text">{{ ipar.u }}</span>
</div>
{%- endmacro %}
...
{% macro input_range(ipar) -%}
<label for="{{ipar.n}}" class="form-label">{{ipar.lab}}</label><br>
<div class="border border-primary rounded-2" style="width: 100%; background-color:rgb(104, 104, 104);">
<div class="input-group">
<table class="table-dark my-2" style="width: 90%;">
<tr>
<td style="text-align: right; width:10%;">{{ ipar.min }}</td>
<td><input type="range" class="form-range" id="{{ ipar.n }}" name="{{ ipar.n }}" value="{{ ipar.val|e }}"
min="{{ ipar.min }}" max="{{ ipar.max }}" step="{{ ipar.step }}" required></td>
<td style="text-align: left; width:10%;">{{ ipar.max }}</td>
</tr>
</table>
<div class="input-group-text" id="{{ ipar.n }}_v" style="width: 10%; color:blue">vv</div>
</div>
</div>
<script>
var slider = document.getElementById("{{ ipar.n }}");
var output = document.getElementById("{{ ipar.n }}_v");
output.innerHTML = slider.value;
slider.oninput = function() {
var output = document.getElementById("{{ ipar.n }}_v");
output.innerHTML = this.value;
}
</script>
{%- endmacro %}
Endpoint FormData
Das Auslesen der Daten und Zurücksschreiben in das Datenmodell:
@app.post(path="/FoPo2")
async def form_post2(request: Request, background_tasks: BackgroundTasks):
# Get the form object
form = await request.form()
vOrt = VOrt()
vOrt.sqlmodel_update(form._dict) ## DER Trick...
with Session(engine) as session:
session.add(vOrt)
session.commit()
session.refresh(vOrt)
# Story im Hintergrund generieren
background_tasks.add_task(gen_story, dict(vOrt))
return {"message": "FoPo2"}
Mit background_tasks.add_task() wird eine rechenzeitaufwendige Weiterverarbeitung der Daten asynchron angestoßen.
