Zuerst die Zeilen in ein eigenes Template templates/_zeilen.html auslagern (der Unterstrich kennzeichnet ein Teil-Template):
html
{% for k in kunden %}
<tr>
<td>{{ k.name }}</td>
<td>{{ k.stadt or '-' }}</td>
<td class="zahl">{{ k.umsatz|euro }}</td>
<td>
<form method="post" action="/kunden/{{ k.id }}/loeschen">
<button type="submit" class="secondary outline">Löschen</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="4">Keine Kunden gefunden</td></tr>
{% endfor %}
In main.py die Abfrage in eine Funktion ziehen und zwei Routen daraus bedienen:
python
def lade_kunden(q):
with pool.connection() as conn:
return conn.execute(
'SELECT id, name, stadt, umsatz FROM kunden WHERE name ILIKE %s ORDER BY name',
(f'%{q}%',),
).fetchall()
@app.get('/', response_class=HTMLResponse)
def kundenliste(request: Request, q: str = ''):
return templates.TemplateResponse(request, 'kunden.html', {'kunden': lade_kunden(q), 'q': q})
@app.get('/tabelle', response_class=HTMLResponse)
def tabelle(request: Request, q: str = ''):
# liefert nur die Tabellenzeilen, für htmx
return templates.TemplateResponse(request, '_zeilen.html', {'kunden': lade_kunden(q)})
In kunden.html steht das Suchfeld mit den hx--Attributen (siehe oben) und die Tabelle mit {% include '_zeilen.html' %}. Das Suchformular braucht keinen Absenden-Knopf mehr.
Was passiert:
- Beim Tippen schickt htmx nach 300 ms Pause
GET /tabelle?q=...(der Feldnameqwird automatisch als Parameter angehängt). Die Antwort sind nur die<tr>-Zeilen, die in#tabelleeingesetzt werden. Die restliche Seite bleibt unverändert. - Dieselbe Vorlage
_zeilen.htmlwird zweimal verwendet: bei der Gesamtseite und bei der Teilantwort. So gibt es genau eine Stelle für das Aussehen der Zeilen. - Der Filter
eurosorgt für einheitliche Zahlen, und die CSS-Klassezahlrichtet sie rechts aus. - Pico gestaltet das alles ohne weitere Klassen. Es sind nur wenige Attribute nötig, weil Pico normales HTML direkt formatiert.
Warum so: Der Server liefert HTML-Bruchstücke, der Browser setzt sie ein: Du brauchst keine zweite Sprache und kein Frontend-Framework, und alle Logik bleibt in Python. Für Verwaltungsoberflächen, Tabellen und Formulare reicht dieser Ansatz sehr weit.