Archetypes

Anmerkung: Eine vollständige Referenz zu Archetypes finden Sie in http://plone.org/documentation/manual/archetypes-developer-manual oder in Products.Archetypes.atapi. Für Beispiele empfehlen sich die Quellen von Products.ATContentTypes.

Content-Basisklassen

Üblicherweise sind die Content-Klassen von einer der folgenden Klassen abgeleitet:

BaseContent
ein einfacher, non-folderish Artikeltyp, der die Dublin Core-Metaangaben (über die ExtensibleMetadata-Mixin-Klasse) enthält.
BaseFolder
eine folderish-Version von BaseContent.
OrderedBaseFolder
Version von BaseFolder, der die Sortierung der enthaltenen Objekte erlaubt.
BaseBTreeFolder
Version von BaseFolder, der die Inhalte in einem binary tree speichert und damit gut geeignet ist, mehrere tausend Objekte zu enthalten.

Alle vier Klassen sind importierbar von Products.Archetypes.atapi.

In unserem Fall sollen Registration und Registrant jedoch den Plone-Artikeltypen ähnlich sein, z.B. beim Editieren sollen die Felder in verschiedene Reiter kategorisiert werden und auch ein Feld für Verweise soll enthalten sein. Daher werden die Artikeltypen von Plone erweitert, die in Products.ATContentTypes.content implementiert sind. Registrant erweitert dabei base.ATCTContent, in dem das übliche Verhalten aller einfachen (non-folderish) Artikeltypen von Plone bereits definiert ist:

class Registrant(base.ATCTContent):
    implements(IRegistrant)

    portal_type = "Registrant"
    _at_rename_after_creation = True
    schema = RegistrantSchema
Registrant
erweitert base.ATCTContent und implementiert IRegistrant.
portal_type
ist der eindeutige Name des Artikeltyps.
at_rename_after_creation
benennt Objekte in die normalisierte Version ihres Titels um.

Schema

Für den obigen Artikeltyp wird das Schema direkt oberhalb der Klasse definiert:

RegistrantSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((

    atapi.StringField('email',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.StringWidget(label=_(u"Email"),
                                  description=_(u""))
        ),

    ))

RegistrantSchema['title'].storage = atapi.AnnotationStorage()
RegistrantSchema['title'].widget.label = _(u"Registrant name")
RegistrantSchema['title'].widget.description = _(u"")

RegistrantSchema['description'].storage = atapi.AnnotationStorage()
RegistrantSchema['description'].widget.label = _(u"Address")
RegistrantSchema['description'].widget.description = _("")

RegistrantSchema['email'].storage = atapi.AnnotationStorage()
RegistrantSchema['email'].widget.label = _(u"Email")
RegistrantSchema['email'].widget.description = _(u"")

finalizeATCTSchema(RegistrantSchema, folderish=False, moveDiscussion=False)

class Registrant(base.ATCTContent):
    """Describe a registrant.
    """
    implements(IRegistrant)

    portal_type = "Registrant"
    _at_rename_after_creation = True
    schema = RegistrantSchema

    name = atapi.ATFieldProperty('title')
    address = atapi.ATFieldProperty('description')
    email = atapi.ATFieldProperty('email')

atapi.registerType(Registrant, PROJECTNAME)

Zunächst wird das Schema von einem Basistypen, in diesem Fall ATContentTypeSchema, kopiert und anschließend das eigene Schema angehängt.

Felder

Im Folgenden eine Liste der gebräuchlichsten Felder:

Feld dazugehörige Widgets Beschreibung
StringField StringWidget, SelectionWidget, PasswordWidget Eine einzelne Textzeile
TextField TextAreaWidget, RichWidget Mehrzeiliges Textfeld, wobei das RichWidget den WYSIWYG-Editor einbindet
LinesField LinesWidget, MultiSelectionWidget, InAndOutWidget Liste von Zeichenketten, mehrzeilig
IntegerField IntegerWidget Ganze Zahl
FixedPointField DecimalWidget Dezimalzahl
BooleanField BooleanWidget Wahr/falsch-Checkbox
FileField FileWidget Feld um Dateien hochzuladen
ImageField ImageWidget Feld um Bilder hochzuladen
DateTimeField CalendarWidget Feld um ein Datum auszuwählen
ReferenceField ReferenceWidget, InAndOutWidget Referenz auf ein anderes Archetypes-Objekt

Werden für SelectionWidgets Vokabularien (Wertelisten) verwendet hängt die Umsetzung des Widgets von der Größe der Werteliste ab. Bei bis zu drei Werten werden Radiobuttons verwendet, ab vier Werten erhält man eine Select-Box.

Die Felder lassen sich mit beliebig vielen Eigenschaften versehen. Im Folgenden nur eine Übersicht über die gebräuchlichsten Eigenschaften:

Feldeigenschaft Beschreibung
required Erforderlich, die möglichen Werte sind true oder false.
searchable Der Wert true schließt das Feld in die Searchable Text-Suche ein.
default Bietet einen Standardwert für dieses Feld.
default_method Name einer Methode (als Zeichenkette), die aufgerufen wird, um den Standardwert zu liefern.
schemata Der Name eines Reiters in der Editieransicht. Das Standardschema ist Default. Durch den Aufruf von finalizeATCTSchema() werden verschiedene Änderungen am Schema vorgenommen, die das Plone-spezifische Erscheinungsbild ermöglichen.

read_permission,

write_permission

Die Namen der Berechtigungen, die zum Lesen bzw. Schreiben des Feldes erforderlich sind. Die Standardwerte sind View bzw. Modify portal content.

vocabulary,

vocabulary_factory,

enforceVocabulary

Definieren eines Vokabulars für das Auswahlfeld, s.u.
validators Eine Liste von Feldvalidatoren, s.u.

accessor,

edit_accessor,

mutator

Überschreibt die Namen der Accessor-, Edit-Accessor- oder Mutator-Methode.
widget Widget, mit dem das Feld dargestellt werden soll
storage

Speicherabstraktion, die für dieses Feld verwendet werden soll Der Standardwert ist AttributeStorage

AttributeStorage
die Feldwerte werden in Attributen dieses Objekts gespeichert. Die Attribute haben dabei denselben Namen wie das Feld
AnnotationStorage
speichert die Werte in Zope3-Annotations, wodurch das Risiko von Namenskonflikten vermieden wird

Widgets

Widgets werden in Products.Archetypes.Widget definiert. Und ähnlich wie für Felder gibt es auch für Widgets eine Reihe von Eigenschaften, wovon die Häufigsten unten aufgeführt sind:

Widget-Eigenschaft Beschreibung
label Eine Zeichenkette oder übersetzbare Nachricht, die als Etikett des Widgets verwendet wird
description Eine Zeichenkette oder übersetzbare Nachricht, die als Hilfe-Text verwendet wird
condition Ein TALES-Ausfruck, die bestimmt, ob ein Widget angezeigt wird. Die Variablen object, portal und folder sind in diesem Kontext verfügbar
size Die Länge einer Textbox oder die Höhe einer Auswahlbox
rows Höhe einer Textbox
default_output_type

Wird von RichWidget verwendet um den eingegebenen Text bei der Ausgabe zu verändern

text/x-html-safe
nutzt Plone’s HTML-Filter-Richtlinien zum Ausfiltern potentiell gefährlicher Tags
text/html
kann verwendet werden, wenn Sie Ihren Nutzern trauen

Vokabularien

vocabulary
spezifisches Vokabular für ein Feld.
enforceVocabulary
wenn der Wert True ist und der eingegebene Wert nicht im Vokabular vorhanden ist, gibt Archetypes einen validation error aus.
vocabulary_factory
erwartet den Namen einer Zope3-IVocabularyFactory-Hilfsmethode. Damit kann Archetypes Zope3-Vokabularien nutzen und sie z.B. mit formlib-Formularen teilen.

Das einfachste Vokabular ist eine statische Liste von akzeptierten Werten (ganze Zahlen für das IntegerField und Zeichenketten für das StringField).

Vokabularien werden im allgemeinen zusammen mit einem SelectionWidget, MultiSelectionWidget oder InAndOutWidget verwendet, wobei nur die Werte des Vokabulars für die Auswahl verfügbar sind. Alternativ kann das AddRemoveWidget, das eine flexiblere Handhabung des Vokabulars ermöglicht und über ein separates Egg importiert werden muss. Um das Vokabular zur Verfügung zu haben importieren wir die config.py in der __init__.py:

…
from zope.i18nmessageid import MessageFactory
import config
…

Im Beispiel wollen wir für Gebrauchtwaren festhalten, wie es um die Funktionsfähigkeit bestellt ist. Für das Produkt definieren wir eine statische Liste von Tupeln, mit der die Werte "ja, nein, eingeschränkt" zur Wahl stehen. Die Angaben lauten beispielsweise wie folgt:

ARTICLE_USABLE=DisplayList((
   ('yes', _(u'Yes')),
   ('no', _(u'No')),
   ('some', _(u'somewhat usable')),
   ))

Für die Internationalisierung des Produkts inklusive des Vokabulars ist die MessageFactory zu importieren und jeder String mit _(u'') zu definieren. Siehe auch Erstellen der Übersetzungsdateien.

Dieses Vokabular wird in der content/vsresale.py verwendet:

…
TypeSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((
   atapi.StringField('usable',
       vocabulary=config.ARTICLE_USABLE,
       default='yes',
       widget=atapi.SelectionWidget(label=_(u"usable"),
                                 description=(u"is the article usable?"))
       ),

Es kann auch gegen eine Liste von (value, label)-Tupeln validiert werden, wobei die Etiketten andere Angaben als die Werte des Vokabulars annehmen können. Archetypes transformiert diese Liste in DisplayList (s.a. Products.Archetypes.utils).

Für dynamische Vokabularien kann für vocabulary die Methode eines Objekts, eines übergeordneten Objekts oder ein Skript in einem Skin-Layer angegeben werden. Beim Aufruf erwartet Archetypes eine einfache Werteliste, eine Liste von Tupeln oder eine DisplayList. Hier ein Beispiel für eine solche Implementierung in ATTopic (Products/ATContentTypes/content/topic.py):

LinesField('customViewFields',
            required=False,
            mode="rw",
            default=('Title',),
            vocabulary='listMetaDataFields',
            enforceVocabulary=True,
            write_permission = ChangeTopics,
            widget=InAndOutWidget(
                    label=_(u'label_custom_view_fields', default=u'Table Columns'),
                    description=_(u'help_custom_view_fields',
                                  default=u"Select which fields to display when "
                                           "'Display as Table' is checked.")
                    ),
             ),
…
security.declareProtected(View, 'listMetaDataFields')
def listMetaDataFields(self, exclude=True):
    """Return a list of metadata fields from portal_catalog.
    """
    tool = getToolByName(self, TOOLNAME)
    return tool.getMetadataDisplay(exclude)

Die Methode gibt eine DisplayList mit Werten und Etiketten zurück.

Schlagworte in einen eigenen Index schreiben

Will man die für einen bestimmten Content-Typen vergebenen Schlagworte für alle gleichartigen Objekte desselben Typs verfügbar machen, stellt man den Vokabelbestand über eine Abfrage des portal_catalog bereit. Als Auswahlfeld verwenden wir das AddRemoveWidget, das über die buildout.cfg hinzugefügt wird:

eggs =
    …
    Products.AddRemoveWidget

Diese Liste wird dann in der content/vsresale.py zusammengestellt:

atapi.LinesField('category',
       required=True,
       searchable=True,
       vocabulary='getTagsVocab',
       enforceVocabulary=False,
       accessor="Category"
       widget=atapi.AddRemoveWidget(label=_(u"tags"),
                               description=_(u"")),
       ),

Das Vokabular entnehmen wir über über die Methode getTagsVocab aus dem Bestand, der in den vorhandenen Objekten desselben Content-Typs angelegt wurde. Die Methoden werden wie folgt definiert:

class VsResale(base.ATCTContent):
…
  def getTagsVocab(self):
       """
       Get the available tags as a DisplayList.
       """
       tags = self.getTagsInUse()
       vocab = atapi.DisplayList()
       for t in tags:
           vocab.add(t, t)
       return vocab

  def getTagsInUse(self):
       """
       Get a list of the resale tags in use in this contenttype.
       """
       catalog = getToolByName(self, 'portal_catalog')
       issues = catalog.searchResults(portal_type = 'Resale Goods Type',)
       tags = {}
       result = set()
       for i in issues:
           issue = i.getObject()
           result.update(issue.Category())
       return sorted(result)

Darüberhinaus bringt Plone 3 in plone.app.vocabularies bereits eine Reihe von häufig verwendeten Vokabularien mit.

Feld- und Objektvalidierung

Wenn im Editierformular eines Archtetypes-Artikeltyps auf Speichern geklickt wird, wird die Methode validate() von BaseObject aufgerufen. Alle Felder bieten einfache Validierungen wie diejenigen, ob auch ein Eintrag in einem als erforderlich deklarierten Feld gemacht wurde oder ein Nummernfeld keine Buchstaben enthält. Es lassen sich jedoch auch eigene Validatoren für spezifische Felder schreiben, z.B.:

atapi.TextField('text',
    ....
    validators=("isTidyHtmlWithCleanup",),
    ....
    ),

Dies weist einem Feld einen oder mehrere Validatoren zu, die in der validator registry registriert sein müssen.

Wie solche Validatoren registriert werden können, sehen Sie in Products.ATContentTypes.lib.validators:

validatorList.append(TidyHtmlWithCleanupValidator('isTidyHtmlWithCleanup', title='', description=''))

Dies ist jedoch nur notwendig, wenn der Validator für mehrere Felder und Artikeltypen Verwendung finden soll. Wird nur ein feldspezifischer Validator benötigt, so lässt sich dieser einfach in einer Methode validate_fieldname() der Klasse dieses Artikeltyps hinzufügen wobei fieldname der Name des zu überprüfenden Feldes ist:

def validate_text (self, value):
    if "maybe" in value:
        return _(u"You shouldn’t be so vague.")
    return None

Klassengenerator

atapi.registerType(Registrant, PROJECTNAME)

registriert den Artikeltyp Registrant, indem der Klassengenerator aufgerufen wird und jedem Feld der Registrant-Klasse drei Methoden hinzufügt:

Accessor
getter-Methode
Edit accessor
falls der Accessor eine Transformation vornimmt und das Editierfeld anders eingelesen werden muss.
Mutator
setter-Methode

So werden z.B. für das Feld email die Methoden getEmail(), getRawEmail() und setEmail() als Accessor, edit accessor und mutator erzeugt.

Manchmal kann es auch notwendig werden, eigene getter- und setter-Methoden zu schreiben. Entspricht der Name der Methode derjenigen eines Accessors oder Mutators, nimmt Archetypes an, dass diese Methode anstatt einer generierten verwendet werden soll.

Der Name einer solchen Methode kann in den Feldeigenschaften angegeben werden, z.B. in Products/Archetypes/ExtensibleMetadata.py:

BooleanField(
    'allowDiscussion',
    accessor="isDiscussable",
    mutator="allowDiscussion",
    edit_accessor="editIsDiscussable",
    ...
),