Aus Linux-Magazin 09/2014

Ein Webframework in Python

© John Cope, Fotolia

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].

Abbildung 1: Aus dem Denied-Projekt, einem Aprilscherz, ging Flask hervor.

Abbildung 1: Aus dem Denied-Projekt, einem Aprilscherz, ging Flask hervor.

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
Abbildung 2: Eine Handvoll Befehle installiert die Beispielanwendung unter Ubuntu 14.04 in eine virtuelle Python-Umgebung. Zum Einsatz kommt dabei Python 3.4.

Abbildung 2: Eine Handvoll Befehle installiert die Beispielanwendung unter Ubuntu 14.04 in eine virtuelle Python-Umgebung. Zum Einsatz kommt dabei Python 3.4.

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« .

Abbildung 3: Die Registrierungsseite von Geistesblitze.

Abbildung 3: Die Registrierungsseite von Geistesblitze.

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)
Abbildung 4: Die Ideensammlung listet alle Ideen des angemeldeten Benutzers auf.

Abbildung 4: Die Ideensammlung listet alle Ideen des angemeldeten Benutzers auf.

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">&times;</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.

Der Autor

Ernesto Rico-Schmidt ist geboren und aufgewachsen in Bolivien. Er studierte Elektrotechnik an der TU Graz in Österreich. Er ist Entwickler, Python-Enthusiast und langjähriger Linux-Nutzer.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben