Flask_Grundlagen¶
ehemalige Veranstaltungen ia3.netz + ia3.data sowie ia4.Netz im Studiengang Interaktive Medien
(c) 2020/2021 Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever
Flask - Grundlagen und Einführung¶
Flask Übersicht¶
Flask ist ein Microframework für Python. Es ist auf der WSGI Utility Bibliothek Werkzeug sowie auf dem Template-Modul Jinja 2 aufgebaut.
"Micro" meint damit nicht, dass die gesamte Webanwendung in eine einzige Python-Datei eingepasst werden muss, obwohl dies durchaus möglich ist. Aber es meint auch nicht, dass Flask weniger an Funktionalität besitzt als normale Webframeworks. "Micro" in Microframework bedeutet, dass Flask im Kernbereich einfach aufgebaut ist aber die Möglichkeit hat diesen Kern zu erweitern.
Installation¶
Flask wird mit dem
PIP - Python Package Installer
über den Aufruf in einem Terminalfenster
pip install Flask
installiert.
Flask "Hello World"¶
%%writefile flask_beispiel_1.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Writing flask_beispiel_1.py
!python flask_beispiel_1.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
Erklärung¶
- app ist die Variable, in der alle Objekte der Anwendung gespeichert sind.
- @app.route("/") ist ein Decorator, der einen HTTP-Request auf "/" mit der nachfolgenden Funktion beantwortet
- def hello(): ist eine Funktion, die einen HTTP-Response zurückliefert
- app.run() startet die Flask-Anwendung als Webserver und arbeitet die Anwendung für jeden eingehenden HTTP-Request ab
Decorator¶
Einen Decorator kann man sich als einen Wrapper oder ein Macro vorstellen, der als Argument die nachfolgende Funktion verwendet und damit diese Funktion verändert aufrufen kann. Eine ausführliche Beschreibung dazu ist in dem Blog von Simeon Franklin oder auch in dem Blog von Bruce Eckel enthalten.
HTTP Telnet Client zum Flask Server¶
from telnetlib import Telnet
connection = Telnet('127.0.0.1',5000)
connection.write(b'GET / HTTP/1.1\r\n')
connection.write(b'Host: 127.0.0.1\r\n')
connection.write(b'\r\n')
response = connection.read_all()
print response
HTTP/1.0 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 12 Server: Werkzeug/0.10.4 Python/2.7.10 Date: Thu, 03 Dec 2015 21:05:57 GMT Hello World!
Chance im Team¶
Erstellen sie auf der Basis des obigen "Hello World"-Beispiels einen einfachen Webserver, der eine HTML-Webseite zurückliefert.
%%writefile flask_beispiel_2.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return """<html>
<head>
<title>index</title>
</head>
<body>
<h2>Dies ist die erste Seite</h2>
</body>
</html>
"""
if __name__ == "__main__":
app.run()
Writing flask_beispiel_2.py
!python flask_beispiel_2.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
Chance im Team¶
Erstellen sie einen einfachen Webserver, der zwei HTML-Webseiten zurückliefert, eine als Root- oder Index.html und eine zweite als Antwort auf eine Anfrage auf eine hello.html Webseite.
%%writefile flask_beispiel_3.py
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
app.debug = False
@app.route("/")
def index():
return """<html>
<head>
<title>Erste Seite</title>
</head>
<body>
<h2>Dies ist die erste Seite</h2>
<a href="zwei">Zur zweiten Seite</a>
</body>
</html>
"""
@app.route("/zwei")
def zwei():
return """<html>
<head>
<title>Zweite Seite</title>
</head>
<body>
<h2>Dies ist die zweite Seite</h2>
<a href="/">Zurück zur ersten Seite</a>
</body>
</html>
"""
if __name__ == "__main__":
app.run()
Overwriting flask_beispiel_3.py
!python flask_beispiel_3.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
Dokumentation¶
Sehen sie sich auch die Dokumentation von Flask an.
from IPython.display import IFrame
IFrame('http://flask.pocoo.org/docs/0.10/quickstart/#debug-mode', width='100%', height=350)
from IPython.display import IFrame
IFrame('http://flask.pocoo.org/docs/0.10/tutorial', width='100%', height=350)
Flask - Objekte¶
Quelle: Flask Quickstart
Flask Request Objekt¶
Das Request Objekt ist detailliert in der Flask API beschrieben.
Die folgenden Methoden sind die wichtigsten:
- method: die Request Methode
- form: ein Dictionary mit dem Inhalt eines HTML-Formulars (POST-Methode)
- args: ein Dictionary mit dem Inhalt des Query-Strings (GET-Methode)
- environ: die komplette WSGI Umgebung
- path: s.u.
- script_root: s.u.
- url: s.u.
- base_url: s.u.
- url_root: s.u.
Wenn eine Anwendung auf die folgende URL hört:
http://www.example.com/myapplication
und ein Benutzer den Request absetzt:
http://www.example.com/myapplication/page.html?x=y
dann sind die obigen Pfad-Variablen folgendermaßen besetzt:
| Variable | Wert |
|---|---|
| path | /page.html |
| script_root | /myapplication |
| base_url | http://www.example.com/myapplication/page.html |
| url | http://www.example.com/myapplication/page.html?x=y |
| url_root | http://www.example.com/myapplication/ |
Der folgende Code stellt einen Auszug aus einer entsprechenden Anwendung dar:
%%writefile flask_beispiel_4.py
from flask import request, Flask, render_template_string
login_template = """ <h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Username:
<dd><input type=text name=username>
<dt>Password:
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
"""
helloworld_template = """ <h2>Hello {{user}}</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
"""
def valid_login(user,passwd):
if user.startswith('a'): return True
else: return False
def log_the_user_in(user):
return render_template_string(helloworld_template, user=user)
app = Flask(__name__)
app.debug = True
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template_string(login_template, error=error)
if __name__ == "__main__":
app.run()
Writing flask_beispiel_4.py
!python flask_beispiel_4.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat 127.0.0.1 - - [03/Dec/2015 22:38:06] "POST /login HTTP/1.1" 200 - ^C
Flask Session Objekt¶
Neben dem Request Objekt gibt es ein zweites Objekt namens session, welches für die Speicherung von Informationen zu einem Benutzer von einem Request zum nächsten verwendet werden kann. Dies wird mittels kryptographisch verschlüsselten Cookies durchgeführt, d.h. der Benutzer kann zwar die Cookies ansehen, aber ohne die entsprechenden geheimen Schlüssel nicht verändern.
%%writefile flask_beispiel_5.py
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
app.debug = True
@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'
if __name__ == "__main__":
app.run()
Overwriting flask_beispiel_5.py
!python flask_beispiel_5.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat 127.0.0.1 - - [03/Dec/2015 22:42:37] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [03/Dec/2015 22:42:41] "GET /login HTTP/1.1" 200 - 127.0.0.1 - - [03/Dec/2015 22:42:55] "POST /login HTTP/1.1" 302 - 127.0.0.1 - - [03/Dec/2015 22:42:55] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [03/Dec/2015 22:43:02] "GET /logout HTTP/1.1" 302 - 127.0.0.1 - - [03/Dec/2015 22:43:02] "GET / HTTP/1.1" 200 - ^C
Flask Response Objekt¶
Der Rückgabewert einer Anwendungsfunktion wird automatisch in ein Response Objekt umgewandelt. Falls der Rückgabewert eine Zeichenkette ist wird automatisch ein Response Objekt mit dem Response Status Code 200 OK erstellt und ein content-type Headerfeld text/html mimetype hinzugefügt. Die Logik von Flask bei der Erstellung des Response Objekts lautet folgendermaßen:
- Falls ein Response Objekt eines korrekten Typs zurückgegeben wird, wird es direkt weitergeleitet
- Falls der Rückgabewert eine Zeichenkette ist, wird ein Response Objekt mit diesem Text als Body erstellt und mit den Default Parametern versehen
- Falls ein Tupel der Form (response, status, headers) zurückgeliefert wird, überschreiben die entsprechend vorhandenen Parameter in dem Tupel die Default Parameter. Headers kann eine Liste oder ein Dictionary mit zusätzlichen Header Feldern sein
- Falls keine der obigen Fälle zutrifft, nimmt Flask an, dass der Rückgabewert eine gültige WSGI Anwendung ist und wandelt diese in ein entsprechendes Response Objekt um
Um das resultierende Response Objekt in der Anwendungsfunktion noch vor der Auslieferung zu verändern kann die make_response() Funktion verwendet werden, wie folgendes Beispiel zeigt:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
Flask 'g' - Globale Objekte in Anwendungen¶
Um Daten in einem Request in mehreren Funktionen gemeinsam nutzen zu können ist eine globale Variable in einer thread-orientierten Umgebung nicht geeignet, da diese in einem anderen Thread verändert werden könnte. In Flask gibt es daher ein spezielles Objekt g, welches nur für einen Request seine Gültigkeit besitzt und somit für unterschiedliche Requests auch unterschiedliche Werte zurückliefert. Dieses Objekt ist seit Flask 0.10 an eine Anwendung geknüpft und damit im Application-Context gespeichert.
g ist damit ein Container, in dem alle möglichen Daten und Objekte abgespeichert werden können, z.B. eine Datenbankverbindung oder der gerade eingeloggte Benutzer.
from flask import Flask
app = Flask('Test')
ctx = app.app_context()
ctx.g.user = 'nik'
user = getattr(ctx.g, 'user', None)
user = ctx.g.get('user', None)
print(user)
print('user' in ctx.g)
nik True
Aktives Plenum¶
Erstellen sie eine Flask Anwendung, die das PTFBestellformular von oben ausliefert und ihnen die Eingaben als Ergebnis wieder ausliefert
PTFBestellformular.html¶
%%writefile PTFBestellformular.html
<html>
<head>
<title>PTF Bestellformular</title>
</head>
<body>
<h1>PTF Bestellformular</h1>
<form ACTION="/formular" method="GET">
<h3>Kunde</h3>
<p>Name <input name="Kunde" size="46"/></p>
<p>Strasse <input name="Strasse" size="40"/></p>
<p>Ort <input name="Ort" size="20"> Plz <input name="Plz" size="4"></p>
<h3>Kreditkarte</h3>
<p>Mastercard <input name="Kreditkarte" type="radio" value="Mastercard"/>
Visacard <input name="Kreditkarte" type="radio" value="Visacard"/></p>
<p>Nummer <input name="Kreditkartennummer" size="10"/>
Ablaufdatum <input name="Ablaufdatum" size="4"/></p>
<h3>PTF Bestellgröße</h3>
<p>Groß <input name="Groesse" type="radio" value="gross"/>
Mittel <input name="Groesse" type="radio" value="mittel"/>
Klein <input name="Groesse" type="radio" value="klein"/></p>
<h3>Zustellung</h3>
<p>Expresszustellung <input name="Express" type="checkbox"/></p>
<p><input type="submit" value="Bestellung abschicken"/></p>
</form>
<p>Vielen Dank für ihre Bestellung!</p>
</body>
</html>
Writing PTFBestellformular.html
PTFBestellformular.py¶
%%writefile flask_beispiel_6.py
from flask import Flask, request
app = Flask(__name__)
app.debug = False
def leseDatei(dateiname):
try:
datei = open(dateiname,"r")
gesamterText = datei.read()
datei.close()
return gesamterText
except IOError:
return "Die Datei {} wurde nicht gefunden".format(dateiname)
@app.route('/')
def index():
return leseDatei('PTFBestellformular.html')
@app.route('/formular', methods=['GET', 'POST'])
def formularausfuehren():
if request.method == 'GET':
html = """<!doctype html>
<html>
<head>
</head>
<body>
<ul>"""
for i in request.args:
html = html + ' <li>'+i+': '+request.args[i]+'</li>'
html = html + """
</ul>
</body>
</html>"""
return html
app.run()
Writing flask_beispiel_6.py
!python flask_beispiel_6.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
MVC Architektur¶
In der schon im allgemeinen Teil von Webframeworks angesprochen ist in der Model - View - Controller (MVC) Architektur
die View in der Regel durch HTML Templates realisiert.
HTML Templates¶
HTML Template Dateien sind HTML Dateien, in denen über ein Template Markup Code eingebaut werden kann.
Das untenstehende Beispiel ist aus dem Flask Quickstart Beispiel entnommen:
%%writefile flask_beispiel_7.py
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
Writing flask_beispiel_7.py
!python flask_beispiel_7.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
Flask schaut nach Template Dateien in dem templates Ordner. Wenn die entsprechende Anwendung ein Modul ist (eine einzige Python-Datei), dann ist der Ordner auf der Ebene dieses Moduls, wenn die Anwendung ein Paket ist, dann ist der Ordner auf der Ebene des Pakets enthalten:
Modul:
/application.py /templates /hello.htmlPaket:
/application /__init__.py /templates /hello.html
%%writefile templates/hello.html
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
Writing templates/hello.html
Obige Aufgabe jetzt mit Templates¶
%%writefile flask_beispiel_8.py
from flask import Flask, request, render_template
app = Flask(__name__)
app.debug = False
@app.route('/')
def index():
return render_template('PTFBestellformular.html')
@app.route('/formular', methods=['GET', 'POST'])
def formularausfuehren():
if request.method == 'GET':
return render_template('requestTemplate.html')
app.run()
Writing flask_beispiel_8.py
%%writefile templates/requestTemplate.html
<!doctype html>
<html>
<head>
</head>
<body>
<ul>
{% for i in request.args %}
<li>{{ i }}:{{ request.args[i] }}</li>
{% endfor %}
</ul>
</body>
</html>
Writing templates/requestTemplate.html
!python flask_beispiel_8.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [03/Dec/2015 22:05:16] "GET / HTTP/1.1" 200 - ^C
from jinja2 import Template
users = [
dict(username='Klever',url='nik.klever@hs-augsburg.de'),
dict(username='Kowarschick',url='wolfgang.kowarschick@hs-augsburg.de'),
dict(username='Kipp',url='michael.kipp@hs-augsburg.de'),
dict(username='Rist',url='thomas.rist@hs-augsburg.de'),
]
templateForm = """
<title>{% block title %}{% endblock %}</title>
<ul>
{% for user in users %}
<li><a href="{{ user['url'] }}">{{ user['username'] }}</a></li>
{% endfor %}
</ul>
"""
template = Template(templateForm)
rs=template.render(users=users)
print(rs)
<title></title> <ul> <li><a href="nik.klever@hs-augsburg.de">Klever</a></li> <li><a href="wolfgang.kowarschick@hs-augsburg.de">Kowarschick</a></li> <li><a href="michael.kipp@hs-augsburg.de">Kipp</a></li> <li><a href="thomas.rist@hs-augsburg.de">Rist</a></li> </ul>
Jinja High-Level API¶
In der Jinja High-Level API stehen in der Klasse Environment die Default-Werte für das Markup, u.a. die wichtigsten:
- block_start_string: The string marking the beginning of a block. Defaults to '{%'.
- block_end_string: The string marking the end of a block. Defaults to '%}'.
- variable_start_string: The string marking the beginning of a print statement. Defaults to '{{'.
- variable_end_string: The string marking the end of a print statement. Defaults to '}}'.
- comment_start_string: The string marking the beginning of a comment. Defaults to '{#'.
- comment_end_string: The string marking the end of a comment. Defaults to '#}'.
Template Design¶
Ein Template ist einfach eine Textdatei. Dieses kann jegliches text-basierte Format (HTML, XML, CSV, LaTeX, etc.) erzeugen. Es gibt keine spezifisches Dateiextension für Templates, .html oder .xml ist völlig ok.
Die wichtigsten Template Design Komponenten sind im Folgenden aufgeführt. Die komplette API ist unter der Template Designer Documentation zu finden.
Template Vererbung¶
mit den {% block %}, {% endblock %} und {% extends %} Tags sowie dem {{ super() }} Block
Basis Template
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
Kind Template
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}
Kontrollstrukturen¶
For¶
<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
mit dem loop Objekt sind zusätzliche Methoden verwendbar:
{% for row in rows %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ loop.index }}. Zeile = {{ row }}</li>
{% endfor %}
If¶
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
Jinja in Flask¶
Innerhalb der Templates hat man Zugriff auf die Flask Objekte request, session und g sowie auf die Funktion get_flashed_messages().
Templates sind dann ausgesprochen sinnvoll, wenn Templates vererbt werden, d.h. bestimmte Elemente einer Webseite immer wieder auf allen Webseiten gleich verwendet werden, z.B. Header, Footer, Navigationsmenü.
Markup in Flask¶
In Templates ist die automatische Escape-Funktionalität eingestellt, d.h. wenn der Dateityp HTML ist, wird automatisch jeglicher verwendete Inhalt escaped. Kann einer Variablen vertraut werden und in dieser wird sicheres HTML verwendet, dann kann die Variable mit der Verwendung der Klasse Markup als sicher markiert werden.
Folgende Beispiele zeigen die Verwendung der Klasse Markup:
from flask import Markup
Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
Aktives Plenum¶
Erstellen sie eine Flask Anwendung, die ein Formular ausliefert, über die sie eine Datei hochladen können und anschliessend diese Datei Zeile für Zeile wieder ausliefern, sodass jede Zeile einmal mit der CSS-Klasse odd und einmal mit even versehen wird. Verwenden sie als Datei eine CSV Datei.
Hinweis:
Beachten sie das Quickstart Beispiel zu File Uploads
CSVUpload.py¶
%%writefile flask_beispiel_9.py
# -*- coding: utf-8 -*-
from flask import Flask, request, render_template
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
return render_template('CSVUpload.html')
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['upload']
dateiSpeicher = f.stream.read().decode('utf-8')
dateiZeilen = dateiSpeicher.split('\n')[:-1]
return render_template('uploadfile.html', dateiZeilen = dateiZeilen, dateiName = f.filename)
app.run()
Overwriting flask_beispiel_9.py
CSVUpload.html¶
%%writefile templates/CSVUpload.html
<html>
<head>
<title>CSV Upload</title>
</head>
<body>
<h1>CSV Upload</h1>
<form ACTION="/upload" method="POST" enctype ="multipart/form-data">
<p><input type="file" name="upload"/></p>
<p><input type="submit" value="Datei hochladen"/></p>
</form>
<p>Vielen Dank!</p>
</body>
</html>
Writing templates/CSVUpload.html
uploadfile.html¶
%%writefile templates/uploadfile.html
<html>
<head>
<title>CSV Datei</title>
<style type="text/css">
.even { background-color:#ffffff; }
.odd { background-color:#e0e0e0; }
</style>
</head>
<body>
<h1>CSV Datei "{{ dateiName }}"</h1>
<ul>
{% for dateiZeile in dateiZeilen %}
<li class = "{{loop.cycle('odd','even')}}"> {{ loop.index}}. Zeile = "{{ dateiZeile }}" </li>
{% endfor %}
</ul>
</body>
</html>
Writing templates/uploadfile.html