Python für Webanwendungen 2: Genshifizierung
Posted by Jesco Freund at Oct. 25, 2010 4:34 p.m.
Die im vorhergehenden Teil vorgestellte Anwendung ist in ihrem Können bisher eher sparsam – keine großartige Anwendungslogik, keine komplexe Steuerung, keine hübsche Darstellung. Genau diesem letzten Askpekt möchte ich mich im zweiten Teil der HowTo-Serie zu Python und Webanwendungen widmen. Für Webanwendungen (fast) unumgänglich ist die Darstellung der Oberfläche mittels (X)HTML. Die finsteren Zeiten, in denen man sich in CGI-Anwendungen das HTML-Dokument noch mittels print() zusammengebaut hat, sind glücklicherweise längst vorbei.
Heute benutzt man dafür Template-Systeme. Grundsätzlich lassen sich Template-Systeme in drei Kategorien einteilen:
- Als Platzhalter-Templates bezeichne ich Template-Systeme, die darauf beruhen, in einem (wie auch immer gearteten) Template-Dokument auf bestimmte Weise gekennzeichnete Platzhalter auszutauschen. Die Verarbeitung dieser Templates basiert meist auf Regulären Ausdrücken oder auf einfachen Suchen/Ersetzen-Operationen.
- XML-Templates würde ich Templates nennen, die einen eigenen XML-Namensraum definieren und dadurch dem xhtml-Namensraum im selben Dokument nicht in die Quere kommen. Die Verarbeitung erfolgt über einen XML-Parser, Xpath und ggf. XSLT-Transformationen.
- Kompilierte Templates kenne ich eigentlich nur aus einem Bereich, und hier stellen sie für mich einen Sonderfall der XML-Templates dar. Das Template wird beim Start der Applikation geladen und in eine Controller-Funktion umgewandelt, die aus den im Template eingebetteten Code-Schnipseln und print()-Anweisungen für die HTML-Dekoration besteht.
Für Python steht eine Reihe von fertigen Template-Systemen aus den Kategorien Platzhalter- und XML-Templates zur Verfügung. Gerade bei arbeitsteiliger Entwicklung bietet sich der Einsatz eines XML-Template Systems an. Auch wenn man sich dadurch zunächst etwas mehr Komplexität einhandelt – die höhere Flexibilität macht das in großen Teilen wieder wett. Aus dieser Überlegung heraus kommt in der Beispielanwendung das Template-System Genshi zum Einsatz.
Wer sich die Verzeichnisstruktur im ersten Teil genauer angesehen hat, dem wird bereits aufgefallen sein, dass für die Ablage von Templates bereits ein Unterverzeichnis geschaffen wurde.
+ MyApp/
|
~
|
+--+ themes/
|
+--+ default/
|
+--+ static/
| |
| +--+ css/
| |
| +--+ img/
|
+--+ templates/
Die themes-Verzeichnisstruktur ist dabei der Erkenntnis geschuldet, dass
- für eine ansprechende Darstellung weit mehr benötigt wird als nur HTML-Templates, und
- der „Kleiderwechsel“ für eine Webanwendung idealerweise über die Konfiguration erfolgt, ohne den Quellcode anfassen zu müssen.
Daraus resultieren auch die beiden folgenden Angaben in der CherryPy-Konfigurationsdatei:
[global]
theme.dir = '/home/myuser/Development/MyApp/themes'
theme.name = 'default'
Die Angabe theme.dir gibt den absoluten Pfad zum themes-Verzeichnis an, theme.name verrät, welches der dort abgelegten Themes Verwendung finden soll. Aus der Angabe eines absoluten Pfades (anstelle eines os.path.join(os.path.dirname(__file__), „../themes“)) lässt sich auch erkennen, dass es kein Problem wäre, das gesamte Themes-Verzeichnis an einer völlig anderen Stelle des Dateisystems unterzubringen, doch das nur am Rande. Diese Angaben haben übrigens nichts mit CherryPy oder Genshi direkt zu tun; CherryPy reicht sie aber bequemerweise für uns durch, so dass wir uns nicht um's Einlesen von Konfigurationsinformationen kümmern müssen.
Für eine erste Demonstration benötigen wir nun ein Template; dieses legen wir im templates-Unterverzeichnis des default-Themes unter dem Namen hello.html an.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
xml:lang="en" lang="en">
<head>
<title>CherryPy Environment Test</title>
</head>
<body>
<h1>CherryPy-Test</h1>
<ul py:if="conf">
<li py:for="key in conf.keys()">
<strong>${str(key)}</strong> ${str(conf.get(key))}
</li>
</ul>
</body>
</html>
Das Template entspricht weitgehend einer normalen XHTML-Datei – bis auf die zusätzlichen Namensraumdeklarationen im Root-Element und dem „merkwürdigen“ Konstrukt im Body-Element. Damit jetzt kein Sprung in die Genshi-Dokumentation nötig wird, hier eine kurze Erläuterung, was besagtes Konstrukt tut: Wenn dem Template ein Objekt namens conf übergeben wurde, wird eine unordered list erzeugt. Genauer gesagt wird erwartet, dass es sich bei conf um ein Python Dictionary handelt, das die Methoden keys() und get() versteht, wobei keys() ein iterierbares Objekt (Liste, Tupel) zurückliefern muss. Iteriert wird über die Schlüssel des Dictionary, dabei wird für jeden Schlüssel ein Listeneintrag (<li>) angelegt und in diesem der Schlüssel und der dazugehörige Wert ausgegeben.
Soweit, so gut – doch noch kann unsere Applikation mit dem Template nichts anfangen. Man könnte jetzt auf die Idee verfallen, in einer Controller-Methode (z. B. der bereits erstellten index()-Methode des Root-Controllers) das Template zu laden, zu rendern und das Ergebnis als Rückgabewert auf den Request zurücksenden. Dabei würde jedoch viel redundanter Code entstehen, da so gut wie jede Controller-Methode wohl von einem Template Gebrauch machen wird. Python bietet einen eleganteren Weg, um diese Aufgabe zu lösen – man benötigt nur ein bisschen „Klebstoff“, um ein Template an eine Controller-Methode heften zu können.
Diesen Klebstoff muss man glücklicherweise nicht erst selbst erfinden – im Genshi Tutorial wird man fündig. Erzeugen Sie also im Package myapp ein Python-Modul namens template.py mit folgendem Inhalt:
import os
import cherrypy
from genshi.core import Stream
from genshi.output import encode, get_serializer
from genshi.template import Context, TemplateLoader
loader = TemplateLoader(os.path.join(str(cherrypy.config.get('theme.dir')), str(cherrypy.config.get('theme.name')), 'templates'), auto_reload = True)
def output(filename, method='xhtml', encoding='utf-8', **options):
"""Decorator for exposed methods to specify what template they should use
for rendering, and which serialization method and options should be
applied.
"""
def decorate(func):
def wrapper(*args, **kwargs):
cherrypy.thread_data.template = loader.load(filename)
opt = options.copy()
if method == 'html':
opt.setdefault('doctype', 'html')
serializer = get_serializer(method, **opt)
stream = func(*args, **kwargs)
if not isinstance(stream, Stream):
return stream
return encode(serializer(stream), method=serializer,
encoding=encoding)
return wrapper
return decorate
def render(*args, **kwargs):
"""Function to render the given data to the template specified via the
``@output`` decorator.
"""
if args:
assert len(args) == 1, \
'Expected exactly one argument, but got %r' % (args,)
template = loader.load(args[0])
else:
template = cherrypy.thread_data.template
ctxt = Context(url=cherrypy.url)
ctxt.push(kwargs)
return template.generate(ctxt)
Besondere Aufmerksamkeit verdient hier die Zeile loader = TemplateLoader(… – hier wird auf Informationen aus der CherryPy-Konfiguration zurückgegriffen. Das funktioniert aber nur, wenn der Konfigurationskontext bereits besteht, bevor das templates-Modul importiert wird. Aus diesem Grund wird in scripts/myapp.py erst die CherryPy-Konfiguration initialisiert, bevor ein Import aus dem myapp-Package erfolgt.
Mit dem oben beschriebenen Klebstoff ist es nun ein leichtes, eine Controller-Methode dazu zu bringen, ein bestimmtes Template zu rendern und auszugeben. Zur Demonstration hier noch mal der Root-Controller (myapp/controller.py):
import cherrypy
from myapp import template
class Root(object):
@cherrypy.expose
@template.output('hello.html')
def index(self):
return template.render(conf = cherrypy.config)
Um eine Controller-Methode mit einem Template zu verbandeln, genügt es also, das Template über den template.output-Dekorator zuzuweisen und innerhalb der Controller-Methode template.render() aufzurufen. Hier im Beispiel wird der render-Funktion mit dem Keyword-Argument conf der CherryPy-Konfigurationskontext übergeben. Wir erinnern uns: Das Template verarbeitet ein Dictionary-Objekt, das unter dem Namen conf übergeben wurde und zeigt dessen Inhalt an.
Hübsch machen geht jetzt also – aber was, wenn die Schönheit nicht für aller Augen gedacht ist? Im dritten Teil wird daher gezeigt, wie man den Zugriff auf einzelne Controller-Methoden nur authentifizierten Usern erlaubt.
No comments | Defined tags for this entry: CherryPy, code, development, Genshi, python
Comments
No comments

Content is subject to the conditions of the