Commit 2c77c808 authored by Axel Dürkop's avatar Axel Dürkop

Add new chapters

parent 2456a8c7
.gitbook
.npm
.gitignore
.bookignore
.gitlab-ci.yml
.pandoc-config.yml
......@@ -61,6 +61,9 @@
* [Flask auf der Serverseite](material/sitzungsmaterial/flask/flask-auf-der-serverseite.md)
* [Request-Daten mit Flask auswerten](material/sitzungsmaterial/flask/request-daten-mit-flask-auswerten.md)
* [Aus statisch mach dynamisch](material/sitzungsmaterial/flask/aus-statisch-mach-dynamisch.md)
* [Das Template-System von Flask](material/sitzungsmaterial/flask/das-template-system-von-flask.md)
* [Die Vergesslichkeit des Servers - Sessions](material/sitzungsmaterial/flask/die-vergesslichkeit-des-servers---sessions.md)
* [Tabellen und Bildergalerien](material/sitzungsmaterial/flask/tabellen-und-bildergalerien.md)
* [Installation von remote-sync](material/sitzungsmaterial/flask/installation-von-remote-sync.md)
## Referenzen
......@@ -72,4 +75,3 @@
## Meta
* [Impressum und Kontakt](meta/impressum.md)
# Das Template-System von Flask
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](http://jinja.pocoo.org/docs/dev/templates/).
## 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:
```jinja
<!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:
```jinja
{% 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.
```jinja
<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>`)!
# Die Vergesslichkeit des Servers - Sessions
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](http://flask.pocoo.org/docs/0.12/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](/abb/material/sitzungsmaterial/flask/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](/abb/material/sitzungsmaterial/flask/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/2/library/stdtypes.html?highlight=dictionary#dict) zu lesen oder aber [einen guten Tutorialausschnitt auf Deutsch](http://www.python-kurs.eu/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.
## Aufgaben
- Ü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"!
# Flask auf der Serverseite
[Flask](http://flask.pocoo.org/docs/0.11/) 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.
[Flask](http://flask.pocoo.org/docs/0.12/) 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
......@@ -49,6 +49,7 @@ def hello_world():
if __name__ =='__main__':
app.run(host="0.0.0.0", debug=True)
```
Mit <kbd>STRG</kbd>+<kbd>X</kbd> verlassen Sie den Editor. Die Frage, ob Sie speichern wollen, beantworten Sie mit `Ja` und stimmen auch dem Namen zu.
## Flask-App starten
......
# Tabellen und Bildergalerien
Flask läuft mit Python. Das ist insofern praktisch, als dass alles, was Python bietet, Teil von Flask werden kann. An einem einfachen und praxisnahen Beispiel soll dies gezeigt werden.
## Tabellen aus einer zweidimensionalen Liste erstellen
Der Datentyp *list* ist ja schon aus dem [vergangenen Semester bekannt](http://www.python-processing-arduino.de/Programmiergrundlagen/listen.html). Mit ihm kann in einer Variablen eine Liste von Werten gespeichert werden.
```python
blumen = ["Rosen","Tulpen", "Lilien"]
```
## Die interaktive Kommandozeile von Python
Die Kommandozeile des Raspberrys ist ja schon bekannt, auf ihr können wir Befehle wie `ls`, `mkdir` und `rm` ausführen. Was passiert aber, wenn wir den Befehl `python` ausführen?
```bash
pi@raspberrypi:~ $ python3
Python 3.4.2 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> _
```
Wir landen in der **interaktiven Kommandozeile von Python**! Die sollte man kennen, weil viele Beispiele im Netz in dieser Kommandozeile vorgeführt werden.
Der Befehl `python3` hat uns in eine neue Umgebung katapultiert, in der nun die Befehle von Python gelten.
## Schleifen in Python
Wir erinnern uns: Mit einer Schleife können die Werte einer Liste ausgegeben werden. Die folgenden Zeilen geben wir in der interaktiven Kommandozeile von Python ein:
```python
>>> blumen = ["Rosen","Tulpen", "Lilien"]
>>> for b in blumen:
... print(b)
...
Rosen
Tulpen
Lilien
>>>
```
Auch hier ist an die Einrückung zu denken! Die Eingabe der Schleife schließen wir mit der Eingabetaste ab, dann folgt die Ausgabe.
## Verschachtelte Listen
Es sind auch verschachtelte Listen möglich. Dabei handelt es sich um eine wichtiges Konstrukt, da auf diese Weise Tabellenstrukturen abgebildet werden können.
```bash
>>> tabelle = [["Rosen", "rot", 1.99], ["Nelken", "gelb", 0.89],["Osterglocken", "braun-gelb", 0.45]]
```
Beachten Sie die äußeren Klammern, die die äußere Liste definieren. In dieser Liste sind die einzelnen Elemente wiederum Listen, in denen Strings und Floats stecken.
## Ausgabe von verschachtelten Listen mit Schleifen
Um nun diese Liste mit Schleifen auszugeben, ist eine Verschachtelung notwendig.
```python
>>> for zeile in tabelle:
... for zelle in zeile:
... print(zelle)
...
Rosen
rot
1.99
Nelken
gelb
0.89
Osterglocken
braun-gelb
0.45
>>>
```
Dieses Wissen werden wir nun auf den *Secondhandblumenladen* und Flask übertragen.
## Datenverarbeitung für das Template in Flask
Das obige Beispiel ist insofern praxisnah, als dass Daten in Form verschachtelter Listen häufig das Ergebnis einer Datenbankabfrage sind. Und Daten aus Datenbanktabellen werden oftmals auch in HTML-Tabellen dargestellt. Wir werden keine Datenbank verwenden, sondern mit einem verschachtelten Listenkonstrukt so tun als ob.
Bevor wir fortfahren, werfen wir noch einen kurzen Blick in die Datei `angebot.html`, die eine statische HTML-Tabelle enthält.
```html
<!-- angebot.html -->
<h2>Angebot</h2>
<table id="angebot">
<tr class="even">
<th class="first-column">Name</th>
<th class="second-column">Farbe</th>
<th class="third-column">Preis</th>
</tr>
<tr class="odd">
<td>Rosen</td>
<td>rot</td>
<td>€1,99</td>
</tr>
<tr class="even">
<td>Nelken</td>
<td>gelb</td>
<td>€ 0,89</td>
</tr>
<tr class="odd">
<td>Osterglocken</td>
<td>braun-gelb</td>
<td>€ 0,45</td>
</tr>
<tr class="even">
<td>Tannenbäume</td>
<td>grün-braun</td>
<td>€ 9,99</td>
</tr>
</table>
```
Dieses Konstrukt werden wir nun verändern. Dazu gehen wir in die `app.py`.
```python
# app.py
@app.route('/angebot')
def angebot():
tabelle = [["Rosen", "rot", 1.99], ["Nelken", "gelb", 0.89],["Osterglocken", "braun-gelb", 0.45]]
return render_template('angebot.html', title='Angebot', tabelle=tabelle)
```
Die verschachtelte Liste ist von oben übernommen. Sie wird als Variable an das View übergeben und steht dort für Jinja2, die Templatesprache von Flask, zur Verfügung.
```jinja
<!-- angebot.html -->
{% extends "layout.html" %}
{% block content %}
<h2>Angebot</h2>
<table id="angebot">
<tr class="even">
<th class="first-column">Name</th>
<th class="second-column">Farbe</th>
<th class="third-column">Preis</th>
</tr>
{% for zeile in tabelle %}
<tr>
{% for zelle in zeile %}
<td>{{ zelle }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endblock %}
```
Es sollte deutlich werden, dass wir im Template mit Jinja2 das gleiche tun wie zuvor auf der interaktiven Kommandozeile.
## Fazit
Diese Technik hat Potenzial, denn sie hat ein Grundprinzip:
> Daten, die aus unterschiedlichsten Quellen stammen können, sollen auf einer Website ausgegeben werden.
Das können sein:
- Geopositionsdaten
- Bilder für eine Bildergalerie
- Messdaten
- Tischtennisturnierergebnisse
- Preise für EM-Finalkarten
- Ergebnisse einer Umfrage
Die Darstellung dieser Daten muss nicht zwingend mit einer HTML-Tabelle erfolgen. Der Vorgang der *Iteration* über das Datenkonstrukt ist aber in der Regel schleifenartig, wie oben gezeigt.
Die Daten werden in der `app.py` beschafft, ggf. mit Python aufbereitet und anschließend an das View bzw. das Template übergeben. Dort erfolgt dann die Konstruktion des notwendigen HTMLs.
## Aufgabe
Überlegen Sie, wie Sie mit einer Anzahl von Bildern, die in einem Verzeichnis Ihrer Flask-Anwendung gegeben sind, eine Bildergalerie programmieren können! Besprechen Sie Ihre Überlegungen zunächst mit anderen und setzen Sie sie dann um!
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment