Das Micro-Webframework Flask ist aus einem Aprilscherz entstanden. Mittlerweile erfreut die leichtgewichtige Django-Alternative viele Webentwickler mit einem Hang zu Python.
Wann immer die Rede von Webframeworks für Python ist, fällt schnell der Name Django [1]. 2005 von Adrian Holovaty und Simon Wilson unter einer BSD-Lizenz freigegeben, hat sich das Python-Framework nicht nur ordentlich entwickelt, sondern auch nachhaltig etabliert. Zahlreiche wichtige Webseiten und Webservices bauen mittlerweile auf Djangos Fundament.
Mitunter stehen Entwickler aber vor Aufgaben, die Djangos objektrelationaler Mapper (ORM) nur sehr kompliziert löst oder die den Einsatz zusätzlicher Applikationen oder weiterer Frameworks erfordern, etwa wenn sie eine REST-Schnittstelle implementieren möchten. Wo also weniger mehr ist, leistet ein so genanntes Micro-Webframework wie Flask [2] oft bessere Dienste.
Über das Vehikel eines kleinen Flask-Projekts namens Geistesblitze stellt dieser Artikel das Framework vor. Geistesblitze besteht aus einer Webanwendung mit grafischem Web-UI, um Ideen schnell zu erfassen und zuzuordnen. Das Beispiel führt die wichtigsten und interessantesten Aspekte von Flask vor. Der komplette Code steht unter einer BSD-Lizenz auf Github [3] zum Download bereit, Ausschnitte zeigen die Listings des Artikels.
April, April
Im Jahr 2010 tauchte im Web erstmals das Micro-Webframework Denied [4] auf. Der Python-Entwickler Armin Ronacher hatte es als Aprilscherz veröffentlicht, er wollte sich damit über die Konjunktur diverser Micro-Webframeworks lustig machen, die damals populär wurden. Dazu packte er den gesamten Code in eine einzige Datei namens »deny.py« und setzte zugleich eine schlecht programmierte Webseite (Abbildung 1) mit gefälschten Zitaten auf. Zudem drehte er mit einem holländischen Freund ein Video, indem dieser sich als französischer Entwickler von Denied ausgab [5].
Zu seiner Überraschung fielen die Reaktionen auf Denied überwiegend positiv aus und übertrafen die auf seine anderen Projekte deutlich. Einige Entwickler fanden die Ideen offenbar interessant und erfrischend neu, das Flask-Projekt war geboren.
Flask
Flask steht ebenfalls unter BSD-Lizenz und basiert auf dem Toolkit Werkzeug [6] und der Template Engine Jinja 2 [7]. Beide Projekte stammen vom Pocoo-Team [8] rund um Georg Brandl und Armin Ronacher.
Flask bringt alles Notwendige mit, um Webapplikationen zu entwerfen. Entwickler schätzen vor allem die Eleganz, die intuitiven Schnittstellen und die Einfachheit, mit der sie Funktionen von Flask über Erweiterungen an die eigenen Wünsche anpassen. Das Framework unterstützt Unicode, Pythons Web Server Gateway Interface (WSGI) sowie gängige Sicherheitsmaßnahmen.
Geistesblitze
Das Projekt Geistesblitze erlaubt es Benutzern, sich zu registrieren und ihre Ideen, das sind die Geistesblitze, zu skizzieren. Jede Idee erhält einen Namen und eine Beschreibung. Die Anwendung demonstriert den Einsatz einiger Features von Flask, zu denen Templates (»render_template« ), Weiterleitungen (»redirect« ), Meldungen (»flash« ) und das Referenzieren von Views mit Hilfe von Python-Funktionen (»url_for« ) gehören. Außerdem setzt sie die Erweiterungen Flask-Bootstrap [9], Flask-Login [10], Flask-SQL-Alchemy [11] und Flask-WTF [12] sinnvoll ein.
Gut gebaut
Die Struktur einer Flask-Applikation ist weitgehend offen. Als einzige Vorgaben müssen die Verzeichnisse »static« und »templates« existieren und sich im selben Verzeichnis wie die Anwendung befinden. Doch auch das lässt sich mit den entsprechenden Parametern beim Erzeugen des App-Objekts ändern.
Der Rest der Applikation darf in einer einzigen Python-Datei stecken, im Beispielcode heißt sie »app.py« . Die komplette Anwendung ließe sich auch auf verschiedene Dateien wie etwa »models.py« , »forms.py« , »views.py« aufteilen. Dies empfiehlt sich, sobald sie komplex und groß wird. Für die Beispielanwendung lohnt sich das nicht, sie besteht aus den Dateien »app.py« , »create_all.py« , »run.py« , den Ordnern »templates« und »static« sowie einigen Standarddateien, darunter »requirements.txt« , »README.md« und »LICENSE« .
Virtuell abgesichert
In einer virtuellen Python-Umgebung lässt sich die Anwendung ausprobieren. Flask unterstützt die Versionen 2.7 und 3.4 von Python, wobei der Artikel mit einem Setup von Ubuntu 14.04 (64 Bit) und Python 3.4 arbeitet.
Im ersten Schritt gilt es, die Geistesblitze-Dateien herunterzuladen [3], zu entpacken und in das neu erzeugte Verzeichnis zu wechseln. Hier folgen die Schritte aus Listing 1. Über »pyvenv« legt der Entwickler eine virtuelle Umgebung an und aktiviert diese über den Aufruf in Zeile 2 (Abbildung 2). Da Ubuntu 14.04 Pythons Paketmanager Pip nicht mitliefert, holt er diesen in Zeile 3 von einer Webseite und installiert ihn dann. Pip wiederum besorgt dann sämtliche Geistesblitze-Abhängigkeiten, welche die Datei »requirements.txt« auflistet.
Listing 1
Geistesblitze installieren
01 pyvenv-3.4 --without-pip venv 02 source venv/bin/activate 03 (venv) wget https://bootstrap.pypa.io/get-pip.py 04 (venv) python get-pip.py 05 (venv) pip install -r requirements.txt 06 (venv) ./create_all.py 07 (venv) ./run.py
Die Datei »create_all.py« , die der Flask-Anwender im vorletzten Schritt ausführt, besteht aus vier Codezeilen und dient dazu, getrennt von der Anwendung selbst die zwei Tabellen »users« und »ideas« in einer lokalen SQlite-Datei anzulegen: »geistesblitze.sqlite« . Auch die Datei »run.py« ist lediglich vier Zeilen lang, sie startet die eigentliche Applikation namens »app.py« .
Über die Eingabe von »http://localhost:5000/register« (Abbildung 3) lässt sich Geistesblitze anschließend in einem Browser wie Firefox aufrufen. Um die virtuelle Python-Umgebung zu verlassen, verwendet der Entwickler übrigens den Befehl »(venv) deactivate« .
Kommt Python 2.7 zum Einsatz, braucht er zudem die »virtualenv« , die er über »sudo apt-get install python-virtualenv« installiert. Dann folgt er den Schritten 1, 2 sowie 5 bis 7 aus Listing 1, ändert aber die Zeile 1 in »virtualenv venv« .
Die Flask-App
Die eigentliche Anwendung besteht aus den Formularen, Modellen und Views, die in der Datei »app.py« (Listing 2) stecken. Sie importiert in den Zeilen 1 bis 9 zunächst die notwendigen Module. Beim Verwalten der Datenbank helfen die eingangs erwähnten Flask-Erweiterungen, etwa SQL-Alchemy [11] und Bootstrap [9]. Ab Zeile 11 legt das Skript diverse Objekte an.
Listing 2
Import und Objektaufbau
01 from werkzeug.security import generate_password_hash, check_password_hash
02 from flask import Flask, render_template, redirect, flash, url_for
03 from flask.ext.bootstrap import Bootstrap
04 from flask.ext.login import LoginManager, UserMixin, login_required, login_user, logout_user, current_user
05 from flask.ext.sqlalchemy import SQLAlchemy
06 from flask.ext.wtf import Form
07 from wtforms import PasswordField, StringField, SubmitField, TextAreaField, ValidationError
08 from wtforms.validators import EqualTo, Required
09 from os.path import abspath, dirname, join
10
11 basedir = abspath(dirname(__file__))
12
13 app = Flask(__name__)
14 app.config['SECRET_KEY'] = 'Passwort'
15 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////{0}'.format (join(basedir, 'geistesblitze.sqlite'))
16
17 bootstrap = Bootstrap(app)
18 db = SQLAlchemy(app)
19
20 login_manager = LoginManager(app)
21 login_manager.session_protection = 'strong'
22 login_manager.login_view = 'login'
23
24 @login_manager.user_loader
25 def load_user(user_id):
26 return User.query.get(int(user_id))
Modelle
Die Datenbank besteht aus den zwei Tabellen »users« und »ideas« . Für die User speichert sie in der gleichnamigen Klasse (Listing 3) nur einen Hash des Passworts oder überprüft ihn später. Die Anwendung speichert keine Passwörter im Klartext, sondern generiert über die Funktion »werkzeug.security.generate_password_hash()« einen sicheren Hash und überprüft Hashes mit »werkzeug.security.check_password_hash()« . Beide Funktionen stellt Werkzeug, eine zentrale Komponente von Flask, bereit.
Listing 3
Userverwaltung
01 class User(db.Model, UserMixin):
02 id = db.Column(db.Integer, primary_key=True)
03 username = db.Column(db.String(64), unique=True)
04 password_hash = db.Column(db.String(128), unique=False)
05 ideas = db.relationship('Idea', backref='user', lazy='dynamic')
06
07 @property
08 def password(self):
09 raise AttributeError('password is not a readable attribute')
10
11 @password.setter
12 def password(self, password):
13 self.password_hash = generate_password_hash(password)
14
15 def verify_password(self, password):
16 return check_password_hash(self.password_hash, password)
17
18 def __repr__(self):
19 return '<User %r>' % self.username
Um die Ideen kümmert sich die Klasse »Idea« (Listing 4). Sie enthält eine Referenz auf den Benutzer, der sie angelegt hat, damit Geistesblitze ihm später lediglich seine eigenen Ideen präsentiert.
Listing 4
Ideensammlung
01 class Idea(db.Model):
02 id = db.Column(db.Integer, primary_key=True)
03 name = db.Column(db.String(128), unique=False)
04 description = db.Column(db.Text(), nullable=True)
05
06 user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
07
08 def __repr__(self):
09 return '<Idea %r>' % self.name
Formulare prüfen
Die Applikation enthält zudem drei Formulare, die WT-Forms [13] erzeugt. Das Formular-Framework definiert nicht nur Formulare, sondern kümmert sich auch um die Validierungen.
Dank der zugehörigen Klasse »RegisterForm« (Listing 5) überprüft WT-Forms nicht nur, ob die zwei angegebenen Passwörter übereinstimmen, sondern auch, ob der Benutzername bereits im Einsatz und in der Datenbank enthalten ist. So ließe sich die Mindestlänge von Benutzername und Passwort prüfen, sobald sich der User registriert. Ähnliche Validierungen der Inhalte nehmen die Klassen »LoginForm« und »AddIdeaForm« vor.
Listing 5
Formularvalidierung
01 class RegisterForm(Form):
02 username = StringField('Username', validators=[Required()])
03 password = PasswordField('Password', validators=[Required(), EqualTo('password2',
message='Passwords must match.')])
04 password2 = PasswordField('Confirm Password', validators=[Required()])
05 submit = SubmitField('Register')
06
07 def validate_username(self, field):
08 if User.query.filter_by(username=field.data).first():
09 raise ValidationError('Username already in use.')
GET-it, POST-it
Es folgen die Views. Ein GET-Request holt das Login-Formular auf den Schirm, ein POST-Request versucht das Formular zu validieren (Listing 6). Stimmt das Passwort, meldet es den Benutzer an (»flask.ext.login.login_user()« ) und leitet ihn auf die Liste der eigenen Ideen weiter. Andernfalls zeigt die Funktion »login()« eine Fehlermeldung.
Listing 6
Login-Formular
01 @app.route('/login', methods=['GET', 'POST'])
02 def login():
03 form = LoginForm()
04 if form.validate_on_submit():
05 user = User.query.filter_by(username=form.username.data).first()
06 if user is not None and user.verify_password(form.password.data):
07 login_user(user, True)
08 return redirect(url_for('ideas'))
09 flash('Invalid username of password')
10 return render_template('login.html', form=form)
Ähnliches leistet die Funktion »logout()« , die dem Benutzer beim Abmelden eine Nachricht anzeigt und ihn auf die Login-Seite befördert. Registriert sich ein neuer Benutzer, bekommt dieser dank der Funktion »register()« nach einem GET-Request das Registrierungsformular zu sehen, während die Anwendung nach einem POST-Request das Formular validiert und den Benutzer anlegt. Die Funktion »idea(id)« enthüllt die Details zu den Geistesblitzen. Fehlt die angegebene Idee oder stammt sie nicht vom angemeldeten Benutzer, erscheint eine entsprechende Fehlerseite (Listing 7).
Listing 7
Ideen im Detail
01 @app.route('/ideas/<int:id>')
02 @login_required
03 def idea(id):
04 idea = Idea.query.filter_by(id=id).first()
05 ideas = Idea.query.filter_by(user=current_user).all()
06 if idea is None:
07 return render_template('404.html'), 404
08 if idea.user != current_user:
09 return render_template('403.html'), 403
10 return render_template('idea.html', idea=idea, ideas=ideas)
Im Gegensatz zu »idea(id)« zeigt die Funktion »ideas()« alle Ideen des aktuell angemeldeten Benutzers an (Abbildung 4). Will er eine neue Idee hinzufügen, zeigt ein GET-Request das entsprechende Formular an, ein POST-Request validiert, ob es zur Idee einen Namen und eine Beschreibung gibt. Ist das der Fall, legt »add_idea()« (Listing 8) die Idee an, speichert sie, gibt eine Meldung aus und leitet den Benutzer auf die Liste der Ideen (»ideas()« ) weiter.
Listing 8
Neue Idee ergänzen
01 @app.route('/add_idea', methods=['GET', 'POST'])
02 @login_required
03 def add_idea():
04 form = AddIdeaForm()
05 if form.validate_on_submit():
06 idea = Idea(name=form.name.data, description=form.description.data)
07 idea.user = current_user
08 db.session.add(idea)
09 db.session.commit()
10 flash('Your idea has been saved')
11 return redirect(url_for('ideas'))
12 return render_template('add_idea.html', form=form)
Auf den Schirm!
Als zentrale Komponente von Flask hilft die Template-Engine Jinja 2 dabei, die Inhalte auf den Bildschirm zu bringen. Inspiriert durch die Templates von Django bietet Jinja 2 eine Reihe von Features an, die es zu einem flexiblen und schnellen System machen.
Der Entwickler kann Templates nicht nur vererben und Blöcke davon überschreiben, sondern diese Blöcke auch erweitern. In Kombination mit Bootstrap ergibt sich ein sehr flexibler Rahmen, um Anwendungen zu schreiben.
Ob das Basis-Template (Listing 9) in der Navigation den Link zur Liste der Ideen oder die Links zum Einloggen und Registrieren anzeigt, hängt davon ab, ob sich der Benutzer anmeldet oder nicht. Hängen im System noch Meldungen an den Benutzer, erscheinen diese im Inhaltsbereich.
Listing 9
Basis-Template
01 <!-- base.html -->
02 {% extends "bootstrap/base.html" %}
03 {% block title %}Geistesblitze{% endblock %}
04 {% block navbar %}
05 <div class="navbar navbar" role="navigation">
06 <div class="container">
07 <div class="navbar-header">
08 <a class="navbar-brand" href="http://%20url_for('ideas')%20"> Geistesblitze</a>
09 </div>
10 <div class="navbar-collapse collapse">
11 {% if current_user.is_authenticated() %}
12 <ul class="nav navbar-nav navbor-left">
13 <li><a href="http://%20url_for('add_idea')%20">Add Idea</a></li>
14 </ul>
15 {% endif %}
16 <ul class="nav navbar-nav navbar-right">
17 {% if current_user.is_authenticated() %}
18 <li><a href="http://%20url_for('logout')%20">Log Out</a></li>
19 {% else %}
20 <li><a href="http://%20url_for('login')%20">Log In</a></li>
21 <li><a href="http://%20url_for('register')%20">Register</a></li>
22 {% endif %}
23 </ul>
24 </div>
25 </div>
26 </div>
27 {% endblock %}
28 {% block content %}
29 <div class="container">
30 {% for message in get_flashed_messages() %}
31 <div class="alert alert-warning">
32 <button type="button" class="close" data-dismiss= "alert">×</button>
33 {{ message }}
34 </div>
35 {% endfor %}
36
37 {% block page_content %}{% endblock %}
38 </div>
39 {% endblock %}
Die Erweiterung Flask-Bootstrap enthält zudem Makros, die es sehr leicht machen, Formulare darzustellen. Soll das Formular zum Registrieren, Anmelden oder Hinzufügen einer neuen Idee erscheinen, ruft der Entwickler lediglich die Funktion »wtf.quick_form(form)« (Listing 10) auf.
Listing 10
Flask-Bootstrap
01 <!-- register.html -->
02 {% extends "base.html" %}
03 {% import "bootstrap/wtf.html" as wtf %}
04 {% block title %}Register{% endblock %}
05 {% block page_content %}
06 <div class="page-header">
07 <h1>Register</h1>
08 </div>
09 <div class="col-md-4">
10 {{ wtf.quick_form(form) }}
11 </div>
12 {% endblock %}
Flask 1.0
Die aktuelle Version 0.10.1 des Micro-Framework Flask ist inzwischen mehr als ein Jahr alt. Das lässt sich durchaus als Zeichen von Stabilität und Reife des Framework und seiner Schnittstellen deuten. Die Bugfix-Version 0.10.2 und die Version 1.0 stehen bereits seit einiger Zeit auf der Roadmap. Auch wenn es noch kein Datum für die Freigabe gibt, dürfte es nicht mehr lange dauern, bis die Version 1.0 von Flask erscheint. In diesem Fall, so ließe sich dann kalauern, wird aus einem Aprilscherz Ernst.
Infos
- Django: https://www.djangoproject.com
- Flask: http://flask.pocoo.org
- Geistesblitze: https://github.com/nnrcschmdt/geistesblitze
- Micro-Webframework Denied: http://denied.immersedcode.org
- Die Flask-Geschichte: http://lucumr.pocoo.org/2010/4/3/april-1st-post-mortem/
- Das Werkzeug-Toolkit: http://werkzeug.pocoo.org/docs/
- Jinja 2: http://jinja.pocoo.org/docs/
- Das Entwicklerteam: http://www.pocoo.org/team/
- Flask-Bootstrap: https://pythonhosted.org/Flask-Bootstrap/
- Flask-Login: https://pythonhosted.org/Flask-Login/
- Flask SQL-Alchemy: https://pythonhosted.org/Flask-SQLAlchemy/
- Flask-WTF: https://flask-wtf.readthedocs.org/en/latest/
- WT-Forms: https://wtforms.readthedocs.org/en/latest/









