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
.
Ü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.
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.
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 |
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
|
searchable |
Der Wert |
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 |
read_permission, write_permission |
Die Namen der Berechtigungen, die zum Lesen
bzw. Schreiben des Feldes erforderlich sind.
Die Standardwerte sind |
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
|
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 |
---|---|
|
Eine Zeichenkette oder übersetzbare Nachricht, die als Etikett des Widgets verwendet wird |
|
Eine Zeichenkette oder übersetzbare Nachricht, die als Hilfe-Text verwendet wird |
|
Ein TALES-Ausfruck, die bestimmt, ob ein
Widget angezeigt wird. Die Variablen
|
|
Die Länge einer Textbox oder die Höhe einer Auswahlbox |
|
Höhe einer Textbox |
|
Wird von
|
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.
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.
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
atapi.registerType(Registrant, PROJECTNAME)
registriert den Artikeltyp Registrant, indem der Klassengenerator aufgerufen wird und jedem Feld der Registrant-Klasse drei Methoden hinzufügt:
getter-Methode
falls der Accessor eine Transformation vornimmt und das Editierfeld anders eingelesen werden muss.
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",
...
),