Commit e704902b authored by Axel Dürkop's avatar Axel Dürkop
Browse files

Add Flask section

parent ea8e400e
Pipeline #93327 passed with stages
in 43 seconds
+++
title= "Webanwendungsentwicklung mit Flask"
date= 2019-04-02T12:50:13+01:00
draft= false
weight = 200
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = false
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = "Flask"
# The title of the page in menu will be prefixed by this HTML content
pre= ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter= false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
Flask ist ein sogenanntes Webframework. Es erleichtert die Entwicklung dynamischer Webseiten.
\ No newline at end of file
+++
title= "Aus statisch mach dynamisch"
date= 2019-04-02T12:50:13+01:00
draft= false
weight = 100
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = false
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = "Statisch vs. dynamisch"
# The title of the page in menu will be prefixed by this HTML content
pre= ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter= false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
Der folgende Abschnitt hat das Ziel, den Mehrwert dynamischer Webseiten herauszusarbeiten. Im vorangegangenen Beispiel wurde schon deutlich, dass übermittelte Parameter den "Bau" der Website, die an den Browser ausgeliefert wird, beeinflussen können.
Im folgenden Abschnitt werden wir die Ausgangsseite "Secondhandblumen Petersen" zu einer dynamischen Website umbauen.
## Vorbereitung
### Projektstruktur auf dem RPI anlegen
Wir werden zunächst eine neue Struktur für diesen Arbeitsschritt auf dem Raspberry vorbereiten.
```bash
/home/pi/www/secondhandblumen_flask/
├── app.py
└── templates/
```
Die Datei `app.py` kann mit `touch app.py` angelegt werden, der Ordner mit `mkdir templates`. Er dient dazu, die HTML-"Bauteile" aufzunehmen, aus denen wir im Folgenden dynamisch unsere Website zusammenbauen.
### Statische HTML-Dokumente auf den RPI kopieren
Zunächst brauchen wir die HTML-Dokumente aus dem GitLab-Repository https://xldrkp@collaborating.tuhh.de/itbh/secondhandblumen.git. Sie können direkt mit `git clone` in den Ordner `templates` gezogen werden.
```bash
pi@raspberrypi:~/www/secondhandblumen_flask/templates $ cd
pi@raspberrypi:~ $ cd www/secondhandblumen_flask/templates/
pi@raspberrypi:~/www/secondhandblumen_flask/templates $ git clone https://xldrkp@collaborating.tuhh.de/itbh/secondhandblumen.git .
```
Wichtig ist der Punkt am Ende, denn er sagt, dass die Dateien direkt in dem Verzeichnis abgelegt werden, in dem wir uns befinden.
## Die Datei `app.py` aufbauen
Bei Flask heißt der Vorgang, HTML-Seiten dynamisch zusammen zu bauen *rendern*. Daher importieren wir am Anfang unserer Hauptdatei auch ein neues Modul.
```Python
#!/usr/bin/env python3
from flask import Flask
from flask import request, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/angebot')
def angebot():
return render_template('angebot.html')
@app.route('/team')
def team():
return render_template('team.html')
if __name__ =='__main__':
app.run(host="0.0.0.0", debug=True)
```
In der zweiten Zeile kommt `render_template` dazu. Es erlaubt uns, mit `return` eine Datei zurückzugeben, die das HTML enthält. So können wir HTML und Programmcode sauber voneinander trennen.
## Zwischenstand checken
An dieser Stelle starten wir mal den Server, um uns das bisherige Ergebnis im Browser anzusehen.
```bash
pi@raspberrypi:~/www/secondhandblumen_flask $ python3 app.py
```
Ein Aufruf der Seite im Browser mit http://[IP-Adresse des Pi]:5000/ zeigt folgende Ansicht
![CSS und Bilder fehlen!](home-ohne-css-und-bild.png)
## Woran liegt's?
Offensichtlich kann unsere derzeitige Anwendung noch nicht das CSS sowie die Bilder anzeigen. Gehen wir der Sache auf den Grund.
![404 für Bilder und CSS](404-fuer-CSS-und-Bilder.png)
Ein Blick in die Entwicklertools von Chromium zeigen, dass die Dateien `style.css` und das Bild aus `img/` nicht geladen werden können: Der Server findet sie dort nicht.
Das Problem ist erklärbar, wenn man weiß, dass man für Flask **statische Dateien** woanders ablegen muss.
## Statische Dateien für Flask verfügbar machen
Dem Dateibaum unseres Projekts fügen wir noch den Ordner `static` hinzu und darin zwei weitere Ordner `css` und `img`.
```bash
pi@raspberrypi:~/www/secondhandblumen_flask $ tree
.
├── app.py
├── static
│   ├── css
│   └── img
└── templates
├── angebot.html
├── img
│   ├── background_.jpg
│   ├── man.jpg
│   ├── photocredits.txt
│   ├── stormtrooper.jpg
│   └── withered_flowers.jpg
├── impressum.html
├── index.html
├── kontakt.html
├── LICENSE
├── style.css
└── team.html
5 directories, 13 files
```
Nun verschieben wir die entsprechenden Dateien aus `templates` an die richtigen Stellen.
### CSS-Datei verschieben
```bash
pi@raspberrypi:~/www/secondhandblumen_flask $ mv templates/style.css static/css/
```
### Bilder verschieben
```bash
pi@raspberrypi:~/www/secondhandblumen_flask $ mv templates/img/* static/img/
```
Anschließend sieht unser Dateibaum wie folgt aus:
```bash
pi@raspberrypi:~/www/secondhandblumen_flask $ tree
.
├── app.py
├── static
│   ├── css
│   │   └── style.css
│   └── img
│   ├── background_.jpg
│   ├── man.jpg
│   ├── photocredits.txt
│   ├── stormtrooper.jpg
│   └── withered_flowers.jpg
└── templates
├── angebot.html
├── img
├── impressum.html
├── index.html
├── kontakt.html
├── LICENSE
└── team.html
5 directories, 13 files
```
## Pfadangaben für CSS und Bilder anpassen
Flask weiß, wo sich die statischen Dateien befinden. In den HTML-Dateien ist dies aber noch nicht bekannt. Daher nehmen wir jetzt zwei Änderungen vor.
```html
<!doctype html>
<html lang="de">
<head>
<title>Home | Secondhandblumen Petersen in Hamburg</title>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"/>
</head>
<body>
<h1><a href="index.html">Secondhandblumen Petersen</a></h1>
<div id="wrapper">
<div id="container">
<div id="navcontainer">
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="angebot.html">Angebot</a></li>
<li><a href="team.html">Team</a></li>
<li><a href="kontakt.html">Kontakt</a></li>
<li><a href="impressum.html">Impressum</a></li>
</ul>
</div>
<h2>Herzlich Willkommen!</h2>
<img class="align-left" src="{{ url_for('static', filename='img/withered_flowers.jpg') }}" alt="As Beauty Withers by bogenfreund@flickr.com"
title="As Beauty Withers by bogenfreund@flickr.com"/>
```
In der Zeile, die das CSS einbindet, wird der Pfad auf eine neue und besondere Art angegeben.
```html
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"/>
```
Die beiden geschweiften Klammern `{{ }}` sind ein Ausgabebefehl. Wir werden später noch sehr viel mehr damit anfangen. An dieser Stelle kontruieren wir einen korrekten Pfad zur CSS-Datei, damit diese auch gefunden wird.
Das gleiche tun wir für das Bild in der Datei `index.html`.
```html
<img class="align-left" src="{{ url_for('static', filename='img/withered_flowers.jpg') }}" alt="As Beauty Withers by bogenfreund@flickr.com"
title="As Beauty Withers by bogenfreund@flickr.com"/>
```
## Erneut das Ergebnis betrachten
Bei gestartetem Server kann man nun erneut das Ergebnis im Browser betrachten.
![Homepage mit CSS und Bild](homepage-mit-flask.png)
## Wie geht's weiter?
Das Potenzial unserer doch recht aufwändigen Umbauaktion ist bei Weitem noch nicht ausgeschöpft. Außerdem haben wir noch nicht alle Probleme gelöst.
### Was passiert, wenn wir in der jetztigen Website navigieren?
Das funktioniert noch nicht. Was allerdings funktioniert - jedenfalls ein bisschen - ist die manuelle Eingabe der URLs im Browser: http://localhost:5000/team zeigt die Team-Seite an, aber schon wieder ohne CSS und ohne Bilder.
Was muss man nun machen, damit wenigstens hier dass CSS sowie die Bilder korrekt angezeigt werden?
+++
title= "Das Template-System von Flask"
date= 2019-04-02T12:50:13+01:00
draft= false
weight = 100
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = false
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = "Template-System"
# The title of the page in menu will be prefixed by this HTML content
pre= ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter= false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
Flask hat ein sehr flexibles und stabiles Konstrukt eingebaut, mit dem sich HTML-Ansichten aus Einzelteilen zusammensetzen lassen. **Jinja2** heißt es und arbeitet mit dem Begriff *Template*. Ein Template ist ein Stück HTML, das mit anderen Stücken kombiniert werden kann. Dabei gibt es einige praktische Konzepte, die die Arbeit noch erleichtern:
- Templates können durch andere Templates erweitert werden (*extend*).
- Templates können Platzhalter enthalten, die dynamische mit Werten aus den Route-Funktionen gefüllt werden können.
- Jinja2 stattet Templates mit Kontrollstrukturen aus - was HTML nicht kann -, sodass Schleifen und Verzweigungen möglich sind.
Eine detaillierte Dokumentation von Jinja2 findet sich auf der [Homepage des Projekts](https://jinja.palletsprojects.com/en/2.10.x/).
## Umbau des Secondhandblumenladens
Bisher liefern wir ganze HTML-Dokumente als Templates aus.
```python
@app.route('/')
def index():
return render_template('index.html')
```
Wir stellen aber fest, dass diese Dokumente zu großen Teilen identisch sind, sich aber an einigen Stellen unterscheiden. Diese Stellen gilt es zu identifizieren.
Aus der Datei `index.html` machen wir daher **durch Kopieren** die Datei `layout.html` und ersetzen in der Kopie den inhaltlichen Teil durch einen Block:
```html
<!doctype html>
<html lang="de">
<head>
<title>Home | Secondhandblumen Petersen in Hamburg</title>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"/>
</head>
<body>
<h1><a href="/">Secondhandblumen Petersen</a></h1>
<div id="wrapper">
<div id="container">
<div id="navcontainer">
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="angebot.html">Angebot</a></li>
<li><a href="team.html">Team</a></li>
<li><a href="kontakt.html">Kontakt</a></li>
<li><a href="impressum.html">Impressum</a></li>
</ul>
</div>
{% block content %}{% endblock content %}
<div class="clearer"></div>
</div>
</div>
</body>
</html>
```
An der Stelle, wo wir den Block definiert haben, soll in Zukunft immer nur der Teil "injiziert" werden, der für die entsprechende Seite gültig ist. Um ein Beispiel zu geben, bauen wir daher die Seite `index.html` um:
```html
{% extends "layout.html" %}
{% block content %}
<h2>Herzlich Willkommen!</h2>
<img class="align-left" src="../static/img/withered_flowers.jpg" alt="As Beauty Withers by bogenfreund@flickr.com"
title="As Beauty Withers by bogenfreund@flickr.com"/>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum
sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
{% endblock %}
```
In der ersten Zeile sagen wir, dass dieses Template das Template `layout.html` erweitert. Und an die Stelle, wo wir in der `layout.html` den Block `content` vorgesehen haben, wird der Teil eingefügt, der hier zwischen den *statement tags* für den Block `content` steht. Flask baut dann beide Teile zusammen und liefert ein komplettes Dokument aus. In der `app.py` brauchen wir nichts mehr zu ändern.
## Werte ans Template übergeben
Spannend wird es nun, wenn wir Werte aus den Route-Funktionen an die Templates übergeben. Denn dann können wir mit Python Daten berechnen, aus einer Datenbank holen, von Sensoren einlesen etc. und dynamische Webseiten generieren!
Mit einem einfachen Beispiel wollen wir uns dieses Potenzial erschließen. In der `app.py` übergeben wir den Namen der Ansicht an das Templates, um es dort anschließend dynamisch einzubauen:
```Python
@app.route('/')
def index():
return render_template('index.html', title='Home')
```
`title` ist hierbei der Name einer Variablen, die wir in den Templates zur Verfügung haben. Daher können wir unsere `layout.html` im `<head>` entsprechend modifizieren.
```html
<head>
<title>{{ title }} | Secondhandblumen Petersen in Hamburg</title>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"/>
</head>
```
## Aufgaben und Fragestellungen
1. Bauen Sie die gesamte Website nach diesem Ansatz um!
1. Überlegen Sie, wie man eine Bildergalerie aus einem Ordner voller Bilder bauen könnte!
1. Überlegen Sie, wie man eine Tabelle mit Produkten und Preisen aus einer Python-List oder einem [Dictionary](http://www.python-kurs.eu/python3_dictionaries.php) generieren kann!
1. Bauen Sie Ihr Webseiten-Projekt so um, dass es mit dem Template-System von Flask läuft!
1. Gegeben ist eine Python-List in der Form `tiere = ['Hund','Katze','Maus','Igel','Giraffe']`. Übergeben Sie diese Liste aus einer Routen-Funktion an ein Template und erstellen Sie daraus eine ungeordnete HTML-Liste (`<ul><li></li></ul>`)!
+++
title= "Die Vergesslichkeit des Servers - Sessions"
date= 2019-04-02T12:50:13+01:00
draft= false
weight = 100
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = false
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = "Sessions"
# The title of the page in menu will be prefixed by this HTML content
pre= ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter= false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
Die Kommunikation über HTTP hat einen entscheidenden Nachteil: Ohne zusätzliche Bemühungen vergisst der Webserver nach jedem Request, wer ihn da eben besucht hat. Man spricht auch von einer zustandslosen Verbindungen zwischen Client und Server (Quelle).
Dieser Missstand ist für einige Webanwendungen vollkommen unvorteilhaft. Denn wenn man weiß, dass z.B. bei einem Webshop viele Seitenwechsel notwendig sind, um Artikel in den Warenkorb zu legen, die Adresse und Zahlungsdaten anzugeben und am Ende den Kauf abzuschließen, dann muss der Server sich merken können, wer da shoppt.
Zu diesem Zweck gibt es unter anderem ein technisches Konzept, das mit dem Begriff *Sessions* überschrieben ist. Eine Session ist eine Sitzung von Client und Server, die in der Regel mit dem ersten Request beginnt und bis zum Schließen des Browserfensters dauert. Dabei schenkt der Server dem Client einen Keks, einen Session-Cookie, den man auch mit den Entwicklertools in Chrome finden kann.
Mit dem Session-Cookie identifiziert sich der Client bei jedem erneuten Request gegenüber dem Server. Der Server kann also wiedererkennen, wer da kommt und quasi eine Beziehung zum Client aufbauen. Jeder Client erhält seine eigene Session. Mit diesem Konzept lassen sich z.B. Logins bauen, Sprachschalter realisieren und Formulartunnel erstellen.
## Sessions mit Flask
Die [Dokumentation von Flask zum Thema Sessions](https://flask.palletsprojects.com/en/1.1.x/quickstart/#sessions) zeigt ein funktionierendes Beispiel, das wir zunächst besprechen wollen.
```python
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
```
Das Beispiel definiert drei Routen, die die Minimalbedingungen für eine Website mit Userlogin implementieren: Startseite, Login und Logout.
Der Aufruf der Startseite zeigt an, dass der User noch nicht eingeloggt ist. In den Entwicklertools findet sich auch kein Cookie (vgl. Abb.).
![Noch kein Cookie gesetzt](no-cookie.png)
Die manuelle Eingabe von `/login` zeigt ein minimalistisches Loginformular, das jeden beliebigen Inhalt aufnimmt. Für dieses Beispiel ist das ok, allerdings sind Formulare, die keine Validierung und Säuberung von Eingabedaten durchlaufen, extrem gefährlich für die Gesundheit des Servers!
Ein Cookie wird es gesetzt, nachdem das Formular abgeschickt wurde (vgl. Abb.).
![Ein Session-Cookie wurde gesetzt](cookie.png)
Wichtig zu bemerken ist, dass die Route `/login` die Userin nach erfolgreichem Einloggen wieder auf die Homepage schickt. Dort steht, wer man ist. Aktualisiert man die Seite, bleibt die Information stehen, obwohl der Client erneut einen Request zum Server geschickt hat. Der Server hat nicht vergessen, wer wir sind! Das Problem ist gelöst. Aber warum?
## Daten in der Session speichern
Sessions können Daten speichern. Das heißt, für jede Sitzung und damit für jeden User können individuelle Daten gespeichert werden. Der entscheidende Teil im obigen Code ist der folgende:
```python
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
```
Hier wird geschaut, ob das Formular abgeschickt wurde, was man über die verwendete HTTP-Methode feststellen kann. Wenn ja, wird der eingegebene Wert aus dem Formular gespeichert. Dabei wird der Session-Speicherplatz in `username` mit dem Wert in `username` aus dem Formular belegt. Flask verwaltet Sessiondaten in einem so genannten *dictionary*, einem sehr häufig verwendeten Python-Datentyp. Hierzu empfiehlt sich, [den entsprechenden Abschnitt in der Python-Dokumentation](https://docs.python.org/3.7/library/stdtypes.html?highlight=dictionary#dict) zu lesen oder aber [einen guten Tutorialausschnitt auf Deutsch](https://www.python-kurs.eu/python3_dictionaries.php). Das *dictionary* hat in unserem Beispiel die folgende Gestalt:
```python
{
'username': u'Peter'
}
```
Bei jedem Request der Homepage wird anschließend nachgeschaut, ob der Speicherplatz `username` belegt ist. Damit lässt sich feststellen, ob die Session existiert, also jemand eingeloggt ist, und wie die Person heißt.
```python
@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
```
## Daten aus der Session löschen
Sobald der User sich über die Route `/logout` abmeldet, wird der Speicherplatz in der Session wieder freigegeben.
```python
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
```
Anschließend gelangt die Userin wieder auf die Homepage und der Cookie ist auch in den Entwicklertools nicht mehr sichtbar.
## Ein Wort zur Sicherheit
Das Beispiel hier zeigt nur das, was es braucht, damit man Sessions erklären kann. Für ein sicheres Konzept der Benutzerauthentifizierung und -authorisierung gibt es noch einiges zu beachten, das an dieser Stelle nicht besprochen werden kann. Es findet sich aber viel zum Thema Sicherheit und Sessions in der Literatur.
Session-Cookies sollten auf jeden Fall verschlüsselt werden, denn wer sie auslesen und manipulieren kann, kann die Identität eines anderen Users annehmen! Daher wird in der letzten Zeile des obigen Beispielcode ein *secret* festgelegt, mit dem alle Session-Cookies verschlüsselt werden, wenn sie an den Client geschickt werden. Zeigt ein Client beim Request dem Server seinen Cookie vor, wird das *secret* zum Entschlüsseln verwendet.
## Anregungen, damit keine Langeweile aufkommt
- Übertragen Sie dieses Konzept sinnvoll auf Ihr eigenes Projekt!
- Realisieren Sie ein Login in Ihrem Projekt. Eingeloggte Benutzer_innen sehen mehr/andere Inhalte, als anonyme.
- Wie könnte eine simple Benutzerverwaltung aussehen, mit der geprüft werden kann, ob man sich anmelden darf oder nicht?
- Wie lässt sich eine Benutzerregistrierung realisieren?
- Recherchieren Sie zum Thema "Formularvalidierung"!
+++
title= "Flask auf der Serverseite"
date= 2019-04-02T12:50:13+01:00
draft= false
weight = 10
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = false
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = ""
# The title of the page in menu will be prefixed by this HTML content
pre= ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter= false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
[Flask](https://flask.palletsprojects.com/en/1.1.x/) ist ein so genanntes Microframework. Darunter lässt sich eine Art Baukasten verstehen, mit dem man schnell zum Ziel kommt - jedenfalls schneller, als wenn man alles von Grund auf neu programmieren würde. Aus Sicht der Didaktik wäre es natürlich besser, alles von Grund auf neu zu programmieren. Aber wir haben nur begrenzt Zeit, alle relevanten Webtechnologien im Rahmen der Veranstaltung anzusprechen und auszuprobieren. Außerdem repräsentiert Flask eine moderne Art der Webprogrammierung, die Sie kennen sollten.
## Installation von Flask
Es gibt viele verschiedene Wege, Flask zu installieren. Wir wählen hier einen aus, der schnell zum Ziel führt. Weitere Möglichkeiten sind in der [Dokumentation von Flask](https://flask.palletsprojects.com/en/1.1.x/installation/) erklärt.
### Per SSH auf den Raspberry Pi
Wir installieren Flask auf dem Raspberry Pi, denn er spielt die Rolle des Webservers. Dazu gehen wir per SSH auf den Rechner und führen dann das folgende Kommando aus.
```bash
pi@raspberrypi:~ $ sudo pip3 install Flask
```
## Minimal-Applikation mit Flask
Um Flask auszuprobieren, legen wir eine erste Datei an, mit der wir die Welt begrüßen - so gehört es sich.
```
pi@raspberrypi:~ $ cd www/
pi@raspberrypi:~/www $ mkdir helloflask
pi@raspberrypi:~/www $ cd helloflask/
pi@raspberrypi:~/www/helloflask $
```
In dem neu erstellten Ordner legen wir mit dem Editor `nano` eine Datei an.
pi@raspberrypi:~/www/helloflask $ nano app.py
Nachdem sich der Editor geöffnet hat, schreiben wir den folgenden Python-Code hinein.
```python
#!/usr/bin/env python3</