Zum Inhalt

Modell-basierte Generatoren

Anwendungsbeosiel: Generieren neuer Inhalte, deren Struktur vorgebbar ist:

model1

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.