Aussehen
Eine Webseite mit Python đđ â
Bisher liefen deine Programme auf deinem Rechner, und nur du hast etwas davon gesehen. Eine Webseite dreht das um: Das Programm lĂ€uft auf einem Server, und wer die Adresse aufruft, bekommt eine Antwort â auf jedem GerĂ€t, ohne etwas zu installieren.
Das Bemerkenswerte daran ist, wie wenig dafĂŒr nötig ist.
Du baust hier eine einzige Seite, und zwar in drei Schritten: Erst zeigt sie ein Bild, dann kann man es liken, und zum Schluss behĂ€lt sie die Likes auch ĂŒber einen Neustart hinweg. Jeder Schritt ist fĂŒr sich klein â zusammen sind sie alles, was eine Webanwendung ausmacht.
Vorher: die Aufteilung â
Wenn Daten, Aussehen und Ablauf in einem Programm durcheinandergehen, wird jede Ănderung zur Suche. Deshalb teilt man Webprogramme fast immer in drei Teile.
Model-View-Controller
Model â die Daten und alles, was mit ihnen erlaubt ist. Meist eine Datenbank.
View â die Darstellung. Bei einer Webseite eine HTML-Vorlage mit Platzhaltern.
Controller â die Steuerung. Nimmt eine Anfrage entgegen, holt beim Model die passenden Daten und ĂŒbergibt sie an die View.
Die drei kennen voneinander nur, was sie voneinander brauchen. Deshalb kannst du das Aussehen Ă€ndern, ohne die Daten anzufassen â und umgekehrt.
Das Modell entwirfst du am besten zuerst auf Papier, als Entity-Relationship-Modell, bevor du eine Zeile schreibst. Wie das geht, steht im Kapitel Datenmanagement.
Schritt 1: Anzeigen â
Als Webserver nimmst du Bottle â ein Modul, das aus einer einzigen Datei besteht und genau das kann, was du jetzt brauchst.
pip install bottlepython
from bottle import route, run, template, static_file
@route("/")
def index():
return template("index")
@route("/static/<dateiname>")
def statisch(dateiname):
return static_file(dateiname, root = "./static/")
run(host = "localhost", port = 8080)Das ist der Controller, und er ist zehn Zeilen lang.
@route("/") darĂŒber ist ein Decorator: Er verĂ€ndert, wie eine Funktion benutzt wird. Hier sagt er âruf diese Funktion auf, wenn jemand diese Adresse anfragt" â aufgerufen wird sie also nicht von dir, sondern von Bottle.
Die zweite Route hat einen Platzhalter: /static/<dateiname>. Fragt der Browser /static/welpe.webp an, landet âwelpe.webp" im Parameter dateiname, und static_file schickt genau diese Datei aus dem Ordner static zurĂŒck.
Diese zweite Route ist keine Nebensache. Ein Bottle-Programm antwortet nur auf Adressen, fĂŒr die es eine Route gibt â ohne sie bekommt der Browser fĂŒr jedes Bild und jede CSS-Datei ein â404 Not Found", und die Seite bleibt leer. Der Ordner static ist die Ausnahme, die du ausdrĂŒcklich erlaubst.

Test
Starte das Programm und öffne http://localhost:8080 im Browser. Solange es keine Vorlage gibt, gibt es eine Fehlermeldung â die kommt im nĂ€chsten Schritt.
Beenden kannst du den Server im Editor mit Stopp.
Die Vorlage â
Eine View ist eine HTML-Datei mit ein paar Python-Stellen darin. Bei Bottle liegt sie in views/index.tpl:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Welpe des Tages</title>
<link rel="stylesheet" href="/static/pico.classless.min.css">
</head>
<body>
<main>
<h1>Welpe des Tages</h1>
<img src="/static/welpe.webp" alt="Ein schlafender Welpe">
</main>
</body>
</html>Beide Adressen im HTML â das Bild und die CSS-Datei â beginnen mit /static/. Sie laufen also ĂŒber die zweite Route von eben.
Zwei Schreibweisen musst du gleich unterscheiden: Zeilen mit % davor sind Python â Entscheidungen und Schleifen. {{...}} setzt einen Wert an dieser Stelle ein. Noch kommt beides nicht vor, weil es nichts einzusetzen gibt. Das Ă€ndert sich im nĂ€chsten Schritt.
Mehr braucht eine Vorlagensprache nicht. Und mehr sollte auch nicht drinstehen: Gerechnet wird im Controller, die View setzt nur ein.
Die Seite bauen
- Leg die Ordner
viewsundstatican, kopier das Bild hinein und bring das Beispiel zum Laufen. - Lad dir Pico CSS herunter und leg
pico.classless.min.cssebenfalls nachstatic. - Benenn das Bild um und schau dir an, was der Browser dann anzeigt.
Schritt 2: VerĂ€ndern â
Anschauen kann man die Seite jetzt. Interessant wird sie erst, wenn man etwas tun kann â also ein Knopf, der den Welpen liked, und eine Zahl, die dabei hochgeht.
DafĂŒr muss sich der Server etwas merken. Drei Möglichkeiten gĂ€be es: eine Variable im Server, ein Wert in der Adresszeile, oder eine Session. Wir nehmen die Variable im Server, weil sie ohne jedes zusĂ€tzliche Modul auskommt und weil ein Like-ZĂ€hler ohnehin fĂŒr alle Besucher:innen derselbe sein soll â in der Adresszeile oder in einer Session wĂŒrde jede Person ihren eigenen ZĂ€hler bekommen.
python
from bottle import route, run, template, static_file, redirect
likes = 0
@route("/")
def index():
return template("index", likes = likes)
@route("/like")
def like():
global likes
likes = likes + 1
redirect("/")
@route("/static/<dateiname>")
def statisch(dateiname):
return static_file(dateiname, root = "./static/")
run(host = "localhost", port = 8080)Drei Stellen sind neu.
template("index", likes = likes). Alles, was du hier zusĂ€tzlich angibst, steht in der Vorlage als Variable bereit. So â und nur so â kommen Daten aus dem Controller in die View.
global likes. Ohne diese Zeile wĂŒrde Python beim likes = likes + 1 eine neue Variable innerhalb der Funktion anlegen, und drauĂen bliebe der ZĂ€hler auf null. Denselben Grund gab es schon beim Spiel.
redirect("/"). Nach dem ZĂ€hlen schickt der Server den Browser zurĂŒck auf die Startseite. Sonst bliebe /like in der Adresszeile stehen, und ein Neuladen wĂŒrde noch einmal zĂ€hlen.
In die Vorlage kommt unter das Bild:
html
<p>{{likes}} mal geliked</p>
<a href="/like" role="button">â€ïž GefĂ€llt mir</a>Der Knopf ist in Wahrheit ein Link. Er fĂŒhrt auf /like, dort wird gezĂ€hlt, und von dort geht es sofort zurĂŒck. role="button" sorgt dafĂŒr, dass Pico CSS ihn wie einen Knopf aussehen lĂ€sst.
Beim Neustart ist alles weg
Klick ein paar Likes zusammen, stopp den Server und starte ihn neu. Der ZĂ€hler steht wieder auf null.
Das ist kein Fehler, sondern die Natur einer Variablen: Sie lebt im Arbeitsspeicher, und der gehört zum laufenden Programm. Endet das Programm, endet die Variable. Genauso verschwindet der ZÀhler, wenn du am Code etwas Ànderst und der Server dabei neu startet.
FĂŒr alles, was lĂ€nger halten soll als ein Programmlauf, brauchst du einen Ort auĂerhalb des Programms. Das ist der ganze Sinn einer Datenbank.
Schritt 3: Mit Datenbank â
Bis hier hatte deine Seite kein Model. Jetzt bekommt sie eines â und der ZĂ€hler ĂŒberlebt den Neustart.
SQLite ist dafĂŒr ideal, weil eine Datenbank aus einer einzigen Datei besteht und das Modul sqlite3 in Python schon eingebaut ist. Statt einer Zahl speicherst du jedes einzelne Like als eigene Zeile mit seinem Zeitpunkt. ZĂ€hlen kann die Datenbank selbst â und du weiĂt hinterher auĂerdem, wann geliked wurde.
python
from bottle import route, run, template, static_file, redirect
import sqlite3
import datetime
db = sqlite3.connect("likes.db")
db.execute("CREATE TABLE IF NOT EXISTS likes (id INTEGER PRIMARY KEY, zeitpunkt TEXT)")
db.commit()
@route("/")
def index():
anzahl = db.execute("SELECT COUNT(*) FROM likes").fetchone()[0]
return template("index", likes = anzahl)
@route("/like")
def like():
now = datetime.datetime.now().isoformat()
db.execute("INSERT INTO likes (zeitpunkt) VALUES (?)", (now,))
db.commit()
redirect("/")
@route("/static/<dateiname>")
def statisch(dateiname):
return static_file(dateiname, root = "./static/")
run(host = "localhost", port = 8080)Die Vorlage bleibt Zeile fĂŒr Zeile dieselbe. Sie bekommt weiterhin eine Zahl namens likes und setzt sie ein â woher die kommt, geht sie nichts an. Genau dafĂŒr ist die Aufteilung in Model, View und Controller da.
IF NOT EXISTS heiĂt: Die Tabelle entsteht beim allerersten Start und wird danach in Ruhe gelassen. commit schreibt eine Ănderung endgĂŒltig in die Datei; ohne diese Zeile passiert nichts Dauerhaftes. Und fetchone()[0] holt aus der Antwort die erste Zeile und daraus den ersten Wert â also die gezĂ€hlte Anzahl.
Das Fragezeichen in der INSERT-Zeile ist kein Schönheitsfehler, sondern wichtig: Werte gehören nie per Textverkettung in eine Abfrage. Sonst kann jemand ĂŒber ein Eingabefeld eigene SQL-Befehle einschleusen. Warum das so gefĂ€hrlich ist, steht im Datenmanagement-Kapitel.
Die fertige Seite herunterladen
Hier ist der ganze Ordner, so wie er am Ende von Schritt 3 aussieht â mit Programm, Vorlage, Bild, Pico CSS und einer Kurzanleitung.
LĂ€uft bei dir schon etwas anderes auf Port 8080 â XAMPP und phpMyAdmin tun das gern â, zeigt der Browser deren Seite statt deiner. Ăndere dann in der letzten Zeile den Port, etwa auf 8081, und ruf http://localhost:8081 auf.
Den ZĂ€hler ausbauen
- Like den Welpen ein paar Mal, stopp den Server und starte ihn neu. Diesmal bleibt die Zahl.
- Zeig zusĂ€tzlich an, wann das letzte Like war â
SELECT zeitpunkt FROM likes ORDER BY id DESC. - Bau einen zweiten Knopf, der das jĂŒngste Like wieder zurĂŒcknimmt.
- Zeig mehrere Bilder, jedes mit eigenem ZĂ€hler. DafĂŒr braucht die Tabelle eine Spalte mehr.
Das Aussehen â
Mit CSS kann man beliebig viel Zeit verlieren. Damit das nicht passiert, nimm ein fertiges GrundgerĂŒst â hier in steigender Schwierigkeit:
| Was es tut | ||
|---|---|---|
| Pico CSS | Sieht sofort gut aus, ohne dass du eine einzige Klasse schreibst | đ |
| Bootstrap | Fertige Bausteine fĂŒr Spalten, Knöpfe, Formulare | đđ |
| Tailwind | Volle Kontrolle, dafĂŒr steht das ganze Aussehen im HTML | đđđ |
Denk beim Entwerfen zuerst an das Smartphone: alle wichtigen Elemente untereinander, in sinnvoller Reihenfolge. Auf einem breiten Bildschirm daraus etwas zu machen ist leicht. Umgekehrt ist es Arbeit.
Deine eigene Seite
Der Welpe war ein Vorwand. Was du jetzt kannst â anzeigen, verĂ€ndern, behalten â reicht fĂŒr alles, was sich etwas merken soll: eine Umfrage, ein Ausleihbuch, die Buchempfehlungen der Klasse, eine Sammlung von Lieblingsorten.
Zeichne zuerst das ERM auf Papier. Erst danach fang an zu programmieren.
Weitere Ideen â
| Projekt | |
|---|---|
| Umfrage mit Auswertung als Diagramm | đđ |
| Ausleihliste fĂŒr die Fachschaft | đđ |
| Deine beste.schule-Noten als eigene Seite darstellen | đđđ |
| Interaktive Seite ohne Neuladen, mit htmx | đđđ |
Die vollstÀndige Anleitung zu Bottle:
https://bottlepy.org/docs/dev/