Benutzerspezifische Werkzeuge
Sie sind hier: Startseite Support Plone (Onlinebuch) 13. Entwickeln mit Archetypes
Die maximale Bildbreite kann wie folgt gewählt werden:

13. Entwickeln mit Archetypes

Entwickeln mit Archetypes

Archetypes ist ein Framework, um die Entwicklung von Plone-Produkten zu automatisieren. Wenn einmal eine formale Beschreibung für einen Inhaltstyp vorliegt, erledigt Archetypes praktisch alles, einschließlich der Erstellung von Views und Editformularen für den Entwickler. Dies erlaubt Ihnen, neue Inhaltstypen schnell und mit einer minimalen Codegröße zu entwickeln. Weniger Code bedeutet weniger Fehlerquellen, weniger Code, den Sie verwalten müssen, wenn Plone sich ändert, schnellere Entwicklungszyklen und generell weniger Kosten.

Da das gesamte Produkt auf der Objektbeschreibung (Schema) basiert, ermöglicht es Ihnen, Tools zur automatischen Codegenerierung zu verwenden. Beispielsweise können Sie mit ArchGenXML, das später in diesem Kapitel behandelt wird, aus einem UML-Modell (Unified Modeling Language), das Sie mit einem UML-Designer Ihrer Wahl erstellt haben, ein lauffähiges Plone-Produkt generieren. Sie haben am Schluss ein lauffähiges Plone-Produkt vorliegen, ohne eine Zeile Code programmiert zu haben. Wenn Sie das Gefühl gehabt haben, dass sich das Schreiben von Produkten in Plone im Kapitel 12 ein wenig nach harter Arbeit anfühlt, wird dieses Kapitel eine willkommene Erleichterung bringen.

Dies bedeutet nicht notwendigerweise, dass Archetypes für jede Art von Produkten das Mittel der Wahl ist; manchmal ist Archetypes für den Zweck nicht optimal. Zum Beispiel hatte in einem Fall mein Inhaltstyp nur ein Feld, aber 16 unterschiedliche Permutationen auf den Daten des Feldes zu präsentieren, was bedeutete, dass nur wenig vom Archetypes-Framework benutzt wurde. Zugegeben, das war ein extremer Fall. Auch ist Archetypes nicht primär gedacht, um Low-level-Programmieraufgaben abzudecken. Aber in den allermeisten Fällen werden Sie sehen, dass Archetypes genau das ist, was Sie brauchen.

Einige Menschen beschweren sich, dass Archetypes das Leben für den Programmierer zu leicht mache und dass es dadurch schwer sei, Geld für eine Arbeit zu verlangen, die nur zehn Minuten dauert. Persönlich hatte ich nie ein Problem damit, denn Archetypes hat mir oft aus einer Sackgasse geholfen, wenn sich plötzlich die Projektanforderung von vier auf vierzehn Inhaltstypen ändert. Hier macht sich die Flexibilität der Archetypes bezahlt.

Eine Anekdote, von der ich einmal gehört habe, betrifft eine Webentwicklungsfirma. Wenn diese Firma Kundenbesuche macht, nehmen sie einen Programmierer mit. Während der Kunde seine Anforderungen vorträgt, tobt sich der Programmierer im Archetypes-Framework aus, und am Ende der Besprechung steht ein schneller Prototyp zur Demonstration zur Verfügung.

Insgesamt gesehen haben die meisten Plone-Entwickler Archetypes als den Weg zur Produktentwicklung akzeptiert, wodurch sich bereits eine große Wissensbasis entwickelt hat und Archetypes sich als Standard in der Plone-Entwicklung etabliert hat. Einige der Schlüsseleigenschaften von Archetypes sind:

  • Archetypes erzeugt automatisch Seiten zum Ansehen und Editieren, so dass man zu einem gewissen Ausmaß ohne Schreiben von Template-Code auskommt.
  • Für jedes Objekt wird automatisch eine systemweit eindeutige ID verwaltet, die der Benutzer nicht ändern kann. Dies bedeutet, dass Objekte wiedergefunden werden können, auch nachdem sie innerhalb des Systems verschoben worden sind. Diese UIDs kommen auch bei Referenzen zwischen Objekten zum Einsatz.
  • Es erzeugt Referenzen zwischen Objekten. Jedes Objekt kann beliebige Referenzen zu anderen Objekten haben. Zum Beispiel können Sie eine beliebige Anzahl von Linkobjekten an ein NewsItem hängen (natürlich nur, wenn dieses mit Archetypes entwickelt wurde).
  • Es besitzt standardmäßige Sicherheitseinstellungen. Die gesamte Sicherheitsarbeit wird für Sie erledigt. Wenn Sie also mit den Standardeinstellungen zufrieden sind (und das ist meistens der Fall), brauchen Sie dahingehend nichts manuell entwickeln.
  • Es bietet optional alternative Speichermöglichkeiten für Attribute. So können Sie zum Beispiel bestimmte Attribute in eine relationale Datenbank speichern statt in die ZODB.
  • Es steht ein Framework zur transparenten Transformation von Feldinhalten zur Verfügung, zum Beispiel eine automatische Umwandlung von Word-Dokumenten in HTML.

Von seinem prinzipiellen Aufbau her ist Archetypes nicht notwendigerweise Plone-spezifisch, es könnte also zum Beispiel direkt in Zusammenhang mit dem Content Management Framework (CMF) verwendet werden. Allerdings kommt es derzeit nur in der Plone-Entwicklung zum Einsatz. Allerdings bringt die schemabasierende Entwicklung eine gewisse Zukunftssicherung mit sich, da in mittlerer Zukunft Plone auf Zope 3 portiert werden wird, und wenn das Archetypes-Programmiermodell dort verfügbar ist, wird eine Anpassung von Plone-Produkten wesentlich einfacher sein.

In diesem Kapitel werde ich die Erstellung von neuen Inhaltstypen mit Archetypes behandeln. Dieses Kapitel bringt tatsächlich all die Informationen, die Sie in den letzten Kapiteln gesammelt haben, in einen Zusammenhang und führt schnell durch einige grundlegende Konzepte. Nach einer Anleitung zum Installieren von Archetypes führe ich Sie in die Erstellung eines einfachen Inhaltstyps ein.

Einführung in Archetypes

Archetypes wird mit den Installationspaketen von Plone geliefert, daher ist es wahrscheinlich, dass Archetypes bereits in Ihrer Plone-Installation enthalten ist. Wenn Sie sich nicht sicher sind, ob das der Fall ist, sollten Sie überprüfen, ob Archetypes in der Produktliste im ZMI im Control Panel aufscheint. In den Codebeispielen habe ich Archetypes 1.2.5 verwendet, da diese Version mit Plone 2.0 geliefert wird. Mittlerweile ist Archetypes in der Version 1.3.x erhältlich, die einige Erweiterungen bietet, auf die ich an entsprechender Stelle hinweisen möchte.

Um Archetypes zu installieren, gehen Sie zur Website http://sf.net/projects/archetypes und holen sich von dort die entsprechende Release von Archetypes. In meinen Beispielen verwendete ich archetypes-1.2.5-rc4.tgz.

$ tar -zxf archetypes-1.2.5-rc4.tgz
$ cd archetypes-1.2.5-rc4l

An diesem Punkt werden Sie entscheiden, was zu installieren ist. Das Minimum, das zu installieren ist, sind das Archetypes-Verzeichnis sowie generator, validation und PortalTransforms. Ab Archetypes 1.3 kommt hier noch MimetypesRegistry hinzu. Zur Installation schieben Sie diese in das Products-Verzeichnis Ihrer Plone-Installation. In meinem Fall ist dies /var/zope/, also führe ich folgende Befehle aus:

$ mv Archetypes /var/zope/Products
$ mv generator /var/zope/Products
$ mv validation /var/zope/Products
$ mv PortalTransforms /var/zope/Products
$ mv MimetypesRegistry /var/zope/Products

ArchExample und ArchGenXML sind beide optional, und Sie werden sie nicht unbedingt für die Funktion von Plone benötigen. Sie werden aber beide in diesem Kapitel behandelt, so empfiehlt es sich hier, diese zu installieren. In Archetypes 1.3 sind diese Produkte nicht im Basispaket enthalten und müssen von http://sf.net/projects/archetypes heruntergeladen werden.

Um ArchExample zu installieren, verschieben Sie ArchExample wie folgt in das Products-Verzeichnis Ihrer Plone-Installation:

$ cd ..
$ mv ArchExample /var/zope/Products

Wenn Sie ArchGenXML einsetzen wollen, können Sie es an einem beliebigen Ort installieren, von wo Sie es als Kommandozeilenbefehl aufrufen können. Ich installiere es normalerweise ebenfalls in mein Products-Verzeichnis zusammen mit allen anderen Produkten. Dort stört es nicht, und ich finde es leicht, wenn ich es zum Generieren von Produkten benötige. Ich installiere es mit folgendem Befehl:

$ mv ArchGenXML /var/zope/Products

Wie in der ArchGenXML-Dokumentation erwähnt wird, braucht ArchGenXML das PyXML-Modul für Python. Wenn Sie den Windows- oder Mac-Installer für Plone benutzt haben, ist PyXML bereits installiert. Wenn nicht, können Sie es von http://pyxml.sf.net beziehen. In meinem Fall installierte ich die Version 0.8.3, deshalb führte ich nach dem Herunterladen folgende Befehle aus:

$ tar -xvf PyXML-0.8.3.tar.gz
$ cd PyXML-0.8.3
$ python setup.py install

Hinweis

Normalerweise benötigen Sie unter Unix Root-Rechte, um Python-Pakete wie PyXML ins globale Python-Verzeichnis zu installieren.

Nun da Sie alles installiert haben, möchte ich Ihnen einige Beispiele zeigen.

Einblick in Archetypes

Eine ganze Menge von großartigen Beispielen sind für Archetypes verfügbar, deshalb möchte ich anstatt ein neues zu erfinden, ArchExample zeigen, das beim Archetypes-Repository dabei ist. Darin wird ein neuer Inhaltstyp namens Article eingeführt, anhand dessen die Stärken von Archetypes demonstriert werden.

Article.py enthält den Kern des Sourcecodes, und Sie werden sehen, dass der Code ziemlich anders aussieht als die früheren Codebeispiele. Eine wichtige Neuerung ist die Verwendung von Schemata. Der Begriff Schema ist hier angelehnt an den Schemabegriff aus der Datenbankentwicklung.

StringField("blurb",
            searchable = 1,
            widget = TextAreaWidget(),
            ),

Dieses Stück Code zeigt ein Attribut namens blurb innerhalb des Inhaltstyps, das den Datentyp string hat und das im Edit-Formular als TextArea angezeigt werden soll. Ich werde später auf alle Optionen für Felder und Kontrollelemente (Widgets) eingehen. Abbildung 13.1 zeigt den neuen Inhaltstyp Article im Einsatz beim Editieren.

img/13-01.png

Abbildung 13.1. Edit-Formular des neuen Inhaltstyps

Mit lediglich vier Codezeilen wurde zu unserem Inhaltstyp ein Feld hinzugefügt. Für jedes Standard-Formelement in Plone gibt es eine Felddefinition in Archetypes; alle "langweiligen" Aufgaben wie z.B. Feldvalidierung, Fehlerbehandlung, richtige Darstellung werden vom Archetypes-Framework für Sie erledigt. Um die Einfachheit zu demonstrieren, ändern Sie einfach die Beschriftung (Label) des Kontrollelements (Widget) auf Article blurb und deklarieren das Eingabefeld als Muss-Feld (required). Diese Änderungen erreichen Sie durch unten stehenden Code:

StringField("blurb",
            required = 1,
            searchable = 1,
            widget = TextAreaWidget(label="Article Blurb"),
            ),

Es wurde der Parameter required = 1 hinzugefügt, der das Feld zu einem Muss-Feld macht, sowie ein label-Parameter, der an das Widget übergeben wird. Wenn Sie Plone neu starten und einen neuen Artikel hinzufügen, werden Sie Ihre Änderungen sehen. Das Feld blurb hat nun eine andere Beschriftung, und durch den roten Punkt beim Eingabefeld wird deutlich, dass es sich um ein Muss-Feld handelt.

img/13-02.png

Abbildung 13.2. Das veränderte Eingabefeld

Dies ist nicht nur eine kosmetische Änderung, denn sie spiegelt die Modifikation im darunter liegenden Schema wider, und das ist die wirkliche Stärke von Archetypes. Wenn Sie dies mit dem Schreiben von Python-Produkten vergleichen, werden Sie sehen, dass viel von der Knochenarbeit des sich ständig wiederholenden Kodierens entfällt und im Hintergrund durch das Framework erledigt wird. Man kann es auch so formulieren: Wenn Sie für einen Inhaltstyp das Schema definieren können, ist das Erstellen der Software keine Hexerei mehr. Außerdem sind Änderungen an existierenden Inhaltstypen leicht durchzuführen, was einem bei den schnelllebigen Projekten der heutigen Zeit sehr zugute kommt.

Wenn Sie Änderungen am Code durchführen, um die folgenden Beispiele auszuprobieren, müssen Sie Plone neu starten, um jene wirksam zu machen. Mehr Informationen darüber gibt es später im Abschnitt 'Entwickeln mit Archetypes'.

Schemata, Felder und Kontrollelemente

In Archetypes hat jeder Inhaltstyp, der im Prinzip eine registrierte Python-Klasse mit ganz bestimmten Basisklassen ist, ein Schema, das Felddefinitionen enthält, die ihrerseits noch ihre Darstellung über Kontrollelemente (Widgets) definieren.

Abbildung 13.3 zeigt die Beziehung zwischen Schemata, Feldern und Kontrollelementen.

img/13-03.png

Abbildung 13.3. Schemata, Felder und Kontrollelemente

Schemata und das Basisschema

Um ein Schema zu erzeugen, werden die Felddefinitionen in einem Tupel (unveränderliche Liste in Python) an den Konstruktor des Schemas übergeben. Zum Beispiel hat das Article-Schema drei Felder: group, burb und body. Der folgende Code stellt den Beginn der Schemadefinition dar:

Schema((
    StringField('group',
        vocabulary=ARTICLE_GROUPS,
        widget=SelectionWidget(),
        ),
        # other fields here
        )

Es ist möglich, ein Schema aus anderen Schemata zusammenzusetzen. Das passiert auch hier, denn bei jeder Schemadefinition wird das BasisSchema (BaseSchema) zum selbst definierten Schema hinzugefügt.

Das BaseSchema enthält die zwei Elemente, die bei jedem Inhaltstyp in Plone enthalten sind: einen Titel und eine ID (Kurzname), die beide den Konventionen des Plone-Namensschemas entsprechen. In ArchExample passiert dies durch Addieren von BaseSchema zum eigentlichen Schema, zum Beispiel:

schema = BaseSchema +  Schema((
    StringField('group',
                vocabulary=ARTICLE_GROUPS,
                widget=SelectionWidget(),
                ),
    ...

Die Einträge im Schema werden beim Abrufen der Schemadefinition, wenn das Objekt dargestellt wird, in der gleichen Reihenfolge retourniert wie bei der Schemadefinition. Dies bedeutet, dass Sie durch Verschieben der Felddefinitionen innerhalb des Schemas die Reihenfolge in der Darstellung beliebig einstellen können. Deswegen wurde das Basisschema auch am Beginn addiert, damit ID und Titel als erste Felder erscheinen.

Felder

Bis jetzt haben Sie das StringField gesehen, das ein allgemeines Feld für Textinhalte eines Inhaltstyps darstellt. Eine große Anzahl von Feldtypen ist in Archetypes verfügbar, wie Sie in Tabelle 13.1 sehen. Im Laufe der Zeit entstehen immer weitere Felddefinitionen, und wenn Sie es benötigen, können Sie sich Ihre eigenen Feldtypen dazudefinieren.

Jedes Feld hat ein Default-Kontrollelement, das benutzt wird, wenn Sie kein eigenes spezifizieren. Im Falle von StringField ist dies zum Beispiel StringWidget, ein einzeiliges Textfeld. Im vorigen Beispiel habe ich aber ein TextAreaWidget festgelegt, um eine mehrzeilige Eingabe zu bewirken. All diese Feld- und Widget-Definitionen werden vom Modul Products.Archetypes.public importiert, zum Beispiel:

from Products.Archetypes.public import BooleanField

Alle Felder werden auf dieselbe Art instanziiert - durch den Konstruktoraufruf an die Klasse Field mit mindestens dem zwingenden Parameter name. Sie können eine zusätzlich beliebige Anzahl von Schlüsselwortparametern übergeben, zum Beispiel:

from Products.Archetypes.public import IntegerField
# a simple field for age
age = IntegerField('age')

Tabelle 13.1. In Archetypes verfügbare Felder

Name Typ Default-Widget Beschreibung
BooleanField Boolesche Werte ComputedWidget Einfache Speicherung von True oder False für ein Feld.
DateTimeField Datums- und Zeitwerte CalendarWidget Speichert Datums- und Zeitwerte.
FileField Dateien FileWidget Speicherung von größeren Datenpaketen wie etwa großen Textdateien, Word-Dateien oder anderen Binärdaten.
FixedPointField Fixpunktzahlen DecimalWidget Zum Speichern von Zahlen mit fixer Kommastelle (z.B. Geldbeträge).
FloatField Fließkomma DecimalWidget Speichern von Zahlen mit Fließkomma. Entspricht dem Datentyp float in Python.
ImageField Bilddaten ImageWidget Speichert ein Bild und unterstützt automatische Skalierung.
IntegerField Ganzzahl StringWidget Ganzzahlige Werte, entspricht dem Python-Typ int.
LinesField Listen LinesWidget Speichert Listen von Strings.
PhotoField Image PhotoWidget Dasselbe wie ImageField, aber mit mehr Skalierungsstufen.
ReferenceField Referenzen ReferenceWidget Enthält Referenzen zu anderen Objekten.
StringField Zeichenketten (String) StringWidget Zeichenketten für einzeilige Texte.
TextField String TextWidget Dasselbe wie StringField, aber für größere, mehrzeilige Texte. Der Text kann außerdem in andere Formate transformiert werden.
ComputedField Pseudofeld ComputedWidget Ein Pseudofeld, das nicht als Wert gespeichert wird, sondern aufgrund eines Ausdrucks (Expression) ausgewertet wird.

Jeder Feldtyp hat Attribute, die einzelnen Formularfeldern zugewiesen werden können. Zwei dieser Attribute sind bereits vorgekommen: name und widget. name ist der einzige erforderliche Parameter und soll keine Umlaute, Leer- oder Sonderzeichen enthalten. Als Konvention hat sich etabliert, dass Feldnamen immer mit Kleinbuchstaben beginnen. Das Attribut name agiert als ID des Feldes und wird intern verwendet, vergleichbar mit dem id-Attribut von Zope-Objekten.

Tabelle 13.2. Feldattribute

Name Beschreibung Mögliche Werte
accessor Der Name der Zugriffsmethode auf ein Feld. Somit können Sie den Namen einer beliebigen Methode einsetzen, die den Wert dieses Feldes ermittelt (z.B. specialGetMethod). Siehe 'Überschreiben von Standardmethoden' später im Kapitel.
default Der Defaultwert für das Feld, wird beim Instanziieren des Objekts gesetzt. Der Wert sollte dem Datentyp des Feldes entsprechen.
default_method Alternative zu default: Ein Stringwert, der den Namen der Methode enthält, die den default -Wert errechnet. Zum Beispiel kann man damit ein Datumsfeld mit dem aktuellen Datum vorbelegen. Stringwert (zum Beispiel getSpecial Description).
edit_accessor Ähnlich wie accessor gibt dieses Attribut den Namen einer Zugriffsfunktion an, jedoch gibt diese Funktion den Rohwert eines Feldes zurück, der im Gegensatz zum Accessor nicht transformiert wird. Beliebiger Methodenname (zum Beispiel rawGetMethod).
enforceVocabulary Wenn dieses Attribut gesetzt ist, kann ein Feld mit Werteliste (Vokabular) nur Werte annehmen, die im Vokabular enthalten sind. True oder False
index Wenn Sie ein Feld in einem eigenen Katalogindex haben wollen, dann spezfizieren Sie den Indextyp hier als String. Wenn Sie :schema anhängen, wird dies auch als Metadatenspalte hinzugefügt. Der Name eines Indextyps, zum Beispiel KeywordIndex oder KeywordIndex:schema
name Eindeutiger Name des Feldes. Stringwert; per Konvention sollte der Wert mit einem Kleinbuchstaben beginnen, ansonsten muss der Feldname den Python-Namensregeln für Variablen entsprechen (zum Beispiel, description, user_name oder coffee_bag_6)
mode Zugriffsmodus für das Feld; standardmäßig ist ein Feld für Lesen und Schreiben (rw) eingestellt. Für lesenden Zugriff: r, für schreibenden Zugriff: w, für Lesen und Schreiben: rw.
multiValued Legt fest, ob ein Feld Mehrfachwerte enthalten kann, zum Beispiel bei Referenzfeldern. True oder False
mutator Das Gegenstück zum Accessor. Dieses Attribut legt den Namen der Set-Methode fest. Wenn Sie nichts angeben, wird wie beim Accessor eine Standardfunktion dafür auto-generiert. Methodenname (zum Beispiel specialSetMethod)
primary Wenn dieses Attribut auf True gesetzt ist, dann wird dieses Feld zum Primärfeld, dessen Inhalt bei WebDAV- und FTP-Anfragen zurückgegeben wird (zum Beispiel das body-Feld bei Plone-Dokumenten). Pro Inhaltstyp kann es naturgemäß nur ein Primärfeld geben, und wenn mehrere angegeben werden, wird das erste angegebene Primärfeld eines Schemas genommen. True oder False
required Legt fest, ob ein Feld eine Eingabe zwingend vorschreibt. True oder False
schemata Innerhalb von Schemata kann man Felder wiederum gruppieren. Felder mit gleichem schemata-Namen werden zum Beispiel beim Edit-Formular eines Inhaltstyps (base_edit) zu Teilformularen gruppiert. Dies empfiehlt sich speziell bei Klassen mit sehr vielen Feldern. Stringwert
metadata Legt fest, ob ein Feld ein Metadatenfeld ist (z.B Title). True oder False
searchable Legt fest, ob ein Feld für die Volltextsuche mitindiziert wird, indem dieses Feld bei SearchableText mit berücksichtigt wird. True oder False
validators Die Validierer, die in Bezug auf dieses Feld ausgeführt werden. Beim Ändern des Feldinhaltes werden diese Validierer der Reihe nach aufgerufen. Beliebiger Validierer; siehe den Abschnitt 'Eingabevalidierung' später im Kapitel.
vocabulary Eine Auswahlliste von Werten, aus denen der Benutzer den Wert für dieses Feld festlegen kann. Im Formular stehen diese Werte z.B. als Dropdown-Liste zur Verfügung. Liste von Strings (z.B. ["Gruen", "Rot", `` "Blau"]``) oder Liste von Wertepaaren aus Beschriftung und Wert, z.B. (("#00ff00", "Gruen"), ("#ff0000","Rot"), ("#0000ff","Blau"))
storage Legt fest, wie ein Wert abgespeichert werden soll; standardmäßig wird AttributeStorage verwendet, was bedeutet, dass Feldwerte als normale Python-Attribute gespeichert werden. Jede beliebige Storage- Klasse, wie zum Beispiel AttributeStorage oder SQLStorage. Sie können eine vollständige Liste der Storage-Klassen dem Archetypes-API entnehmen. Später im Kapitel wird die Verwendung von SQLStorage näher erläutert.
widget Das Kontrollelement, das für dieses Feld verwendet werden soll. Konstruktoraufruf zu einer passenden Widget- Klasse (z.B. TextAreaWidget())

Nachdem die Felder und deren Attribute behandelt wurden, ist es Zeit, die einzelnen Kontrollelemente zu behandeln.

Kontrollelemente

System Message: INFO/1 (<string>, line 447)

Possible title underline, too short for the title. Treating it as ordinary text because it's so short.

Ein Kontrollelement, auch Widget genannt, enthält die Information, wie ein Feld eines Objekts dargestellt werden soll. Die Auswahl eines Kontrollelements hängt natürlich mit dem Feldtyp zusammen, so kann ein StringField auf verschiedenste Arten dargestellt werden z.B. mit einem StringWidget oder einem TextAreaWidget. Archetypes enthält eine große Zahl von Kontrollelementen, die alle über Products.Archetypes.public importiert werden können. Zum Beispiel:

from Products.Archetypes.public import BooleanWidget

Alle Widgets werden auf dieselbe Art instanziiert - durch einen Konstruktoraufruf an die entsprechende Widget-Klasse mit optionalen Parametern. Zum Beispiel:

from Products.Archetypes.public import IntegerField
from Products.Archetypes.public import IntegerWidget
# a simple field for age
age = IntegerField('age',
       widget=IntegerWidget(label="Your age")
       )

System Message: INFO/1 (<string>, line 465)

Possible title underline, too short for the title. Treating it as ordinary text because it's so short.

Widgets können Extra-Attribute haben, die vom Typ des Widgets abhängen; zum Beispiel kann man bei einem StringWidget ein size-Attribut setzen, das dann im HTML-Code auch als size-Parameter erscheint. Um etwa ein Textfeld mit 20 Zeichen Breite zu definieren, schreiben Sie folgenden Code:

bankAccountNumber = StringField('bank',
   widget=StringWidget(
           label="Bank account number",
           size=20)
         )

Tabelle 13.3 beschreibt alle Widgets, die von Archetypes zur Verfügung gestellt werden.

Tabelle 13.3. Die verfügbaren Widgets

Name Beschreibung Weitere Attribute
BooleanWidget Zeigt eine Checkbox zur Eingabe von Boolschen Werten an.

System Message: INFO/1 (<string>, line 483)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

CalendarWidget Dient zur Eingabe von Datumswerten. Zeigt Eingabefelder für Jahr, Monat, Tag und Uhrzeit sowie ein Popup-Fenster, um den Tag aus einem Kalender auszuwählen.

System Message: INFO/1 (<string>, line 486)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

ComputedWidget Das Widget für ComputedField. Es hat folglich kein Eingabefeld, sondern nur ein Anzeigefeld (sichtbar in base_view).

System Message: INFO/1 (<string>, line 493)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

DecimalWidget Das Widget für das FixedPointField. size
FileWidget Zeigt ein HTML-Element zum Upload von Dateien an; dient als Standard-Widget für das FileField.

System Message: INFO/1 (<string>, line 501)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

IdWidget Einfaches HTML-Eingabefeld für autogenerierte IDs.

System Message: INFO/1 (<string>, line 506)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

ImageWidget Dient zum Anzeigen und Editieren von Bildern. Es gibt einen Parameter namens display_threshold, mit dem man angeben kann, welche Bildgröße (in Bytes angegeben) inline maximal angezeigt werden soll. Wenn die Bildgröße über diesem Wert liegt, wird das Bild nur als Link angezeigt.
IntegerWidget Einfaches Eingabefeld für Ganzzahlen. size
KeywordWidget Dieses Widget zeigt eine Liste von Schlüsselwörtern vom Katalog in einem komplexen Widget, wie Sie es vom Eigenschaften- Reiter kennen, der bei jedem Plone-Objekt zur Verfügung steht.

System Message: INFO/1 (<string>, line 525)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

LabelWidgets Zeigt Beschriftungen (Labels) für Formelemente an. Dieses Widget editiert keine Werte.

System Message: INFO/1 (<string>, line 532)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

LinesWidget Dient zur Eingabe von LinesFields, also Listen von Strings. rows und columns
MultiSelectionWidget Auswahlliste; standardmäßig ist es ein HTML-select Element (Listbox), bei dem mehrere Werte gleichzeitig ausgewählt werden können. format, kann entweder select oder checkbox sein.
PasswordWidget HTML-Element zum Eingeben von Passwörtern.

System Message: INFO/1 (<string>, line 546)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

RichWidget Kontrollelement, das den vom System eingestellten WYSIWYG- Texteditor (Epoz oder Kupu) als RichText-Editor einsetzt. Erlaubt die Eingabe in verschiedenen Formaten, die automatisch transformiert werden. Mögliche Werte sind format, rows, mode, und cols.
ReferenceWidget Auswahlelement, um Referenzen zu anderen Objekten zu editieren.

System Message: INFO/1 (<string>, line 557)

Unexpected possible title overline or transition. Treating it as ordinary text because it's so short.

--

SelectionWidget HTML-Auswahlliste. Wenn der Modus flex ist (Standardwert), wird bei mehr als 4 Auswahloptionen eine HTML-Dropdown-Liste angezeigt, bei kleinerer Zahl eine Liste mit Radiobuttons. format kann einer von den folgenden Werten sein: flex, select oder radio.
StringWidget Eingabeelement für einzeilige Textfelder. size und maxlength
TextAreaWidget Mehrzeiliges Texteingabefeld, das auch das Heraufladen von Content in verschiedenen Formaten ermöglicht. rows, cols, format, divider, append_only

Für jedes der Widgets, die in Tabelle 13.3 aufgelistet sind, beschreibt Tabelle 13.4 die Attribute, die für alle Widgets gelten. Zum Beispiel haben Sie schon das Attribut label gesehen, das die Beschriftung Ihres Widgets festlegt. In Verbindung mit den extra Attributen jedes Widgets haben Sie das komplette Set von Widget-Attributen.

Tabelle 13.4 Attribute der Widgets

Name Beschreibung Mögliche Werte
label Legt die Beschriftung fest, die zusammen mit diesem Feld im Benutzerinterface erscheint. Beliebiger Text, z.B. Beginn für ein Feld mit Namen start_date.
modes Anzeigemodi des Feldes, es gibt hier zwei Modi: view (Ansicht) und edit (Bearbeiten). Liste von Strings; Standardwert: ("view", "edit").
populate Gibt an, ob ein Widget mit dem Wert des Feldes befüllt werden soll. Normalerweise ist dies der Fall, aber in speziellen Fällen wie zum Beispiel bei Passwortfeldern ist es nicht erwünscht, den Feldinhalt angezeigt zu bekommen. Standardmäßig ist der Wert auf True gesetzt. True oder False
postback Wenn beim Ausfüllen des Edit- Formulars ein Fehler auftritt, d.h. eine Validierung fehlschlägt, und man nach der Eingabe wieder auf das Formular geleitet wird, um die Eingabe zu korrigieren, legt dieses Attribut fest, ob ein Widget mit dem bereits eingegebenen Wert wieder befüllt werden soll. Standardmäßig ist dieses Attribut auf True gesetzt, aber zum Beispiel bei Passwortfeldern ist dies nicht erwünscht. True oder False
visible Legt die Sichtbarkeit eines Feldes fest. Es ist ein Python-Dictionary, das zu dem jeweiligen Anzeigemodus die Sichtbarkeit als String angibt. Mögliche Werte sind visible, hidden (als verstecktes HTML-Element angezeigt) und invisible (überhaupt nicht dargestellt). Zum Beispiel bedeutet {'view': 'visible', 'edit': 'hidden' } dass das Feld zwar in der Standardansicht (base_view) angezeigt wird, aber in der Bearbeitungsansicht (base_edit) versteckt wird.
Einige Beispiele für Feld- und Widget-Kombinationen

Dieser Abschnitt zeigt einige nützliche Kombinationen, die oft verwendet werden und als gute Beispiele dienen. In diesem Beispiel soll eine Auswahlliste von Früchten für ein Feld namens fruit zur Verfügung gestellt werden. Als Feldtyp wird hier StringField mit einem eigenen Vokabular genommen und als Widget das SelectionWidget, das für das Darstellen der Auswahlliste verantwortlich ist:

StringField('fruit'
    vocabulary = ["Apple", "Orange", "Mango", "Banana"],
    widget = SelectionWidget(label = "Favourite Fruit")
    )

Das ImageField ist sehr nützlich, um einen Inhaltstyp mit Bildern zu versehen. Hier ein einfaches Beispiel für ein ImageField:

ImageField('portrait',
    widget = ImageWidget(label = "My picture"),
    )

Das folgende Beispiel zeigt einen etwas komplizierteren Inhaltstyp. Die meisten Inhaltstypen haben ein Primärfeld, das Daten enthalten kann, wie zum Beispiel der Inhaltstyp Document, wo body das Primärfeld ist. Dieses Body-Feld ist der Haupttext des Inhaltstyps. Um dies zu erreichen, muss man nur wenige Attribute setzen.

Zuerst solte dieses Feld volltextindiziert sein, was durch das Setzen des searchable-Attributs erreicht wird. Außerdem sollte dieses Feld bei FTP- und WebDAV-Anfragen zurückgegeben werden, deshalb setzen Sie hier das Attribut primary auf 1. Lesen Sie mehr dazu im Kasten 'Das Primary Field: Marshaling und Behandeln von FTP- und WebDAV-Anfragen'. Wenn dieses Feld verschiedene Inhaltstypen akzeptieren soll, können Sie diese mit dem Attribut allowable_content_types einstellen. (Zur Information:. allowable_content_types bezieht sich hier nicht auf Plone-Inhaltstypen, sondern auf mögliche MIME-Types des Textfeldes). Durch die Verwendung von RichWidget wird außerdem das Editieren des Textes mit dem im System eingestellten WYSIWYG-Editor (Epoz oder Kupu) unterstützt.

TextField('body'
          searchable = 1,
          primary = 1,
          default_output_types = 'text/html',
          allowable_content_types = ('text/plain',
                                     'text/structured',
                                     'text/restructured',
                                     'text/html'),
          widget = RichWidget(label = 'Body'),
          )
Validieren von Eingaben

Obwohl die automatisch generierten Formulare von Archetypes das Editieren von Objekten korrekt behandeln und auch Fehler wie etwa das Nichtausfüllen von zwingenden Feldern abfangen, wird manchmal eine detailliertere Validierung von Feldeingaben benötigt. Um das zu erreichen, kann man pro Feld eine Kette von Validierern festlegen, die vor dem Speichern des Wertes der Reihe nach aufgerufen werden.

Um dies zu erreichen, ordnen Sie dem Feld den Validierer IsInt mit dem validators Parameter zu, wie das folgende Beispiel zeigt:

from Products.Archetypes.public import IntegerField
from Products.Archetypes.public import IntegerWidget
# a simple field for age
age = IntegerField('age',
      validators=("isInt"),
      widget=IntegerWidget(label="Your age")
      )

Woher kommt IsInt? IsInt ist der Name des Validierers, der im validation-Framework registriert ist; in diesem Paket ist ein Satz von vordefinierten Validierern enthalten, die in Tabelle 13.5 aufgelistet sind. Um mit den genaueren Details vertraut zu werden, empfiehlt es sich, den Sourcecode des Pakets in Products/validation/validators/__init__.py zu studieren. Einige der Validierer sind spezifisch für die USA, so dass man sie im europäischen Markt nur bedingt einsetzen kann, wie zum Beispiel die Überprüfung, ob ein String eine gültige US-Sozialversicherungsnummer ist.

Tabelle 13.5. Verfügbare Validierer

Name Beschreibung
isDecimal Überprüft, ob der eingegebene Text einer Dezimalzahl entspricht. Beinhaltet positive und negative Zahlenliterale sowie die Exponentialnotation.
isInt Überprüft, ob der Text eine Ganzzahl ist.
isPrintable Überprüft, ob ein Text druckbar ist, also nur aus Buchstaben, Ziffern und dem Dollarzeichen besteht.
isSSN Prüft, ob ein Text eine gültige US-amerikanische Sozialversicherungsnummer ist.
isUSPhoneNumber Prüft, ob ein Text eine gültige US-amerikanische Telefonnummer ist.
isInternationalPhoneNumber Prüft, ob ein Text eine gültige internationale Telefonnummer ist.
isZipCode Prüft, ob ein Text eine gültige US-Postleitzahl mit 5 oder 9 Zeichen ist.
isURL Prüft, ob ein Text mit http://, ftp:// oder https:// beginnt.
isEmail Prüft, ob ein Text eine korrekte E-Mail-Adresse ist.

Es ist auch möglich und nicht sehr kompliziert, eigene Validierer zu schreiben und zu registrieren. Ein Validierer ist eine einfache Klasse, die das IValidator-Interface implementiert, einen einfachen Konstruktor hat und eine __call__-Methode aufweist. Es gibt schon zwei vorbereitete Validiererklassen, die nur noch mit den richtigen Parametern instanziiert und registriert werden müssen: RegexValidator und RangeValidator. Um zum Beispiel einen Validierer zu definieren, der Altersangaben auf einen Wertebereich von 0 bis 150 Jahren verifiziert, müssen folgende Zeilen am Beginn Ihres Python-Moduls hinzugefügt werden:

from validation.validators.validator import RangeValidator
from validation import validation
 
# the RangeValidator takes a name for the validator
# and a start and end value
validAge = RangeValidator("validAge", 0, 150)
validation.register(validAge)

Anschließend muss noch der Validierer dem Feld zugeordnet werden:

validators=("isInt","validAge"),

Zuerst überprüft der Code, dass Sie eine gültige Ganzzahl eingegeben haben, dann wird verifiziert, dass die Altersangabe plausibel ist. Wenn Sie einen gänzlich neuen Validierer schreiben wollen, müssen Sie eine Validiererklasse schreiben und eine Instanz davon im Validierersystem registrieren. In diesem Beispiel wird eine Validiererklasse geschrieben, die verifiziert, ob der Datumswert zwischen 2 angegebenen Zeitpunkten liegt. Mit diesem Validierer könnte man zum Beispiel prüfen, ob ein Urlaub innerhalb der Schulferien liegt. Dazu muss gesagt werden, dass der IsInt-Validierer nur der Vollständigkeit halber erwähnt wird. Weiters muss darauf hingewiesen werden, dass dieses Codebeispiel erst mit Archetypes 1.3.2 funktioniert , da RangeValidator in den vorigen Versionen einen Bug enthielt.

Dazu definieren Sie nun einen Validierer namens DateRangeValidator und registrieren ihn im Validation-Framework. Sie können dies in einem eigenen Python-Modul oder am Beginn des Moduls Ihres Inhaltstyps einfügen. Der DateRangeValidator arbeitet ähnlich wie der RangeValidator, nur dass er mit Datumswerten der Klasse DateTime arbeitet (mehr zur Klasse DateTime finden Sie in Anhang A). Eine Validiererklasse muss drei Voraussetzungen erfüllen: Sie muss das Interface ivalidator implementieren; sie muss ein Attribut namens name besitzen, mit dem die Validiererklasse registriert wird; und sie muss aufrufbar sein, indem sie eine Methode namens __call__ besitzt, die im Moment der Validierung aufgerufen wird. Das folgende Beispiel zeigt die Validiererklasse DateRangeValidator:

from Products.validation.interfaces import ivalidator
from DateTime import DateTime

class DateRangeValidator:
   __implements__ = (ivalidator,)

   def __init__(self, name, begindate, enddate):
       self.name = name
       self.begindate=begindate
       self.enddate=enddate

   def __call__(self, value, *args, **kwargs):

       if not isinstance(value, DateTime):
           value = DateTime(value)

       if self.begindate < value and value < self.enddate:
           return True
       else:
           return "the given date is not between %s and %s"%(str(self.begindate), str(self.enddate))

christmas = DateRangeValidator("ChristmasHolidays",
    DateTime('2004-12-18 00:00:00'),
    DateTime('2005-01-09 00:00:00'),)
validation.register(christmas)

Anschließend muss in der Felddeklaration des Schemas noch das validators-Attribut gesetzt werden:

validators=("ChristmasHolidays",)

Definition von Ansichten (Views) und Aktionen (Actions) in der Basisklasse

Archetypes erzeugt für jeden Inhaltstyp die Standard-Ansichten und -Aktionen basierend auf Anforderungen, die den meisten Anwendungen gerecht werden. Diese Standard-Aktionen sind view, edit und properties. In Ihrem Produkt werden Sie keine Page Templates dazu finden, denn diese Views werden automatisch zum Zeitpunkt der Darstellung umgesetzt. Aber selbstverständlich können Sie für Ihre Klasse eigene Ansichten programmieren und anpassen.

Meistens ist es so, dass man für den Inhaltstyp eine eigene view-Ansicht programmieren will, denn die Standardansicht zählt einfach nur die Attribute des Schemas mit den dazugehörigen Werten auf und erhebt auch nicht den Anspruch, eine perfekte Seite zu sein, wohingegen die edit- und properties-(Metadaten-)Ansichten von Haus aus den meisten Ansprüchen gerecht werden.

Im Prinzip wird für jeden Inhaltstyp eine Klasse geschrieben, vergleichbar mit dem Schreiben von Klassen im letzten Kapitel, wobei pro Plone-Produkt beliebig viele solcher Klassen enthalten sein können. Jede Klasse, die einen Inhaltstyp beschreibt, erbt von einer Archetypes-spezifischen Basisklasse, in den meisten Fällen entweder von BaseContent oder BaseFolder. Erstere Basisklasse wählt man für alle einfachen Inhaltstypen, wie zum Beispiel Article, während BaseFolder zum Einsatz kommt, wenn man einen Inhaltstyp schreiben will, der seinerseits andere Objekte enthalten soll (im Zope-Jargon folderish object genannt). In diesen Basisklassen ist alles enthalten, was vom Archetypes-Framework benötigt wird, so dass man sich in der eigenen Klasse auf das Wesentliche konzentrieren kann.

Wie ich jetzt zeigen werde, besteht dies im Wesentlichen aus zwei Teilen. Zuerst definieren wir eine Aktion, indem wir diese in der Factory-Type-Information (siehe letztes Kapitel) in Form eines Attributs namens actions definieren, das eine Liste der einzelnen Aktionsdefinitionen enthält. Zum Beispiel:

from Products.Archetypes.public import BaseContent
 
class Article(BaseContent):
    # other stuff
    actions = ({ 'id': 'view',
                 'name': 'View',
                 'action': 'string:${object_url}/article_view',
                 'permissions': (CMFCorePermissions.View,)
                },)

Zweitens benötigen Sie ein PageTemplate für den View namens article_view. Der Name des Templates muss zu der URL im action-Parameter passen, denn unter diesem Namen wird das Page Template in der Plone-Skin gesucht. In diesem Fall wird das passende Page Template im skins/archexample-Verzeichnis unseres Produkts abgelegt. Im Anhang B können Sie ein vollständiges Listing dieses Page Templates finden. Beim Schreiben dieses Views haben Sie volle Gestaltungsfreiheit.

Um die Änderungen wirksam werden zu lassen, muss Plone neu gestartet werden. In diesem Fall wurde eine Aktion neu definiert, die während des Installationsprozesses des Produkts im portal_types-Tool registriert wird. Deswegen muss das Produkt zusätzlich mit dem QuickInstaller reinstalliert werden, indem Sie in Plone auf Plone konfigurieren klicken.

Alle Elemente in der Factory-Type-Information (FTI) können als gleichnamige Attribute der Klasse überschrieben werden. Um zum Beispiel das content_icon-Element der FTI zu überschreiben, können Sie in der Klasse einfach ein Attribut mit Namen content_icon definieren. Sehen Sie sich dazu folgendes Beispiel an:

class SomeProduct(BaseContent):
          """ Some product """
          content_icon = "some_icon.gif"

Überschreiben von Standardmethoden

In Tabelle 13.2 habe ich die Möglichkeit des Überschreibens von Standardmethoden erwähnt, die in einem Inhaltstyp auftauchen können, etwa accessor oder mutator. Dies ist eine Option für Fortgeschrittene, die es erlaubt,den Mechanismus zu beeinflussen, wie Felder editiert werden.

Wie im Sourcecode im letzten Kapitel greifen Sie auf die Attribute niemals direkt zu, sondern über die dazugehörigen Get- und Set-Methoden, die in Archetypes accessors und mutators genannt werden. Diese Zugriffsmethoden werden automatisch von Archetypes generiert. Im Falle eines Feldes namens blurb heißen diese beiden Methoden getBlurb und setBlurb, das Präfix set oder get wird dem großgeschriebenen Feldnamen vorangestellt.

Jedoch werden Sie vielleicht im Accessor oder Mutator etwas anderes machen wollen. Nehmen wir an, Sie wollen beim Speichern den Wert eines Feldes filtern und korrigieren, wie zum Beispiel die Schreibweise Ihrer Firma korrigieren oder beim Ändern eines Feldinhaltes auch andere Felder ändern. Dies können Sie erreichen, indem Sie die oben erwähnten Standardmethoden überschreiben. Im folgenden Beispiel werden Sie eine neue Methode namens getSpecialBlurb schreiben, die im blurb-Wert den Text Perl durch Python ersetzt, bevor der Wert zurückgegeben wird.

class Article(BaseContent):
 
    def getSpecialBlurb(self):
        """ The view for an article """
        blurb = self.getField('blurb').get(self)
        blurb = blurb.replace('Perl', 'Python')
        return blurb

Außerdem müssen Sie noch Ihr Feld ändern, damit es diese Methode verwendet:

StringField('blurb',
    searchable=1,
    widget=TextAreaWidget(),
    accessor="getSpecialBlurb",
),

In diesem Beispiel wird bei jedem lesenden Zugriff auf das blurb-Feld in einer View- oder Edit-Seite der Wert von getSpecialBlurb zurückgegeben. Archetypes weiß, dass es diese Methode verwenden soll, weil deren Name im accessor-Parameter der Felddeklaration festgelegt ist. Ein bisher noch nicht erwähnter Griff in die Trickkiste kommt hier zum Einsatz: Um von getSpecialBlurb den Feldinhalt zu bekommen, muss man zuerst das Feldobjekt bekommen und dort die get-Methode aufrufen, was man durch den etwas komplexen Ausdruck blurbself.getField('blurb').get(self) erreicht. Dieses Muster, ein Feldobjekt zu holen und dann eine Methode darauf aufzurufen, ist in Archetypes recht gebräuchlich.

Das praktische Ergebnis davon ist nun, dass, sobald jemand das Wort Perl im Feld blurb eingibt, dieses durch Python ersetzt wird.

Den Rest des Inhaltstyps zusammensetzen

Ich habe nun alle Hauptelemente des Inhaltstyps behandelt. Listing 13.1 zeigt den gesamten Code. Sie werden bemerken, dass der Rest des Programmcodes kompakter ist als der Python-Code im letzten Kapitel, da Archetypes sehr viel im Hintergrund für Sie erledigt.

Listing 13.1. Article.py

from Products.ArchExample.config import ARTICLE_GROUPS
from Products.Archetypes.public import BaseSchema, Schema
from Products.Archetypes.public import StringField, TextField
from Products.Archetypes.public import SelectionWidget, TextAreaWidget
from Products.Archetypes.public import RichWidget
from Products.Archetypes.public import BaseContent, registerType
from Products.Archetypes.Marshall import PrimaryFieldMarshaller
from Products.CMFCore import CMFCorePermissions
from config import PROJECTNAME
 
schema = BaseSchema +  Schema((
    StringField('group',
                vocabulary=ARTICLE_GROUPS,
                widget=SelectionWidget(),
                ),
    StringField('blurb',
                searchable=1,
                widget=TextAreaWidget(),
                ),
    TextField('body',
              searchable=1,
              required=1,
              primary=1,
              default_output_type='text/html',
              allowable_content_types=('text/plain',
                                       'text/structured',
                                       'text/restructured',
                                       'text/html',
                                       'application/msword'),
              widget=RichWidget(label='Body'),
              ),
           ),
     marshall=PrimaryFieldMarshaller(),
     )
 
class Article(BaseContent):
    """This is a sample article; it has an overridden view for show,
    but this is purely optional
    """
 
    schema = schema
 
    actions = ({
        'id': 'view',
        'name': 'View',
        'action': 'string:${object_url}/article_view',
        'permissions': (CMFCorePermissions.View,)
        },)
 
registerType(Article, PROJECTNAME)

Abgesehen von den import-Anweisungen am Beginn und dem Schema, habe ich alle Elemente des Sourcecodes behandelt, mit der Ausnahme von registerType. Diese Funktion registriert die Klasse mit Ihrem Produkt im Archetypes-Framework. Jedes Produkt kann beliebig viele Inhaltstypen registrieren. So wird diese Funktion registerType mit zwei Parametern aufgerufen: der Klasse und dem Namen des Produktes. Der zweite Parameter ist optional und wird nur in Ausnahmefällen benötigt, da die Funktion registerType von der Klasse auf das Produkt schließen kann. In diesem Beispiel ist der Produktname in der Datei config.py definiert; es ist allgemein üblich, Einstellungen an zentraler Stelle in einer config.py-Datei innerhalb des Produkts vorzunehmen, wie zum Beispiel:

Listing 13.2. Die Konfigurationsdatei von ArchExample

from Products.CMFCore.CMFCorePermissions import AddPortalContent
from Products.Archetypes.public import DisplayList
 
ADD_CONTENT_PERMISSION = AddPortalContent
PROJECTNAME = "ArchExample"
SKINS_DIR = 'skins'
 
GLOBALS = globals()
 
ARTICLE_GROUPS = DisplayList((
    ('headline', 'Headline'),
    ('bulletin', 'Special Bulletin'),
    ('column', 'Weekly Column'),
    ('editorial', 'Editorial'),
    ('release', 'Press Release'),
    ))

Die Variable ARTICLE_GROUPS ist eine Liste von Wertepaaren für das Vokabular des group-Feldes in Article, wobei jeweils der erste Wert bei Auswahl im Feld gespeichert wird und der zweite als zugehöriger Text in der Auswahlliste erscheint. Man kann allerdings auch eine einfache Liste von Werten als Vokabular angeben, wenn man nicht zwischen den Werten und deren Beschriftung unterscheiden muss. In Fall dieses Beispiels sieht der erzeugte HTML-Code für das groups-Feld folgendermaßen aus:

<option value="headline">Headline</option>
<option value="bulletin">Special Bulletin</option>
...

Ich möchte hier noch auf die Verwendung von globals() hinweisen. Dies ist eine eingebaute Python-Funktion, die alle Symbole aus dem globalen Namespace zurückgibt. Sie wird hier verwendet, um den Pfad zum skins-Verzeichnis im Filesystem zu berechnen, so dass Sie ein File-System-Directory-View davon für das Skins-Tool erstellen können. Das Produktinitialisierungsmodul __init__.py ist dadurch also wesentlich einfacher. Mit einem neuen Zusatz sehen die Funktionen process_types und listTypes nun so aus:

from Products.Archetypes.public import process_types, listTypes
content_types, constructors, ftis = process_types(
    listTypes(PROJECTNAME),
    PROJECTNAME)

Die listTypes-Funktion aus dem Archetypes-Framework gibt alle Typdeklarationen zurück, die bisher im angegebenen Produkt registriert wurden. Diese Liste wird von process_types noch weiter behandelt, die dann die Inhaltstypen, Konstruktoren und Factory-Type-Informationen (FTI) zurückgibt, die sodann beim CMF mit der Funktion ContentInit registriert werden. Vieles ist ähnlich wie beim Schreiben von reinen Python-Produkten für das CMF, nur dass diese Funktionen das Ganze ein wenig vereinfachen.

Schließlich gibt es noch die install-Funktion im Skript Extensions/Install.py. Im Gegensatz zum __init__.py-Modul, das aufgerufen wird, wenn das Produkt beim Hochstarten von Zope registriert wird, wird das Installationsskript aufgerufen, wenn ein Produkt in einer bestimmten Plone-Instanz zum Beispiel durch den QuickInstaller installiert wird. Dieses Skript vereinfacht sich durch Archetypes ebenfalls wesentlich, da die meiste Arbeit durch installTypes und install_subskin abgehandelt wird. Der Begriff Subskin ist ein wenig irreführend - eigentlich ist damit ein Skin-Layer gemeint.

Listing 13.3. Installationsskript

from Products.Archetypes.public import listTypes
from Products.Archetypes.Extensions.utils import installTypes,
install_subskin
from Products.ArchExample.config import  PROJECTNAME, GLOBALS
 
from StringIO import StringIO
 
def install(self):
    out = StringIO()
    installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME)
    install_subskin(self, out, GLOBALS)
    out.write("Successfully installed %s." % PROJECTNAME)
    return out.getvalue()

Das vollständige Paket von ArchExample ist auf der Homepage des Autors des Plone-Buches unter http://plone-book.agmweb.ca zum Download verfügbar, Sie können es durch Auspacken in Ihrem Plone ausprobieren. Wie Sie gesehen haben, ist dieser Inhaltstyp schnell und einfach zu entwickeln und auch leicht zu ändern, ohne viel Code zu schreiben.

Entwickeln mit Archetypes

System Message: INFO/1 (<string>, line 1026); backlink

Duplicate implicit target name: "entwickeln mit archetypes".

Dieser Abschnitt wird Sie in einige fortgeschrittene Programmiertechniken von Archetypes einführen, wie zum Beispiel Referenzen, Erstellen von eigenen Widgets und Transformationen.

Für diesen Abschnitt ist es wichtig zu sehen, wie die Änderungen, die Sie am Produkt vornehmen, in Plone wirksam werden. Wie Sie bisher gesehen haben, gibt es verschiedene Stadien in der Entwicklung des Produkts. Wenn Sie Ihr Produkt ändern, ist es nützlich, die notwendigen Schritte zu kennen.

Wenn Sie etwas in einer Skin ändern und Zope im Debug-Modus läuft, werden diese Änderungen sofort wirksam. Wenn Sie Dinge ändern, die im Installationsprozess involviert sind, wie etwa das portal_actions-Tool oder das portal_types-Tool, müssen Sie Plone neu starten und im QuickInstaller Ihr Produkt reinstallieren.

Wenn Sie das Schema ändern, reicht ein Neustart von Plone. Hingegen stellt sich die Frage, was dann mit bereits existierenden Objekten passiert. Archetypes bietet hier im archetype-tool ein Update Schema-Werkzeug, das auf Wunsch über alle Archetypes-Objekte iteriert und sie mit dem neuen Schema abgleicht. Zu diesem Zweck klicken Sie im ZMI auf archetype_tool und wählen Update Schema. Nun können Sie die Klassen selektieren, deren Schema abgeglichen werden soll, und klicken sodann auf den Update Schema-Button; dies wird daraufhin alle Instanzen dieser Klassen mit dem neuen Schema abgleichen.

Verwendung von eindeutigen Schlüsseln (Unique Ids, UIDs)

Das Konzept von eindeutigen Schlüsseln ist einfach, aber hat in Zope bislang gefehlt. Ursprünglich glaubten Zope-Entwickler, dass der Pfad auf ein Objekt für dessen Adressierung ausreichend sei; unglücklicherweise hat sich das als unzureichend herausgestellt. Was passiert zum Beispiel, wenn ein Objekt verschoben wird? Alle Referenzen auf dieses Objekt würden nun ins Leere verweisen, da sich durch das Verschieben der Pfad ändert. Mit Hilfe der eindeutigen Schlüssel steht eine elegante Lösung des Problems zur Verfügung. Archetypes vergibt und speichert automatisch für jedes Objekt eine UID, die in einem speziellen Katalog namens uid_catalog indiziert wird.

Sie können diesen uid_cataog im ZMI ansehen. Dieser ist dem portal_catalog ziemlich ähnlich, nur dass er ausschließlich die für die Verwaltung und Suche von UIDs nötigen Indizes enthält. Es ist ziemlich einfach, ein Objekt aufgrund seiner UID über den uid_catalog zu erhalten. Sie müssen lediglich über den UID-Index nach der entsprechenden UID suchen; bei jedem Objekt können Sie dessen UID mit Hilfe der Funktion UID() abfragen. Das nachfolgende Python-Script kann jedes Objekt aus dem Katalog suchen, vorausgesetzt, Sie kennen dessen UID.

##paramaters=objectId
results = context.uid_catalog(UID=objectId)
return results[0].getObject()

Wirklich nützlich sind die eindeutigen Schlüssel bei der Verwaltung von Referenzen zwischen Objekten. Nehmen wir an, Sie wollen von Ihrem Artikel aus verschiedene Bilder referenzieren, die sich in Ihrem Portal befinden. Wenn diese Bilder Archetypes-Ojekte sind, können Sie wie folgt ein ReferenceField zu Ihrem Schema hinzufügen:

ReferenceField("images"
    allowed_types=("Archetype Images",),
    multivalued=1,
    ),

Der User bekommt nun eine Dropdown-Liste mit den Titeln aller Archetype Image-Objekte und kann eines auswählen. Im Hintergrund werden beim Speichern Referenzen zu den Zielobjekten angelegt. Verantwortlich für die Referenzlogik in Archetypes ist die ReferenceEngine; der interessierte Leser möge hierfür den Code in Archetypes/ReferenceEngine.py studieren. Im Archetypes-Subversion-Archiv unter http://svn.plone.org gibt es das Produkt ACME, das viele der fortgeschrittenen Programmiertechniken wie die Referenzen demonstriert. Die Installation ist aber aufgrund von weiteren Produkten, die benötigt werden, nicht ganz einfach.

Anpassen von Widgets

Eine häufig gestellte Frage ist: "Warum macht dieses Widget das eigentlich?" oder: "Warum sieht dieses Widget so aus?" Oft lautet die Antwort: "Weil es so geschrieben ist - Sie können es sich ja anpassen." Da die Widgets das sind, was der Kunde sieht, ändern sich die Anforderungen oft, so dass dem Entwickler hier die Flexibilität von Archetypes zugute kommt.

Alle Widgets bestehen einerseits aus einer Python-Klasse - die vorgegebenen Widgets kann man in Archetypes/Widget.py sehen - und die dazugehörigen Page Templates in Archetypes/skins/archetypes/widgets. Wenn Sie im ZMI auf portal_skins klicken und dort archetypes/widgets wählen, sehen Sie alle mit Archetypes gelieferten Widgets. Unglücklicherweise hat das portal_skins-Tool von CMF einen kleinen Bug. Weil die Widgets in einem Unterverzeichnis sind, kann man leider nicht einfach eines der Templates ändern. Das ist aber nicht so schlimm, Sie können in Ihrem Produkt einen eigenen Skin-Layer definieren und dann in der Widget-Deklaration des Schemas spezifizieren, dass das Widget mit einem anderen Makro dargestellt werden soll. Jedes Widget-Objekt hat ein macro-Attribut, das definiert, welches Makro das Widget darstellen soll, und das als Pfad-Ausdruck zum Widget Page Template spezifiziert wird.

Nun ist der Begriff macro irreführend, denn das Attribut verweist auf ein Page Template, das seinerseits drei Makros enthält: view, edit und search. Diese Makros sind für das Anzeigen Ihres Widgets in den verschiedenen Situationen zuständig.

Das view-Makro ist eine benutzerfreundliche Ansicht mit rein lesendem Zugriff und wird zum Beispiel in der base_view-Ansicht pro Feld angezeigt. Das edit-Makro wird in der Bearbeitungsansicht angezeigt und ist das Makro, mit dem der Benutzer die Daten für das einzelne Feld eingibt. Es kommt zum Beispiel in der base_edit-Ansicht zum Einsatz. Das search-Makro wird verwendet, wenn Sie eine Suchmaske für Ihren Inhaltstyp zusammenstellen wollen; es sieht häufig wie das edit-Makro aus, muss aber nicht unbedingt so aussehen . Ein Stringfeld kann zum Beispiel in einem RichWidget eingegeben werden, aber der Suchbegriff dazu könnte mit einem StringWidget eingegeben werden.

Nun, für unser Beispielprodukt wollen wir ein StringField für die E-Mail-Adresse einer Person verwenden, die in der view-Ansicht als klickbarer E-Mail-Link dargestellt wird. Dafür benötigen wir ein neues view-Makro; die Makros für edit und search können wir vom StringWidget nehmen. Dazu erstellen Sie ein neues Page Template namens email_widget.pt in der Skin Ihres Produkts. Für edit und search werden einfach die entsprechenden Makros des StringWidgets aufgerufen:

<html xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      i18n:domain="plone">
 
  <body>
    <div metal:define-macro="edit">
      <div metal:use-macro="here/widgets/string/macros/edit" />
    </div>
 
    <div metal:define-macro="search">
      <div metal:use-macro="here/widgets/string/macros/search" />
    </div>

Für view erzeugen Sie einen mailto:-Link, den Sie durch folgende kleine Änderung erreichen:

    <div class="field" metal:define-macro="view">
        <a href="#" tal:attributes="href string:mailto:${accessor}"
          tal:content="accessor">E-Mail</a>
    </div>
</body>
</html>

Nach dem Definieren des neuen Page Templates mit Ihrem eigenen Code können Sie einfach dessen Namen in der Widget-Deklaration als macro-Parameter angeben. Im folgenden Codebeispiel definieren Sie das E-Mail-Feld als StringField mit zugehörigem StringWidget wie üblich, nur dass Sie noch das zu verwendende Makro explizit angeben:

StringField('E-Mail',
    validators = ('isEmail',),
    widget = StringWidget(
        label='E-Mail',
        macro='email_template'
    )
)

Bis jetzt haben Sie lediglich das Makro eines bereits vorhandenen Widgets geändert. Ein ganz neues Widget zu erstellen erfordert das Schreiben einer eigenen Widget-Klasse und deren Registrierung. Alle Widgets haben eine gemeinsame Basisklasse. Im folgenden Beispiel schreiben wir ein eigenes Modul namens EmailWidget.py und speichern es in das ArchExample-Verzeichnis. Es enthält die neue Widget-Klasse und deren Registrierung. Beachten Sie, dass die macro-Property des Widgets auf das email_template voreingestellt ist:

from Products.Archetypes.Widget import TypesWidget
from Products.Archetypes.Registry import registerWidget
 
class EmailWidget(TypesWidget):
    _properties = TypesWidget._properties.copy()
    _properties.update({
       'macro' : "email_template",
       'size' : '30',
       'maxlength' : '255',
       })
 
registerWidget(EmailWidget,
    title='String',
    description='Renders a clickable E-Mail field',
    used_for=('Products.Archetypes.Field.StringField',)
)

Um das neue Widget in Ihrer Article-Klasse zu verwenden, importieren Sie EmailWidget und geben es wie folgt im widget-Parameter zum Feld an:

from EmailWidget import EmailWidget
 
StringField('E-Mail',
    validators = ('isEmail',),
    widget = EmailWidget(
        label='E-Mail',

    )
)

Das Entwickeln von ordnerartigen Objekten (Folderish Objects)

Sie haben vermutlich schon oft mit ordnerartigen Objekten in Plone gearbeitet, ohne sich dessen bewusst zu sein. Ein ordnerartiges Objekt ist eines, das ähnliche Eigenschaften wie ein Ordner aufweist, nämlich dass es seinerseits Unterobjekte enthalten kann. Das Erstellen eines ordnerartigen Objekts ist nichts Besonderes - die Klasse muss lediglich von einer besonderen Basisklasse erben, um die entsprechenden Fähigkeiten zur Verfügung zu stellen, und schon kann man zu unserem Objekt neue Unterobjekte hinzufügen.

Ordnerartige Objekte sind aus verschiedenen Gründen nützlich. Sie stellen einen einfachen Weg dar, um Sammlungen von einzelnen Objekten zu erzeugen. Sie erlauben außerdem, dass Plone-Benutzer ihre speziellen Inhaltstypen über die gewohnte Plone-Oberfläche verwalten können, ohne dass Sie dafür speziell programmieren müssen. Generell ist es am besten, den Ordner einfach zu halten, und die Logik sollte in Ihren Objekten und in deren Workflow enthalten sein. Natürlich gibt es Ausnahmen dazu, und ein klassisches Beispiel ist die Familie der Bugtracker, CMFCollector und PloneCollectorNG, die beide komplexe ordnerartige Objekte sind, die einiges an Logik für die einzelnen Einträge bieten.

Der einfachste Weg, um ordnerartige Objekte zu erstellen, ist wiederum die Verwendung von Archetypes. Sie haben in den vorigen Beispielen gesehen, dass BaseContent alle nötigen Standardarbeiten für "normale" Objekte erledigt. Nun gibt es auch eine Klasse BaseFolder, die die entsprechenden Aufgaben für ordnerartige Objekte übernimmt. Außerdem besitzen Ordner ein eigenes Basisschema BaseFolderSchema. Auf den Punkt gebracht: Um einen ordnerartigen Inhaltstyp zu erstellen, ändern Sie die Basisklasse auf BaseFolder und fügen sie zum Schema BaseFolderSchema hinzu. Ein einfaches Beispiel für einen ordnerartigen Inhaltstyp sehen Sie hier:

from Products.Archetypes.public import BaseFolder, BaseFolderSchema
 
schema = BaseFolderSchema
 
class SimpleFolder(BaseFolder):
    """A simple folderish archetype"""
    schema = schema
 
registerType(SimpleFolder)

Wenn Sie beabsichtigen, eine große Menge von Unterobjekten in einem Ordner zu speichern, dann ist die Klasse BaseFolder weniger geeignet, da diese Klasse von der Zope-Basisklasse Folder erbt, die nicht auf große Datenmengen ausgelegt ist. Für große Mengen von Unterobjekten empfiehlt sich die Verwendung von Binary-Tree-Ordnern, die die Unterobjekte intern in einem Binärbaum effizienter speichern statt in einem Python-Dictionary, wie dies bei Folder der Fall ist. In diesem Fall lassen Sie Ihre Klasse von BaseBTreeFolder erben und nehmen als Basisschema BaseBTreeFolderSchema. Was die Produktentwicklung betrifft, sind die beiden gleich zu programmieren, nur dass auch bei einer Anzahl von mehr als 100.000 Objekten der BtreeFolder gut skaliert.

Arbeiten mit Microsoft Office-Dateien

Der Umgang mit Microsoft-Office-Dateien wie zum Beispiel Word oder Excel ist ein Fluch, mit dem jedes Content-Management-System irgendwann einmal konfrontiert wird. Aber dies ist der Fall mit mit verschiedenen Dateiformaten - Microsoft Office, OpenOffice.org, Portable Document Format (PDF), Imagedateien usw. Der Umgang mit diesen Dateiformaten auf Webseiten verursacht normalerweise einige Probleme; dies ist natürlich wohlbekannt. Das Editieren ist umständlich, da durch das Anklicken das Dokument heruntergeladen wird oder - noch schlimmer - in Ihrem Webbrowser geöffnet wird. Wenn Sie es fertig editiert haben, müssen Sie das Dokument abermals auf die Website hochladen, was wiederum eine Runde des Herumklickens bedeutet. Wenn das Dokument endlich online ist, ist dieses meistens nicht suchbar, weil Binärdateien nicht indiziert werden. Außerdem können Sie das Dokument nicht online betrachten, weil der Webbrowser das jeweilige Datenformat ohne spezielle Plugins nicht darstellen kann.

Es gibt einige Lösungen, um das Problem des Editierens von Inhalt zu lösen. Wenn Sie weiter annehmen, dass die meisten Ihrer Anwender Windows verwenden, dann kann die Verwendung von WebDAV mit Komplikationen verbunden sein, da Microsofts Implementation des WebDAV-Clients (Web Folders) von fraglicher Qualität ist. Hingegen erfüllt der External Editor diese Funktion recht gut. Wenn Plone dem External Editor mitteilen kann, dass es sich bei dem editierten Objekt zum Beispiel um eine Word-Datei handelt, wird der External Editor das Dokument mit Word öffnen.

Und für den Rest enthält Archetypes ein Transformationspaket namens Portal Transforms, das die Transformation von Inhaltstypen beherrscht. Dies kann ein Dokument in einem bestimmten Datenformat in HTML transformieren, wonach es katalogisiert werden kann, und als HTML kann es auch im Webbrowser dargestellt werden. Dies funktioniert durch die Verwendung eines externen Transformationsprozesses, um die Daten umzuwandeln und das Ergebnis auszulesen und darzustellen. Wenn Sie zum Beispiel Plone unter Windows (auf dem Server) verwenden, dann nimmt Portal Transforms ein auf dem Server installiertes Microsoft Office als COM-Server, um die Word-Datei in HTML umzuwandeln.

All dies passiert im Hintergrund. Sie müssen lediglich sicherstellen, dass die für die Transformationen benötigten Komponenten installiert sind und funktionieren. Es gibt verschiedene Typen von Transformationen, und oft gibt es mehr als eine Möglichkeit, Daten zu transformieren.

Das OpenOffice-Paket stellt hervorragende Transformationen von Microsoft Office-Daten zur Verfügung (oft sogar besser als MS Office selbst!) und bietet somit auch auf Nicht-Microsoft-Plattformen die Möglichkeit, Office-Daten zu verarbeiten. Ich habe auch schon wvWare (http://wvware.sourceforge.net/) verwendet, ein Kommandozeilen-Werkzeug, das es ermöglicht, verschiedene Microsoft-Formate in HTML zu übersetzen. All diese Optionen stehen Ihnen zur Verfügung; oft sind die Komponenten aber nicht einfach zu installieren. Nach dem Aufsetzen der Komponenten berichten Benutzer, dass die besten Ergebnisse mit Microsoft Office und Openoffice.org als Backend der Transformationen erzielt werden.

Ich empfehle, zuerst genau zu überlegen, was transformiert werden soll, und dann einen Blick in den Quellcode von PortalTransforms zu werfen, um zu sehen, ob Sie eine geeignete Transformation für Ihre Zwecke finden.

Einrichten eines Inhaltstyps

Sie werden nun einen einfachen Inhaltstyp erstellen, der Microsoft Office Word-Dokumente verarbeiten kann. Word ist sicher der Dokumenttyp, den Sie am häufigsten verarbeiten werden. In diesem Beispiel habe ich die Transformation unter Windows durchgeführt. Wenn Sie dies hingegen auf einem alternativen System wie zum Beispiel Linux aufsetzen, ändert sich am Code Ihres Inhaltstyps nichts, es kommt lediglich eine andere Transformation vom Dokumenttyp application/msword nach HTML zum Tragen. Welche Transformationen in einem Portal zur Verfügung stehen, wird im Werkzeug portal_transforms hinterlegt. In diesem Beispiel ist es am einfachsten, Ihre Plone-Instanz unter Windows aufzusetzen, wo Microsoft Office schon installiert ist. Der Plone Installer richtet eine Plone-Instanz mit allen notwendigen Win32-API-Modulen ein. Dann erstellen Sie einen Inhaltstyp für dieses Beispiel mit Namen WordExample. Im Wesentlichen ist ein Feld in diesem Schema für die Speicherung von Word-Dokumenten zuständig, so dass sich folgendes einfaches Schema ergibt:

schema = BaseSchema +  Schema((
    TextField('body',
              searchable=1,
              required=1,
              primary=1,
              default_output_type='text/html',
              allowable_content_types=('application/msword',),
              widget=RichWidget(label='Body'),
              ),
    ),
    marshall=PrimaryFieldMarshaller(),
    )

Der einzige Unterschied zu einem normalen TextField ist die Angabe des Multipurpose Internet Mail Extensions-(MIME-)Typ für Microsoft Word im allowable_content_types-Attribut. Für jeden Dokumenttyp, den dieses Feld zur Transformation akzeptieren soll, geben Sie den entsprechenden MIME-Typ im allowable_content_types-Attribut an. Wenn Sie beispielsweise Microsoft Office- und PDF-Dateien verarbeiten wollen, geben Sie folgende Zeile an:

allowable_content_types=('application/msword','application/pdf'),

Dieser Inhaltstyp ist das einfachstmögliche Beispiel in diesem Moment. Aber Sie können diesen Inhaltstyp erweitern wie jeden anderen, zum Beispiel um ein Beschreibungsfeld und andere Attribute. Eine sinnvolle, aber arbeitsintensive Erweiterung wäre zum Beispiel, die Metadaten des Word-Dokuments in die Metadaten des Plone-Objekts zu speichern.

Wenn Sie bereits Archetypes 1.3 verwenden, empfiehlt sich hier die Installation der ATContentTypes (http://sf.net/projects/collective), die die klassischen Inhaltstypen von Plone (Document, File,...) durch kompatible Archetypes-basierende Klassen ersetzen und spätestens ab Plone 2.1 standardmäßig mit Plone installiert werden. Der Document-Inhaltstyp verwendet hier bereits PortalTransforms, um alle möglichen Transformationen durchzuführen, so dass Sie hier keinen eigenen Inhaltstyp mehr schreiben müssen, um in den Genuss des Transformations-Frameworks von Archetypes zu kommen.

Transformationen unter Windows einrichten

Die Transformation sollte automatisch durch den Installationsprozess im Tool portal_transforms registriert sein. Hier gibt es nicht viel Arbeit an dem Tool; es liefert im Wesentlichen eine Liste von verfügbaren Transformationen. Eine Transformation hat eine Liste von eingehenden MIME-Typen (wie zum Beispiel application/msword), die sie transformieren kann, und einen ausgehenden MIME-Typ, der produziert wird (so wie in den meisten Fällen text/html). Jede Transformation ist ein Python-Modul im Dateisystem; in diesem Fall finden Sie den Code in PortalTransforms/transforms/word_to_html.

Um diese Transformation unter Windows zum Laufen zu bringen, müssen Sie ein Kommandozeilen-Tool aufrufen, das die COM-Bindungen für Python generiert. Um dies zu tun, starten Sie PythonWin aus der Plone-Gruppe in Ihrem Startmenü und selektieren Tools - COM Makepy utility. Dieses listet alle verfügbaren COM-Interfaces auf; wenn Sie Office installiert haben, werden Sie hier einen Eintrag für Microsoft Office in der Liste finden. Auf meinem Windows-Server lautete dieser Eintrag Microsoft Word 9.0 Object Library (8.1). Wählen Sie diesen aus, und klicken Sie auf OK. PythonWin wird nun die entsprechende Information generieren. Wenn Sie dies durchgeführt haben, bekommen Sie die Meldung 'Generating to ...'.

Sie können nun PythonWin schließen und Plone neu starten.

Testen des Inhaltstyps

Um den Inhaltstyp zu testen, starten Sie Plone neu, stellen sicher, dass Ihr Produkt wie üblich registriert ist, und installieren es in Ihrem Portal mit Produkte hinzufügen/löschen. Nun legen Sie eine Instanz Ihres neuen Inhaltstyps an.

Nun selektieren Sie eine Word-Datei aus Ihrem Dateisystem und klicken Save, um sie zu Plone hochzuladen. Anschließend wird diese Datei hochgeladen, und Sie können das Resultat sehen. Verlieren Sie hier nicht die Geduld, denn dieser Vorgang kann länger dauern, denn zusätzlich zum normalen Upload der Daten muss Word als externes Programm aufgerufen werden, um die HTML-Datei mit eventuell eingebetteten Bildern zu erzeugen. Wenn alles richtig arbeitet, sollten Sie Ihr Word-Dokument als HTML etwa wie in Abbildung 13.4 sehen.

img/13-04.png

Abbildung 13.4. Die hochgeladene Datei

Nun da Sie das Dokument im Plone haben, wollen Sie es auch editieren können. Hierzu empfiehlt sich der Einsatz von External Editor. Wenn Sie External Editor auf Ihrem Arbeitsplatz installiert haben, klicken Sie auf das Bleistiftsymbol in der oberen rechten Ecke, und die Datei wird in Word geöffnet, wie in Abbildung 13.5 zu sehen ist. Sie können nun das Dokument ändern und editieren; jedes Mal, wenn Sie die Datei speichern, wird diese im Hintergrund zu Plone hochgeladen und wiederum zu HTML transformiert.

img/13-05.png

Abbildung 13.5. Das Dokument in Word geöffnet

Alternative Transformationen von Office-Dokumenten

Die Verwendung von Microsoft Office setzt nicht nur Windows auf dem Plone Server voraus, sondern auch ein dort installiertes Microsoft Office nebst dazu gültigen Lizenzen.

Die Installation von wvWare unter Linux gestaltet sich recht einfach, so dass Sie hier relativ schnell zu einer Word-Transformation kommen, da die Verfügbarkeit von wvWare von PortalTransforms automatisch erkannt wird. Dazu besorgen Sie sich das Paket von http://wvware.sourceforge.net und folgen den (etwas veralteten) Installationsanleitungen. Für Debian-Anwender gibt es das vorkonfigurierte Paket wv, das im Handumdrehen ohne manuellen Eingriff installiert ist. wvWare steht dadurch als Kommandozeilenbefehl zur Verfügung und wird von PortalTransforms automatisch erkannt. Allerdings ist die Qualität des Resultats nicht mit der Microsoft Office- oder OpenOffice.org-Transformation vergleichbar.

OpenOffice.org bietet vergleichbar mit Microsofts COM eine applikationsübergreifende Programmierschnittstelle namens UNO, zu der es auch ein Python-Binding mit Namen PyUNO gibt. Um OpenOffice innerhalb von Plone als Transformation-Backend verfügbar zu machen, muss die PyUNO-Schnittstelle in der Python-Installation, die von Plone verwendet wird, installiert werden, was jedoch einige manuelle Schritte erfordert.

Derzeit entsteht ein weiteres Produkt namens BlueDCS im Entstehen, das vielleicht bei Drucklegung dieses Buches als Betaversion verfügbar sein wird. Es teilt die OfficeTransformation auf zwei Serverprozesse auf: in einen von Plone unabhängigen XMLRPC-Server, der sich um die Kommunikation mit OpenOffice kümmert, und in ein sehr einfach zu installierendes Plone-Produkt, das die Transformationsanforderung an jenen Server delegiert. Dies bringt den Vorteil, dass die Arbeit des Einrichtens von OpenOffice an zentraler Stelle nur einmal durchgeführt werden muss und etwaige Inkompatibilitäten zwischen der von Plone verwendeten Python-Version und PyUNO ausgeschlossen sind, da der XMLRPC-Server einfach und ohne großen Installationsaufwand mit der von OpenOffice mitinstallierten Python-Distribution gestartet wird. Der Installationsaufwand innerhalb von Plone beschränkt sich auf das Aktivieren des Produktes und auf die Angabe der Adresse des BueDCS-Servers.

Tipp

Sie können Portal Transforms vom Archetypes CVS unter http://sf.net/projects/archetypes herunter laden. Sie werden im Quellcode von Portal Transforms Dokumentation vorfinden und es gibt auch eine Online-Version unter http://www.logilab.org/projects/portaltransforms/documentation .

Fortgeschrittenes Entwickeln: Erstellen von Inhaltstypen mit UML

Nun haben Sie ein Produkt mit mehreren komplizierten Inhaltstypen und finden die Idee, diese manuell zu schreiben, langweilig? Nun, keine Angst, denn ArchGenXML ist hier! Dies ist in hilfreiches Produkt, das es Ihnen erlaubt, Ihre Plone-Applikation mit UML in einem UML-Modellierungstool Ihrer Wahl zu modellieren und aus diesem Modell ein lauffähiges Plone-Produkt zu generieren.

Philipp Auersperg hat das ArchGenXML-Projekt begonnen, und Jens Klein hat vieles an Code und Dokumentation dazu beigetragen. Ich werde in diesem Abschnitt einige der Fähigkeiten von ArchGenXML, das zur Drucklegung dieses Buches in der Version 1.2 vorliegt, vorstellen.

Um mit ArchGenXML zusammenarbeiten zu können, muss ein UML-Tool den Export des Modells als XMI (XML-Anwendung zum Austausch von Modell-Metadaten) unterstützen. ArchGenXML wurde erfolgreich mit den folgenden Produkten getestet:

Der Export von XMI-Modelldateien wird von den verschiedenen UML Tools leicht unterschiedlich gehandhabt (zum Beispiel exportiert ObjectDomain keine Statemachines). Aber wenn Sie einen Blick auf die ArchGenXML-Beispiele werfen, sehen Sie einige Beispielmodelle, die die Funktionalität von ArchGenXML demonstrieren. Abbildung 13.6 zeigt ein einfaches Modell mit einigen Klassen und Beziehungen.

img/13-06.png

Abbildung 13.6. Ein komplexes Beispiel eines Datenmodells

img/13-07.png

Abbildung 13.7. Zustandsdiagramm für die Klasse Project

Der einfachste Weg, um daraus ein lauffähiges Plone-Produkt zu generieren, besteht darin, in das Verzeichnis samples/PloneBook in Ihrem ArchGenXML-Verzeichnis zu wechseln und den unten stehenden Befehl aufzurufen. Damit wird aus dem ArgoUML-Modell ein lauffähiges Plone-Produkt namens Project erzeugt.

python ../../ArchGenXML.py product.zargo Product

Sie müssen sodann das Produkt in Ihren Products-Ordner Ihrer Plone-Installation kopieren und Zope neu starten. Wenn Sie einen Blick in das eben generierte Produkt werfen, werden Sie sehen, dass alles Notwendige für Sie generiert wurde: die Klassendateien, die Modulinitialisationsdatei __init__.py und die Skripten zum Installieren des Produktes in Ihre Plone-Instanz im Extensions-Verzeichnis. Im neu gestarteten Plone müssen Sie nun lediglich Plone konfigurieren wählen und das neu verfügbare Produkt installieren, und schon können Sie mit den neuen Inhaltstypen experimentieren. Sie werden die Attribute, die im Datenmodell für die Klassen vergeben wurden, im Edit-Schirm der Inhaltstypen wiedererkennen.

img/13-08.png

Abbildung 13.8. Das frisch generierte Produkt im Einsatz

Was passiert mit den manuellen Änderungen, die Sie an Ihrem Produkt-Quellcode vornehmen, wenn Sie mit ArchGenXML noch einmal das Modell über Ihr modifiziertes Produkt generieren? Nun, ArchGenXML stellt zwar kein Roundtripping zur Verfügung, aber es behält bestimmte Änderungen bei, die Sie im Quellcode vornehmen. So werden Methoden, die Sie in der Klassendefinition modifiziert oder hinzugefügt haben, beibehalten; weiters werden Sie im Quellcode geschützte Codebereiche finden (mit #code-section markiert), die beim Generieren beibehalten werden. Ebenso werden sämtliche Page Templates beibehalten, die innerhalb des skins-Ordners generiert wurden.

Welche UML-Konzepte werden von ArchGenXML unterstützt? ArchGenXML stellt Ihnen eine Reihe von Konzepten aus der UML-Spezifikation für die Plone-Entwicklung zur Verfügung, ich möchte sie hier kurz anhand des oben erwähnten Beispiels anführen:

  • Klassen: ArchGenXML generiert aus jeder Klasse im Modell den Code für genau einen Inhaltstyp. Klassen, die als Abstrakt markiert wurden, werden als nicht instanziierbare Inhaltstypen generiert (z.B. Resource).
  • Methoden: Jede Methode im Modell entspricht einer Methode innerhalb der Klassendefinition des Inhaltstyps. Methoden, die den Stereotyp view oder form besitzen, werden hingegen als Page Template mit einer dazugehörigen Action generiert (z.B project_overview in der Klasse Project). Methoden, die als Stereotyp action besitzen, werden nur als Actions generiert.
  • Attribute werden als Feld in der Schemadefinition generiert.
  • Interfaces werden als Python-Interface-Deklarationen generiert. Klassen, die eine Realisationsbeziehung zu einem Interface besitzen, bekommen das in der Zope-Entwicklung übliche __implements__-Statement verpasst.
  • Vererbung: Generalisierungsbeziehungen zwischen Klassen werden auch in Python als Vererbungsbeziehung generiert; Mehrfachvererbung wird unterstützt. Beispiel: Resource und Person bzw. Room.
  • Aggregation und Composite: Diese beiden Beziehungen drücken sich in ordnerartigen Objekten aus. Die Klasse, die andere enthalten kann, wird als ordnerartiges Objekt generiert, das den Clientteil der Beziehung enthalten kann (mit Hilfe von allowed_content_types). Beispiel: Projekt-Resource für eine Aggregation oder Projekt-Task für ein Composite.
  • Assoziationen werden als ReferenceFields generiert, wobei hier zwischen den Kardinalitäten 0..1 und 0..n unterschieden wird. Beispiel: Projekt-Person oder Task-Resource.
  • Zustandsmodelle (Statemachines) werden als Workflow-Definition generiert, die automatisch beim Installieren des Produkts im Workflow-Tool eingerichtet und der entsprechenden Klasse zugeordnet wird. Beispiel: Das Zustandsdiagramm der Klasse Project

Das ist alles über das Rapid Development, was im Rahmen dieses Buches angeführt werden kann. Es gibt noch eine Reihe von Optionen in ArchGenXML. Viele Feineinstellungen können über Stereotypen und so genannte Tagged Values sowie Kommandozeilenparameter eingestellt werden. Eine umfangreiche und ständig wachsende Online-Dokumentation wurde von Jens Klein initiiert und ist auf plone.org unter http://plone.org/documentation/tutorial/archgenxml-getting-started zu finden.

Daten in einer SQL-Datenbank speichern

Für fast alles, was in diesem Buch gezeigt wurde, sind die Daten in der Zope Object Database (ZODB) gespeichert worden. Ich habe gezeigt, wie Daten im Dateisystem gespeichert werden, aber eine häufig gestellt Frage ist, wie Daten in einer relationalen Datenbank gespeichert werden können. Eine Speicherung in einer relationalen Datenbank lohnt sich vor allem dann, wenn folgende Tatsachen zutreffen:

  • Ihre Datenstrukturen sind starr, und das Schema ändert sich selten (obwohl Archetypes diesen Aspekt abschwächt).
  • Sie haben andere Applikationen, die Zugriff auf die relationale Datenbank haben oder benötigen.
  • Sie haben Tools und andere Anforderungen, die bereits von einer relationalen Datenbank erfüllt werden.
  • Sie haben eine große Menge an Daten, die oft verändert werden.

In einer eher traditionellen CGI-Umgebung werden Sie wahrscheinlich einige SQL-Statements schreiben, um die Daten aus einer relationalen Datenbank zu beziehen. Ähnliches erreichen Sie in Zope durch die Verwendung von ZSQL-Methoden. Diese sind in vielen Situationen nützlich und werden sehr ausführlich im Online-Zope-Buch auf zope.org und einigen anderen Büchern behandelt, aber sie bieten kein richtiges objektrelationales (OR) Mapping. ZSQL-Methoden speichern als Template das SQL-Statement und führen Abfragen aus, indem dieses SQL-Statement an die Datenbank abgesetzt wird. Dies ist recht nützlich, um einfache Abfragen durchzuführen, aber es eignet sich nicht, um ganze Objekte transparent in einer relationalen Datenbank abzuspeichern, und entspricht somit nicht der Gedankenwelt von Plone.

SQLStorage macht sich die Tatsache zunutze, dass Archetypes eine eigene konfigurierbare Schicht für das Speichern von Att