Zope - ein einfaches Content Management System

Brano Ivakovic

... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ...

Übersicht: Erstellung dynamischer Inhalte


DTML

DTML ist Zopes Tag-basierte serverbasierte Präsentations- und Scripting-Sprache (wie z.B. SSI, PHP, PSP und JSP). DTML dient dem Anlegen modularer und dynamischer Web-Schnittstellen, unterstützt die Wiederverwendung von Inhalten und Layout, die Formatierung heterogener Daten und die Trennung der Präsentation von Logik und Daten.
Konkrete Anwendungsfälle sind Aufrufe von Skripten, Suchanfragen oder Formularverarbeitung, und es können neben HTML auch andere Text-Typen generiert werden (z.B. eMail-Nachrichten). DTML ist für die Präsentation von Objekten zuständig und wird von Web-Designern verwaltet, während der Logikaspekt, z.B. komplexe Algorithmen oder Stringverarbeitung mit Hilfe von Python und Perl realisiert werden sollte.

Grundlegende DTML-Tags

DTML-Kommandos werden als Tags geschrieben, die mit dtml- beginnen. Es gibt singleton- und block-Tags (müssen geschlossen werden).

Das var-Tag:
Das var-Tag setzt Variablen in DTML-Skripte und -Dokumente ein, z.B. Strings. Das Verhalten des var-Tags kann über Attribute beeinflusst werden, z.B. ein optionales Attribut mit Standardwert, falls eine Variable nicht gefunden werden kann: <dtml-var Spannweite missing="Spannweite unbekannt">. Manche Attribute haben keine Werte, z.B. kann eine eingesetzte Variable mit dem upper-Attribut in Großbuchstaben konvertiert werden: <dtml-var exclamation upper>. Die wichtigsten Attribute sind html_quote, missing und fmt (Diese Attribute, die Syntax von var-Tag-Ausdrücken und der Umgang mit Python-Ausdrücken werden im Anhang erläutert). Die Suche nach Variablen erfolgt zuerst im aktuellen Objekt, dann in seinen Behältern (i.e.S. Ordner) und schließlich in der Web-Anfrage (Formulare und Cookies). Misslingt die Suche, wird eine Ausnahme ausgegeben und die DTML-Ausführung angehalten. Die Tag-Basierung von DTML kommt darin zum Ausdruck, dass z.B. für mehrfach benutzbare HTML-Fragment wie Header und Footer Tags definiert werden: Beispiel: Es existiere ein Ordner "Futtertaschen" mit dem Titel "Alfreds Lustige Futtertaschen" und ein DTML-Skript "Preisliste" folgenden Inhalts:                                                                                                                                                                 

  => Zope fügt einen Header, Titel und Footer in die resultierende Web-Seite ein:       

Zuerst wird der standard_html_header und -footer erfolglos im aktuellen Objekt (DTML-Skript Preisliste) und in dessen Behälter (Ordner Futtertaschen) gesucht. Erst im Root-Ordner wird Zope  fündig und fügt die Ergebnisse der Skriptausführung ein. Die Variable title wird im Ordner "Futtertaschen" gefunden und eingesetzt.

Das in-Tag: In den meisten Web-Anwendungen wird Iteration angewandt. Das in-Tag von DTML iteriert über eine Folge von Objekten und führt für jedes Objekt einen Prozess-Block aus. Die allgemeine Syntax ist wie folgt:

<dtml-in expr="...">
  Anweisungsblock
</dtml-in>

Die Arbeitsweise des in-Tags und ein Beispiel finden sich im Anhang.

Das if-Tag: Das if-Tag wertet eine Bedingung aus und führt aufgrund des Ergebnisses verschiedene Aktionen aus, was die Anpassung von Web-Seiten ermöglicht (Beispiels-Bedingung im Anhang). Der Block innerhalb des if-Tags wird ausgeführt, wenn die Bedingung wahr ist. if-Tags sind beliebig tief schachtelbar und arbeiten genauso wie if-Statements in Python.

Else- und Elif-Tags: Der if-Block kann auch ein else- oder elif-Singleton-Tag enthalten. Hinter einem else-Tag steht eine Aktion, die bei einer unwahren Bedingung aufgerufen wird. Mit Hilfe des elif-Tags können mehrere Bedingungen in einem Block dargestellt werden können. Elif-Tags können Bedingungen sowohl mit Namens- als auch mit Ausdrucks-Syntax prüfen.


Dynamisch erworbener Inhalt

Der Begriff des 'Erwerbens' (acquisition) bezieht sich auf den dynamischen Gebrauch von Inhalten und Verhaltensregeln aus übergeordneten Objekten. Dieses Prinzip kann z.B. dazu genutzt werden, header für verschiedene Bereiche einer Site einzurichten. Wird z.B. ein neuer standard_html_header in einem Ordner angelegt, ersetzt dieser den allgemeinen header für diesen Ordner und alle Unterordner: Beispiel: Man lege im Ordner "Futtertaschen" einen Ordner mit der id "Grün " und darin ein DTML-Dokument "standard_html_header" an:

      <html>
      <head>
        <style type="text/css">
          body {color: #00FF00;}          
          p {font-family: sans-serif;}  
        </style>
      </head>
      <body>


Dieses HTML-Fragment kann nun als Header benutzt werden. Es benutzt CSS (Cascading Style Sheets), um das Aussehen und Verhalten der Web-Seite zu verändern:


Grün
Abb. 7: Dokument mit eingerichtetem Header

Der Header wird nun von allen Web-Seiten im Ordner Grün und seinen Unterordnern benutzt. Dieser Prozess kann für Unterordner weitergeführt werden. Mit diesem Muster kann das Aussehen und Verhalten (von Teilen) einer Web-Site schnell geändert werden. Zope unterstützt damit wesentliche objektorientierte Prinzipien:




Suchen von Variablen

Der DTML-Namensraum ist eine Sammlung von Objekten, die in einem Stack angeordnet sind. Ein Stack ist eine Liste von Objekten, die durch die Befehle push (Element auf den Stack schieben) und pop (Element vom Stack nehmen) manipuliert werden. Kann ein Name im obersten Objekt des Namensraum-Stacks nicht gefunden werden, wird im nächsten Objekt nachgeschaut usw. Gelingt dies nicht, wird ein Fehler ausgegeben. DTML-
Namensräume werden dynamisch für jeden Request in Zope erzeugt, z.B. beim Aufruf einer DTML-Methode oder -Dokuments
:
 

   
Abb. 8: Anfänglicher DTML-Namensraum-Stack

Das Client-Objekt ist das oberste Element, und der Request das unterste Element im
Namensraum-Stack, und daher das letzte Element, wo nach Namen gesucht wird.

DTML Client-Objekt: Die Art des Client-Objekts in DTML ist davon abhängig, ob eine DTML-Methode oder ein DTML-Dokument ausgeführt wird. Ein DTML-Dokument ist ihr eigenes Client-Objekt und
kann verschiedene Arten von Client-Objekten haben, abhängig von ihrem Aufruf. So ist z.B. bei einer DTML-Methode, die alle Inhalte eines Ordners anzeigt, das Client-Objekt der anzuzeigende Ordner. Es liege z.B. folgende DTML-Methode im Root-Ordner vor:

        ..<dtml-in objectValues>
          <li><dtml-var title_or_id></li>            
        </dtml-in>..


Bei Anwendung dieser Methode auf einen Ordner "Reptilien" (das Client-Objekt) mit der URL
http://localhost:8080/Reptiles/list wird folgendes ausgegeben: Bei der Suche nach der objectValues-Variablen wird die objectValues-Methode des jeweiligen Ordners aufgerufen. Variablen wie Methoden und Eigenschaften werden zuerst im Client-Objekt und danach in den Objektcontainern mittels acquisition gesucht.

DTML Request-Objekt: Das Request-Objekt
enthält spezifische Informationen über den aktuellen Web-Request und ist das unterste Objekt auf dem DTML-Namensraum-Stack. Bei der Suche nach Variablen im Request werden folgende Informationsquellen nacheinander zu Rate gezogen: CGI-Umgebung, Formulardaten, Cookies, Zusätzliche Variablen


Veränderung des DTML-Namensraums:
Einige DTML-Tags verändern den DTML-Namensraum während ihrer Ausführung. Zu diesen Tags gehören das in-Tag, das with-Tag und das let-Tag.

Das in-Tag: Wenn das in-Tag über eine Sequenz iteriert, wird das aktuelle Element in der Sequenz für die Ausführungsdauer der Anweisungen im Block auf die oberste Stelle des Namensraum-Stacks geschoben und nach Ablauf wieder herunter genommen:
      


Das with-Tag: Das with tag schiebt ein anzugebendes Objekt
für die Ausführungsdauer des with-Blocks an die oberste Position des Namensraum-Stacks. Hierdurch wird angegeben, wo Variablen zuerst nachgeschaut werden sollen. Die Namen eines Ordners "Reptilien" könnten über Python-Ausdrücke wie folgt zugegriffen werden:

Das with-Tag verbessert die Lesbarkeit:  

Ein anderer Einsatzzweck für das with-Tag ist, das Request-Objekt oder einen Teil davon auf die oberste Stelle des Namensraum-Stacks zu schieben. Es liege z.B. ein Formular mit einem Input namens "id" vor. Der Zugriff <dtml-var id> liefert nicht die id-Variable des Formulars, sondern die des Client-Objekts. Auf folgende Weise wird sichergestellt, dass man die Formular-id erhält:

        <dtml-with expr="REQUEST.form">
          <dtml-var id>
        </dtml-with>


Das let-Tag: Das Let-Tag befördert einen neuen Namensraum auf den Namensraum-Stack. Dieser Namensraum ist definiert durch die Tag-Attribute des let-tags:

        <dtml-let person="'Bob'" relation="'uncle'">
          <p><dtml-var person>'s your <dtml-var relation>.</p>       =>
Das würde anzeigen: <p>Bob's your uncle.</p>
        </dtml-let>


Fortgeschrittene DTML-Tags

Der Vollständigkeit halber werden an dieser Stelle weitere wichtige DTML-Tags zusammengestellt:


call
Aufruf von Methoden ohne Einfügen Ihrer Rückgabewerte in die Ausgabe
comment
Zu Dokumentationszwecken. Der Comment-Block wird aus der Ausgabe entfernt
tree
Erzeugung dynamischer Bäume in HTML zur Darstellung hierarchischer Daten. Die Navigationsansicht von Zope ist mit Hilfe des tree-Tags realisiert
return
Spezifikation eines Rückgabewertes
sendmail
Formatierung und Senden von Email-Nachrichten. Kann benutzt werden, um eine Verbindung zu einem Mail Host herzustellen
mime
Formatierung von Daten mit MIME . Damit können Emails mit Anhängen in Zope verschickt werden
unless
Gegenstück zum if-tag, d.h. Ausführung eines Blockes, wenn die Bedingung nicht wahr ist
raise
Manuelles Generieren einer Ausnahme, z.B. um einen Fehler zu signalisieren 
try
Ausnahmebehandlung 
Tab. 4: Fortgeschrittene DTML-Tags


DTML-Dokumente vs. DTML-Methoden


Eine Quelle für Missverständnisse ist die Frage, wann ein DTML-Dokument und wann eine DTML-Methode zu verwenden ist. Beide enthalten DTML und anderen Inhalt, beide führen DTML-Code aus, und beide haben eine ähnliche Benutzerschnittstelle und ein ähnliches API. Die folgenden
allgemeinen Hinweise helfen bei der Entscheidung:

DTML-Dokumente speichern Dokument-ähnliche Inhalte, z.B. könnten Kapitel eines Buchs in einem DTML-Dokument gespeichert werden. Allgemein gilt: Ist der Inhalt hauptsächlich Dokument-orientiert und soll er auf einer Site von anderen Objekten dargestellt werden, dann sollte er in ein DTML-Dokument kommen (oder eine Datei, wenn kein DTML-Scripting benötigt wird).
DTML-Methoden sind dazu gedacht, andere Objekte zu verändern und darzustellen. DTML-Methoden speichern normalerweise nicht viel Inhalt, es sei denn, der Inhalt soll sich ändern oder anderen Inhalt manipulieren.
Also gilt: DTML-Methoden, wenn es einfache Logik ist oder andere Objekte darzustellen sind.
Python- oder Perl-basiert Skripte: Ratsam für die Codierung komplexen Verhaltens


Python- und Perl-Skripte

Zope-Skript-Objekte enthalten Code, der in Python oder Perl geschrieben ist und werden zum Schreiben von Programm-Logik verwendet. Python und Perl sind sehr beliebte und leistungsfähige Open-Source Programmiersprachen mit den gemeinsamen Merkmalen: leistungsfähige, rasche Entwicklung, einfache Syntax, viele Add-On-Bibliotheken, starke Unterstützung durch Benutzergemeinde und ein reichhaltiges Angebot an kostenloser Online-Dokumentation.

Skripte aufrufen: Zope-Skripte werden über das Web oder andere Skripte oder Objekte aufgerufen. Sie können problemlos untereinander ausgetauscht werden, womit eine hohe Flexibilität gewährleistet wird. Ein Skriptaufruf erfolgt im Kontext des Objekts, an dem die Aufgabe ausgeführt werden soll. Vereinfacht gesagt, wird das Skript auf dem Objekt aufgerufen.

Skripte aus dem Web aufrufen: Ein Skript kann direkt aus dem Web über seine URL aufgerufen werden. Durch Benutzung verschiedener URLs können einem Skript verschiedene Kontexte   übergeben werden. Dadurch kann Logik auf Objekte angewendet werden, ohne dass der Code dazu im Objekt eingebettet sein muss. Um ein Skript auf einem Objekt aus dem Web aufzurufen, wird einfach die URL des Objekts besucht, gefolgt vom Skriptnamen. Angenommen, es liegt folgende Sammlung von Objekten und Skripten vor:


Skriptaufruf
Abb. 9: Eine Sammlung von Objekten und Skripten

Über die URL
Zoo/GroßeTiere/Giraffe/füttern wird das Skript füttern auf dem Objekt Giraffe aufgerufen, über Zoo/KleineTiere/Springmaus/füttern auf dem Objekt Springmaus. Zope zerlegt die URL und vergleicht sie mit der Objekthierarchie, indem es sich rückwärts durcharbeitet, bis es eine Übereinstimmung für jeden Teil findet. Dieser Prozess wird URL-Traversierung (URL traversal) genannt. Im Falle der URL Zoo/GroßeTiere/Giraffe/füttern beginnt es beim Root-Ordner und sucht nach dem Objekt 'Zoo', darin nach 'GroßeTiere' und dann nach 'Giraffe'. Im Objekt Giraffe wird das Objekt füttern nicht gefunden, aber durch Erwerbung (acquisition) im Ordner Zoo.
Der Vorgang der acquisition läuft wie folgt ab: Wird das gesuchte Objekt nicht gefunden, erfolgt ein Rückzug entlang des URL-Pfades. Im Beispiel ist der Suchweg für das Objekt füttern: Giraffe - GroßeTiere - Zoo, wo schließlich füttern am Ende der URL gefunden und in seinem Kontext aufgerufen wird, der auf dem vorletzten gefundenen Objekt beruht, dem Objekt Giraffe. Soll in einer komplexeren Anordnung das Skript impfen auf dem Objekt Nilpferd aufgerufen werden, muss der Pfad zum Skript als Teil der URL eingegeben werden. Die URL zur Impfung des Nilpferds ist Zoo/Tierarzt/GroßeTiere/Nilpferd/impfen, die wie folgt durchlaufen wird: 1.
Suche Objekt Zoo im Root-Ordner - Wechsel in Ordner Zoo 2. Suche Objekt Tierarzt im Ordner Zoo - Wechsel in Ordner Tierarzt 3. Suche Objekt GroßeTiere -> GroßeTiere wird in Ordner Tierarzt nicht gefunden 4. Wechsel in den Ordner GroßeTiere über den Behälter Zoo, 5. Suche Objekt Nilpferd im Ordner GroßeTiere - Wechsel zum Objekt Nilpferd, 6. Suche Objekt impfen im Objekt Nilpferd -> Objekt impfen in Objekt Nilpferd und seinen Behältern nicht gefunden, 7. Suche Objekt impfen im Ordner Tierarzt - Objekt impfen gefunden. Damit ist das Ende der URL erreicht und impfen wird im Kontext des Objekts Nilpferd angewandt.

Skripte von anderen Objekten aufrufen:
Üblicherweise werden Skripte aus DTML-Skripten heraus mit  dem call-Tag: <dtml-call SkriptName> aufgerufen. Hierbei braucht nicht angegeben werden, wie das Skript implementiert ist (genauso gut können andere DTML-Objekte oder SQL-Skripte auf diese Art aufgerufen werden). Benötigt ein Skript Parameter, muss entweder ein Name für die DTML-Namensraum-Bindung ausgewählt werden oder die Parameter in einem Ausdruck übergeben werden: <dtml-call expr="aktualisiereInfo(farbe='braun', muster='gefleckt')">

In Python lautet der Aufruf desselben Skriptes mit Parametern: context.aktualisiereInfo(farbe='braun', muster='gefleckt');
Und in Standard-Perl-Semantik:                                           $self->aktualisiereInfo(farbe => 'braun', muster => 'gefleckt');

Skripte von anderen Skripten aus zu benutzen, ist dem Aufrufen von Skripten aus dem Web sehr ähnlich. Die Semantik ist etwas anders, aber die Erwerbungs-Regeln bleiben die gleichen.
Liegt z.B. das impfen-Skript als Python-Skript vor (impfeNilpferd.py), erfolgt der Aufruf von impfen auf dem Objekt Nilpferd
In Python wie folgt: context.Tierarzt.GroßeTiere.Nilpferd.impfen();
In Perl:                  $self->Tierarzt->GroßeTiere->Nilpferd->impfen();


Python-basierte Skripte werden durch Auswahl von "Script (Python)" in der Produktauswahlliste angelegt
. Unter "Edit" können Parameter eingegeben und Inhalte des Skriptes kontrolliert werden. Unter "Test" können Skripte durch Vorgabe von Paramtern und Überprüfung der Rückgabewerte getestet werden. Für weitergehende Details und ein Tutorial siehe http://www.python.org verwiesen. 


Beispiel: Verarbeitung von Formulareingaben

Ein einfaches Beispiel für die Verwendung von Skripten ist ein Online-Webformular zur Berechnung des Zinseszinsbetrages von Schulden. Es werden folgende Informationen benötigt: Der aktuelle Saldo (oder Schulden) - "kapital",  Jahreszinssatz als Dezimalzahl (beispielsweise 0,095) - "zinssatz", Zahl der Zeitpunkte in einem Jahr, zu denen verzinst wird (normalerweise monatlich) -  "perioden" und Anzahl an Jahren ab jetzt, die berechnet werden soll - "jahre".
Die Berechnung schließt folgende Prozedur ein: Division des "zinssatz" durch "perioden" (normalerweise 12) - Ergebnis "i", Multiplikation von "perioden" mit "jahre" - Ergebnis "n", Potenzierung von (1 + "i") mit dem Exponenten "n", Multiplikation des Ergebnisses mit dem "kapital" => Dies ist der neue Saldo (oder die neuen Schulden)

Es werden zwei DTML-Methoden zinsFormular und zinsDarstellung benötigt, um die Informationen vom Benutzer abzufragen bzw. sie darzustellen, außerdem ein Python-Skript berechneZinseszins, das die tatsächliche Berechnung durchführt.
Hier ist eine beispielhafte Seitenvorlage zinsFormular zur Abfrage von "kapital", "zinssatz", "perioden" und "jahre":

      <html>
        <body>
        <form action="zinsDarstellung" method="POST">
        <p>Bitte geben Sie die folgende Informationen ein:</p>
        Ihr aktueller Saldo (oder Schulden): <input name="kapital:float"><br>
        Ihr Jahreszinssatz: <input name="zinssatz:float"><br>
        Verzinsungsperioden pro Jahr: <input name="perioden:int"><br>
        Anzahl der Jahre: <input name="jahre:int"><br>
        <input type="submit" value=" Berechnen "><br>
        </form>
        </body>
      </html>


Dieses Formular ruft die zinsDarstellung auf. Nun wird ein Python-Skript berechneZinseszins erstellt, dass die Parameter "kapital", "zinssatz", "perioden" und "jahre" aufnimmt:

      ## Script (Python) "berechneZinseszins"
      ## Parameter = kapital, zinssatz, perioden, jahre
      ##
      """
      Zinseszinsrechnung
      """
      i = zinssatz / perioden
      n = perioden * jahre
      return ((1 + i) ** n) * kapital

Die Parameter werden in das Feld Parameters List und der Code in das Haupttextfeld eingegeben. Die Kommentare sind beim Bearbeiten über das Web nicht nötig, aber hilfreich beim Bearbeiten von Skripten über FTP. Dies gibt den Saldo oder die Schulden zurück, der über Zeitraum "jahre" verzinst wurde. Eine Seitenvorlage namens zinsDarstellung ruft berechneZinseszins auf und gibt das Ergebnis zurück:

      <html>
        <body>
        <p>Ihr Gesamtsaldo (oder Ihre Gesamtschulden) einschließlich Zinseszins über
        <span tal:content="years">2</span> Jahre beträgt:</p>
        <p><b><span tal:content="python: here.berechneZinseszins(kapital,
                                                           zinssatz,
                                                           perioden,
                                                           jahre)" >1,00</span> €</b></p>
        </body>
      </html>               
                                           

Nach Eingabe von Saldo oder Schulden in der Seitenvorlage zinsFormular und Klick auf Berechnen schickt zinsFormular die gesammelten Informationen an zinsDarstellung, das wiederum das Python-Skript berechneZinseszins aufruft. Die Darstellungs-Methode verwendet den vom Skript zurückgegebenen Wert bei der entstehenden Darstellung.


... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ...