Phase 4: Oberfläche mit Galerie, Erfassung und Reservierung
Jinja2 mit HTMX, Tailwind über die Standalone-Binärdatei im Docker-Build - kein Node.js, kein npm. Mobile-First, Tippflächen mindestens 44 px hoch. Seiten: Galerie mit Filterleiste, Detailseite, Erfassungsformular, Stapel-Import mit Entwurfsliste, Anmeldung. HTMX tauscht beim Filtern nur die Liste aus statt die ganze Seite neu zu laden. Aus den Entscheidungen im Plan folgt konkret: - Der Stapel-Import steht ZUERST im Formular: wer zwanzig Teile hat, will nicht zwanzigmal ein Formular ausfüllen. Bewusst ohne capture-Attribut, damit sich wahlweise fotografieren oder aus der Mediathek wählen lässt - mit capture ginge nur je ein Foto pro Vorgang. - Grösse, Kategorie, Geschlecht, Saison und Zustand werden vom zuletzt gespeicherten Teil übernommen. Beim Abarbeiten einer Kiste sind das fünf Felder weniger pro Stück. - Nach dem Nachtragen eines Entwurfs geht es direkt zum nächsten, nicht auf die Detailseite - das ist der eigentliche Arbeitsablauf. - Titel bleibt leer erlaubt; angezeigt wird dann Kategorie und Grösse. - reserved_by erscheint öffentlich nur als "reserviert". Den Namen sieht ausschliesslich der angemeldete Betreiber. - Nach dem Reservieren wird der Freigabe-Link angezeigt, als vollständige Adresse zum Aufbewahren. - Post/Redirect/Get überall, damit ein Neuladen nicht erneut abschickt. - CSRF-Token in jedem Formular, zusätzlich zu SameSite=Lax. HTMX liegt lokal im Repository, nicht vom CDN: die CSP erlaubt nur script-src 'self', und ein CDN-Aufruf gäbe die IP jedes Besuchers weiter - dieselbe Überlegung wie bei Swagger und bei den Schriften der Kantone-App. Ein Test prüft für alle Seiten, dass nichts von aussen geladen wird. Nebenbei TemplateResponse auf die aktuelle Signatur umgestellt (64 Deprecation-Warnungen weniger). 77 Tests, alle grün. Zusätzlich der ganze Weg gegen den Container: anmelden, drei Fotos als Stapel, Entwurf nachtragen, Werte werden für das nächste Teil übernommen, als Gast reservieren, Name bleibt verborgen, Selbst-Freigabe über den Link. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
This commit is contained in:
+2
-28
@@ -19,7 +19,7 @@ from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from app.config import einstellungen
|
||||
from app.database import SessionLocal, engine
|
||||
from app.routers import auth, categories, images, items
|
||||
from app.routers import auth, categories, images, items, pages
|
||||
from app.security import limiter
|
||||
from app.seed import stammdaten_anlegen
|
||||
|
||||
@@ -120,6 +120,7 @@ app.include_router(auth.router)
|
||||
app.include_router(categories.router)
|
||||
app.include_router(items.router)
|
||||
app.include_router(images.router)
|
||||
app.include_router(pages.router)
|
||||
|
||||
|
||||
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
|
||||
@@ -144,33 +145,6 @@ def swagger_ui():
|
||||
)
|
||||
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
def startseite():
|
||||
"""Platzhalter, bis die Galerie steht (Phase 4).
|
||||
|
||||
Ohne diese Route liefert die Wurzel ein nacktes 404-JSON - im Browser
|
||||
sieht das aus wie eine kaputte Anwendung.
|
||||
"""
|
||||
return HTMLResponse(
|
||||
"""<!doctype html>
|
||||
<html lang="de-CH"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>Kinderkleider-Börse</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; max-width: 34rem; margin: 4rem auto;
|
||||
padding: 0 1rem; line-height: 1.6; color: #1f2937; }
|
||||
code { background: #f3f4f6; padding: .1rem .35rem; border-radius: .25rem; }
|
||||
</style></head><body>
|
||||
<h1>Kinderkleider-Börse</h1>
|
||||
<p>Die Anwendung läuft. Die Oberfläche entsteht noch – bis dahin lässt sich
|
||||
alles über die <a href="/docs">API-Oberfläche</a> ausprobieren.</p>
|
||||
<p>Zum Erfassen zuerst <code>POST /api/v1/auth/login</code> ausführen.
|
||||
Ohne Anmeldung sind Galerie und Reservierung nutzbar.</p>
|
||||
</body></html>"""
|
||||
)
|
||||
|
||||
|
||||
@app.get("/robots.txt", include_in_schema=False)
|
||||
def robots():
|
||||
return PlainTextResponse("User-agent: *\nDisallow: /\n")
|
||||
|
||||
Reference in New Issue
Block a user