Aus Linux-Magazin 11/2013

Das Python-Framework Django

© Elnur Amikishiyev, 123RF.com

Alle befragten Django-Entwickler waren über die vom Linux-Magazin gestellte Aufgabe nicht glücklich: Mit einem CMS sei das wesentlich einfacher zu lösen, hieß es. Autor Sven Schannak zeigt, wie es trotzdem klappt.

Mit Django 1.5, das die vom Linux-Magazin gestellte Aufgabe bewältigen soll, betritt ein in Python verfasstes Framework die illustre Runde. Seit Version 1.3 bringt Django die so genannten Class-based Views (CBV) mit, die den Zeitaufwand für das Entwickeln weniger komplexer Aufgaben minimieren. Grundlage für den Code bieten die offizielle Django-Doku [1] und die Anregungen aus dem Referenzwerk “Two Scoops of Django” [2]. Zum besseren Verständnis der hier eingesetzten Techniken empfiehlt sich zudem der Blick in das Tutorial des Django-Projekts [3].

Doch die CBV kosten: Wer sie nutzen will, muss eine steilere Lernkurve akzeptieren als bei den traditionellen Functional-based Views (FBV). Aber durch die Zeitersparnis beim Entwickeln lohnt sich der Mehraufwand am Ende. FBVs ließen sich für die Lösung der Aufgabe aber auch verwenden.

An die Quelle

Um die offene Datenquelle anzuzapfen und die Berliner und Brandenburger Volks- und Straßenfeste [4] auf einer Webseite anzuzeigen, lässt sich das mitgelieferte REST-API mit XML- oder Json-Schnittstellen einsetzen. Das Python-Framework kann zwar mit beiden Datentypen arbeiten, im Artikel kommt jedoch Json als favorisiertes Format zum Einsatz. Um außerdem nicht ständig Requests an den Portalbetreiber zu senden, soll Django die Daten cachen und aufbereiten, falls ein Besucher der Webseite zum Beispiel nach dem Termin für das Berliner Bierfestival [5] sucht.

Um die Daten serverseitig zu holen, gibt es mehrere Ansätze: Zum Beispiel ließe sich das Task-Framework Celery mit dem Plugin »django-celery« einspannen, das einige großartige Funktionen für das Bewältigen periodisch wiederkehrender Aufgaben mitbringt. Allerdings wäre der Konfigurationsaufwand recht hoch, weshalb ein so genannter Management-Befehl diese Aufgabe übernimmt. Ihn ruft der Entwickler direkt aus der Shell heraus auf. Er lässt sich als Cronjob einrichten, der die Daten regelmäßig von der Quelle abholt und aktualisiert. Die Entwicklung dieses Befehls macht den Anfang des Artikels.

Nach dem Einrichten von Django [1] erstellt der Programmierer zunächst eine App mit dem Namen »streetparty« :

django-admin.py startapp streetparty

Der Management-Befehl gehört in einen Ordner mit dem Namen »management« , den er unterhalb von »streetparty« anlegt. Darin legt er zudem eine leere Datei mit der Bezeichnung »__init__.py« und einen weiteren Ordner »commands« an, welcher ebenfalls eine Datei »__init__.py« enthalten soll. An ihr erkennt Python, dass sich in den Ordnern Python-Skripte befinden. Im Verzeichnis »commands« erstellt der Entwickler zudem die Datei mit dem Code zum Holen der Daten. Im Beispiel heißt sie »get_data.py« und enthält den Code aus Listing 1.

Listing 1

get_data.py

01 from django.core.management.base import BaseCommand
02 from streetparty.helper import StrassenFestHelper
03
04
05 class Command(BaseCommand):
06     help = 'Holt die aktuellen Daten der Straßenfeste'
07
08     def handle(self, *args, **options):
09         StrassenFestHelper().update()
10

Management-Befehle ähneln sich stets im Aufbau: Es muss eine Klasse »Command« geben, die mindestens von »BaseCommand« erbt. In der Methode »handle()« wird dann die eigentliche Logik implementiert, wobei Management-Befehle auch Argumente empfangen können. Das Beispiel bindet zusätzlich noch einen Hilfetext ein.

Hilfe beim Straßenfest

Die Methode selbst ruft eine andere Methode der Klasse »StrassenFestHelper« auf, die später implementiert wird und die Daten in die Datenbank schreibt. Diese Klasse in der neu anzulegenden Datei »streetparty/helper.py« schreibt der Django-Entwickler. Den Code für die Helferklasse zeigt Listing 2.

Listing 2

helper.py

01 import sys
02 import json
03 import urllib2
04 from datetime import datetime
05 from django.conf import settings
06 from django.core.exceptions import ValidationError
07 from streetparty.models import StrassenFest
08
09 class StrassenFestHelper():
10     def update(self):
11         req = urllib2.Request(settings.STREETPARTY_DATA_URL)
12         opener = urllib2.build_opener()
13         source = opener.open(req)
14         StrassenFest.objects.all().delete()
15
16         for data in json.load(source)['index']:
17             data['von'] = datetime.strptime(data['von'], '%Y-%m-%d')
18             data['bis'] = datetime.strptime(data['bis'], '%d.%m.%Y')
19             data['api_id'] = data.pop('id')
20             fest = StrassenFest(**data)
21             try:
22                 fest.full_clean()
23             except ValidationError as e:
24                 print >>sys.stderr, 'Failed to validate remote entry with id %s' % data['id']
25                 print >>sys.stderr, str(e)
26             else:
27                 fest.save()

In der vorgegebenen Datenquelle ist ein »id« -Feld vorhanden, allerdings verrät die Dokumentation nicht, ob die IDs eindeutig vergeben sind. Bekommen zwei Feste zufälligerweise die gleiche ID zugewiesen, kann es zu Konflikten kommen. Daher löscht die Funktion »update()« die Datensätze zunächst, um sie dann komplett neu einzuspielen. Im Modell sichert das Feld »api_id« die ID der Datenquelle. Um letztere nicht als Primary Key zu verwenden, setzt die Funktion den Schlüssel für »id« auf »api_id« um.

Formatprüfung

Da man prinzipiell Daten von anderen Seiten auf Korrektheit überprüfen sollte, wird zudem die Methode »full_clean« verwendet. Diese Methode überprüft, ob die Daten grundsätzlich einem Format entsprechen, mit dem Django umgehen kann. So überprüft Django beispielsweise, ob ein »DateField« auch tatsächlich mit einem Datum befüllt ist. Eine andere Möglichkeit bestünde darin, Formular-Klassen zu verwenden, die es erlauben, Input beliebig zu validieren. Im nächsten Schritt lässt sich endlich der Management-Befehl ausführen:

python manage.py get_data

Allerdings ruft der Befehl jetzt noch Fehlermeldungen hervor, weil zunächst ein passendes Datenbank-Modell fehlt und Django aus diesem Grund die Daten nicht ablegen kann. Dieses Modell definiert die Datei »models.py« , die Listing 3 zeigt.

Listing 3

models.py

01 from django.db import models
02
03 # Create your models here.
04 class StrassenFest(models.Model):
05     # so, id cannot overwrite the database-id
06     api_id = models.CharField(max_length=255, blank=True)
07     bezeichnung = models.TextField()
08     # adress-data should normally be normalized
09     bezirk = models.CharField(max_length=255, blank=True)
10     strasse = models.CharField(max_length=255, blank=True)
11     plz = models.CharField(max_length=5, blank=True)
12     von = models.DateField()
13     bis = models.DateField()
14     zeit = models.CharField(max_length=255, blank=True)
15     veranstalter = models.TextField(blank=True)
16     mail = models.EmailField(blank=True)
17     www = models.CharField(max_lenght=255, blank=True)
18     bemerkungen = models.TextField(blank=True)

Modellbaukasten

Djangos Datenbankmodelle sind im Prinzip selbsterklärend. Sie bestehen aus einer Klasse, die mindestens von »models.Model« erbt und ansonsten aus Attributen und Methoden besteht. Die Attribute lehnen sich an die Originalbezeichnungen der Datenquelle an und sind den passenden Feldern zugeordnet.

Der Vorteil korrekter Felder offenbart sich beim Einsatz von Django-Forms: Diese validieren automatisch Daten vor dem Einfügen und geben notfalls passende Fehlermeldungen aus. Das Attribut »api_id« bekommt zusätzlich den Parameter »blank=True« übergeben, der dafür sorgt, dass dieses Feld auch leer bleiben darf. Hat der Entwickler das Modell schließlich implementiert, in die Konfiguration der App eingetragen und über

python manage.py syncdb

die Datenbank synchronisiert, sollte der Management-Befehl erfolgreich arbeiten und die Datensätze eintragen.

Logik bitte!

Als Nächstes geht es um die Logik, mit der Django die Daten in den Templates präsentiert. Diese legt die Datei »views.py« (Listing 4) fest (Abbildung 1). Hier treten nun die bereits angesprochenen Class-based Views in Gestalt einer »ListView« ins Rampenlicht. Das ergibt Sinn, da Django eine Liste von Elementen anzeigen soll. Für die Anzeige einzelner Elemente käme etwa eine »DetailView« zum Zuge. Das verdeutlicht auch den Vorteil von CBV: Gerade mal drei Zeilen Code sind nötig, um die erste Liste mit Daten anzuzeigen. Sehr viel passiert dabei im Hintergrund. Beim FBV-Ansatz muss sich der Entwickler hingegen selbst um viele Dinge kümmern und kann so nicht immer dem DRY-Ansatz (“Don’t Repeat Yourself”) folgen.

Listing 4

views.py

01 from django.views.generic import ListView
02 from django.utils import timezone
03
04 from .models import StrassenFest
05
06 class StrassenFestList(ListView):
07      def get_queryset(self):
08         today = timezone.now()
09         return StrassenFest.objects.filter(von__year=today.year, von__month=today.month).order_by('von')
Abbildung 1: Die vom Autor genutzte IDE heißt Pycharm, im Bild zu sehen ist die geöffnete Datei »views.py«.

Abbildung 1: Die vom Autor genutzte IDE heißt Pycharm, im Bild zu sehen ist die geöffnete Datei »views.py«.

In der View selbst wird zuerst das heutige Datum bestimmt. Das ist wichtig, denn das Template soll später nur die Daten des aktuellen Monats anzeigen. Dann legt der Entwickler für die Klasse ein »queryset« fest. Das versammelt alle Elemente, auf die das Template später Zugriff erlaubt. Dazu ruft Django die Klasse des Modells »StrassenFest« auf, das gerade implementiert wurde.

Die Methode »objects.filter()« schränkt die aus der Datenbank ausgegebenen Elemente ein, mit dem Parameter »von__year« auf die Elemente aus dem aktuellen Jahr. Das Gleiche gilt für die Monate mit »von__month« . Der Befehl »order_by()« ordnet die Elemente.

Mehr ist in den Views anfangs nicht zu tun. Django sucht nun automatisch nach dem passenden Template, in diesem Fall unter »templates/streetparty/strassenfest_list.html« . Es kann aber über das Attribut »template_name« auch eine andere Vorlage wählen. Die Standardauswahl basiert auf dem Namen der Klasse und der hier benutzten Class-based View. Zusätzlich muss der Django-Entwickler den Ordner »templates/streetparty« im »streetparty« -Verzeichnis anlegen.

Selbstdarsteller

Anschließend knöpft er sich die Datei zur Darstellung im Browser vor (Listing 5).

Listing 5

strassenfest_list.html

01 {% extends 'base.html' %}
02 {% block content %}
03
04     <ul class="dataList">
05
06         {% for data in object_list %}
07             <li>
08                 <ul>
09                     <li>{{ data.bezeichnung }}</li>
10                     <li>Von {{ data.von|date:"d.m.Y" }} bis {{ data.bis|date:"d.m.Y" }}</li>
11                 </ul>
12             </li>
13         {% endfor %}
14     </ul>
15
16 {% endblock %}

In »strassenfest_list.html« steht die Liste »object_list« zur Ausgabe in Django bereit. Die Framework-eigene Template Engine, die in den Templates ihren Auftritt hat, lässt sich bei Bedarf austauschen. Django-Projekte bauen ihre Templates meist modular auf, diese Möglichkeit bietet auch die Template Engine.

Im Beispiel fußt das Template auf einem Basis-Template, von dem es über den Befehl »{% extends %}« erben kann. Im Basis-Template lassen sich dann mit »{% block %}« an verschiedenen Stellen im Markup Blöcke anlegen, etwa für Inhalte oder einzelne CSS-Definitionen. In der Datei »strassenfest_list.html« gibt am Anfang eine Liste die Feste aus. Dank des For-Operator durchläuft das Konstrukt alle Elemente aus »object_list« . Das erlaubt den Zugriff auf einzelne Elemente, in diesem Fall Objekte, zum Beispiel auf das Feld »bezeichnung« über »{{ data.bezeichnung }}« .

Um das Datum in einem lesbaren Format darzustellen, benutzt Zeile 10 den »date« -Filter. Django bietet standardmäßig verschiedene Filter und Tags für Templates an, die man selber erweitern kann.

Nach der Logik passt nun auch die Darstellung. Fehlt noch die URL zum Aufrufen im Browser, also das Routing. Diese URLs muss Django fast immer neu festlegen, unter »streetparty/urls.py« befinden sich die URL-Definitionen für die App (Listing 6).

Listing 6

urls.py

01 from django.conf.urls import patterns, include, url
02 from .views import StrassenFestList
03
04 urlpatterns = patterns('',
05     url(r'^$', StrassenFestList.as_view(), name='feste'),
06 )
07

Das Beispiel braucht nur eine URL, die sich aus der relativen URL, der aufzurufenden View und dem Namen der URL zusammensetzt. Die URL selbst kann mit Hilfe regulärer Ausdrücke Parameter abfangen und an die View weitergeben. Der Name ist nötig, um nicht direkt die URL in die Templates schreiben zu müssen. Django soll sich die URL selbst zusammenbauen, denn so lässt sie sich ohne Nachteile für die Nutzbarkeit ändern. Verwendet der Entwickler CBV, muss er eine CBV-Methode aufrufen, konkret die Methode »as_view()« . Ruft er das Beispiel jetzt im Browser auf, müsste sich unter der entsprechenden URL eine Liste mit Veranstaltungen in diesem Monat befinden (Abbildung 2).

Abbildung 2: Django zeigt nun eine Liste der geplanten Feste an. Diese lässt sich jetzt in das passende Stylesheet der Webseite einbetten.

Abbildung 2: Django zeigt nun eine Liste der geplanten Feste an. Diese lässt sich jetzt in das passende Stylesheet der Webseite einbetten.

Soll der Nutzer nur die Veranstaltungen in einem bestimmten Zeitraum finden, also zwischen zwei Daten, kommen der Einfachheit halber zwei Input-Felder vom Typ »date« und mit den Namen »von« und »bis« zum Einsatz, die Listing 7 ergänzt. Diese Felder sollten sich in einem Formular befinden. Als Formular-URL dient der URL-Befehl der Template Engine: »{% url “feste” %}« . Das Formular braucht aus Sicherheitsgründen ein CSRF-Token [6], das der Programmierer mit »{% csrf_token %}« einbindet. Vergisst er dies, nimmt Django die »POST« -Daten nicht an – ein Sicherheitsaspekt, an dem er nicht vorbei kommt.

Listing 7

strassenfest_list.html

01 {% extends 'base.html' %}
02 {% block content %}
03     <form action="{% url "feste" %}" method="POST">
04     {% csrf_token %}
05     <input type="date" name="von"/>
06     <input type="date" name="bis"/>
07     <input type="submit"/>
08     </form>
09
10     <ul class="dataList">
11
12         {% for data in object_list %}
13             <li>
14                 <ul>
15                     <li>{{ data.bezeichnung }}</li>
16                     <li>Von {{ data.von|date:"d.m.Y" }} bis {{ data.bis|date:"d.m.Y" }}</li>
17                 </ul>
18             </li>
19         {% endfor %}
20     </ul>
21
22 {% endblock %}

Das Formular nutzt dieselbe URL wie für die eigentliche Seite, um die gleiche View aufzurufen. Will der Entwickler die Daten verarbeiten, muss er die View erweitern, indem er die Klasse »StrassenFestList()« überschreibt (Listing 8).

Listing 8

views.py

01 # überschreibt Klasse aus Listing 4
02 class StrassenFestList(ListView):
03     def get_queryset(self):
04         qs = StrassenFest.objects.order_by('von')
05         form_data = self.request.POST
06         if 'von' in form_data and 'bis' in form_data:
07             qs = qs.filter(von__gte=form_data['von'], bis__lte=form_data['bis'])
08         else:
09             today = timezone.now()
10             qs = qs.filter(von__year=today.year, von__month=today.month)
11         return qs
12
13     def post(self, *args, **kwargs):
14         return self.get(*args, **kwargs)

Das Skript ruft die »post()« -Methode der Klasse »ListView« auf, sobald »POST« -Daten übergeben werden. Letztere speichert ein Request-Objekt und macht sie so für die Methode nutzbar. Das Beispiel verwendet einen neuen Filter. Der Befehl »von__gte« steht für “Gib mir alle Elemente, die größer als oder gleich dem folgenden Datensatz sind”. Der Datensatz ist das Anfangsdatum des Filters. Beim Enddatum verhält es sich ähnlich, »lte« steht hier für kleiner oder gleich. Zeile 13 und 14 übergeben die gefilterten Daten noch dem Kontext und rufen die Methode auf, die das Template dann rendert.

Fazit

Django ist durch seinen modularen Aufbau eher für große Projekte konzipiert, dank der CBV und der automatisierten Logiken dahinter lassen sich aber auch kleinere Projekte mit wenig Entwicklungsaufwand umsetzen. Hält sich der Entwickler an die Vorgaben und Anregungen der Django-Dokumentation und setzt diese effektiv ein, kann er stabile Anwendungen mit allen Django-Features entwickeln. Dies verlangt einen hohen Lernaufwand, aber die CBV vermeiden unnötigen Boilerplate-Code. Der Entwickler gewinnt Flexibilität und Sicherheit.

Aufgrund seiner Modularität lässt sich das Beispiel auch leicht in andere Projekte integrieren, eine große Stärke von Django. Hinter Django steht eine große Community, die für fast jeden Anwendungszweck Tools und Plugins bereithält. Eine Schwäche liegt im langwierigen Setup-Prozess für neue Projekte: Wild drauf los zu programmieren, ist mit Django schwierig, wie wohl mit den meisten professionellen Frameworks. Wer es kleiner mag, sollte einen Blick auf die Django-Alternative Flask [7] werfen. (kki)

Der Autor

Sven Schannak ist als Unternehmer und leidenschaftlicher Softwareentwickler an der wundervollen Ostsee tätig.

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