====================
Generierte Formulare
====================
Im Abschnitt `Portlet erstellen`_ haben wir bereits gesehen, wie mit ``zope.formlib`` aus einem Interface *add*- und *edit*-Formulare generiert wurden. Dies war jedoch nur ein Spezialfall, nun wollen wir die Formulargenerierung mit ``zope.formlib`` allgemeiner betrachten. Hierzu definieren wir zunächst einmal einen View in ``browser/configure.zcml``::
Anschließend definieren wir das Formular vollständig in ``browser/enquiry.py`` – es ist kein zusätzliches *Page Template* erforderlich::
import re
from zope.interface import Interface
from zope import schema
from zope.formlib import form
from Products.Five.formlib import formbase
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.statusmessages.interfaces import IStatusMessage
from Acquisition import aq_inner
from Products.CMFCore.utils import getToolByName
from vs.registration import RegistrationMessageFactory as _
# Define a valiation method for email addresses
class NotAnEmailAddress(schema.ValidationError):
__doc__ = _(u"Invalid email address")
check_email = re.compile(r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+.)*[a-zA-Z]{2,4}").match
def validate_email(value):
if not check_email(value):
raise NotAnEmailAddress(value)
return True
MESSAGE_TEMPLATE = """\
Enquiry from: %(name)s <%(email_address)s>
%(message)s
"""
class IEnquiryForm(Interface):
"""Define the fields of our form
"""
subject = schema.TextLine(title=_(u"Subject"),
required=True)
name = schema.TextLine(title=_(u"Your name"),
required=True)
email_address = schema.ASCIILine(title=_(u"Your email address"),
description=_(u"We will use this to contact you if you request it"),
required=True,
constraint=validate_email)
message = schema.Text(title=_(u"Message"),
description=_(u"Please keep to 1,000 characters"),
required=True,
max_length=1000)
Die ``constraint``-Eigenschaft des ``email_address``-Feldes sollte bei einem Aufruf ``True`` zurückgeben, wenn der Wert gültig ist. Andernfalls wird die in ``__doc__`` angegebene Fehlermeldung ausgegeben.
Eine Übersicht über die verfügbaren Felder und deren Optionen erhalten Sie in ``zope.schema.interfaces``.
Der View für das Formular wird dann abgeleitet aus der Basisklasse von ``zope.formlib``, die in einem Zope2-Produkt mittels ``Products.Five.formlib.formbase`` zur Verfügung steht::
class EnquiryForm(formbase.PageForm):
form_fields = form.FormFields(IEnquiryForm)
label = _(u"Enquiry")
description = _(u"Got a question? Please make an enquiry using the form below!")
- Die ``form_fields``-Variable enthält die darzustellenden Felder. Hier können auch eigene Widgets hinzugefügt oder Felder aus der Liste ausgenommen werden. Weitere Informationen hierzu erhalten Sie in den Interfaces ``IFormFields`` und ``IFormField`` in ``zope.formlib.interfaces``.
- ``label`` und ``description`` werden als Titel und Beschreibung oben auf der Seite dargestellt.
Mit ``__call__`` wird das Formular gerendert. Dabei ändern wir die übliche Darstellung indem der Rahmen und die Reiter beim Editieren nicht angezeigt werden::
def __call__(self):
self.request.set('disable_border', True)
return super(EnquiryForm, self).__call__()
Anschließend wird die einzige Taste dieses Formulars angegeben::
@form.action(_(u"Send"))
def action_send(self, action, data):
Beim Abschicken des Formulars wird die *Decorator*-Funktion aufgerufen und die Werte des Formulars in das Wörterbuch ``data`` geschrieben.
Anschließend wird die E-Mail konstruiert::
context = aq_inner(self.context)
mailhost = getToolByName(context, 'MailHost')
urltool = getToolByName(context, 'portal_url')
portal = urltool.getPortalObject()
email_charset = portal.getProperty('email_charset')
to_address = portal.getProperty('email_from_address')
source = "%s <%s>" % (data['name'], data['email_address'])
subject = data['subject']
message = MESSAGE_TEMPLATE % data
mailhost.secureSend(message, to_address, str(source),
subject=subject, subtype='plain',
charset=email_charset, debug=False,
From=source)
Und schließlich wird auf die Startseite weitergeleitet und dort eine Statusmeldung angezeigt::
confirm = _(u"Thank you! Your enquiry has been received and we will respond as soon as possible")
IStatusMessage(self.request).addStatusMessage(confirm, type='info')
self.request.response.redirect(portal.absolute_url())
return ''
Eigene Widgets schreiben
========================
Wir wollen nun am Beispiel eines Boolschen Feld zum Abonnieren eines Newsletters zeigen, wie eigene Widgets definiert werden können. Hierzu erweitern wir zunächst unsere Schemadefinition::
class IEnquiryForm(Interface):
...
newsletter = schema.Bool(title=u'Subscribe to Newsletter?',
default=False,
required=True)
Nun können wir ein Widget definieren, wobei statt *True* und *False* die Werte *Yes* und *No* angezeigt werden sollen::
from zope.schema import vocabulary as schemavocabulary
from zope.app.form import browser as formbrowser
def YesNoWidget(field, request, true=_('yes'), false=_('no')):
vocabulary = schemavocabulary.SimpleVocabulary.fromItems(((true, True),
(false, False)))
return formbrowser.RadioWidget(field, vocabulary, request)
Dieses Widget wird schließlich in der EnquiryForm-Klasse dem ``newsletter``-Feld zugewiesen::
class EnquiryForm(formbase.PageForm):
form_fields = form.FormFields(IEnquiryForm)
form_fields['newsletter'].custom_widget = YesNoWidget
...
Das Formular sollte nun so aussehen:
.. figure:: enquiry.png
:alt: Anfrage-Formular
Bestehende Widgets verwenden
============================
In ``plone.app.form`` stehen bereits diverse Widgets bereit:
``CheckBoxWidget``
Plone-spezifisches Widget, das eine Checkbox links neben ``label`` anzeigt, mit dem die Felder ``title``, ``label`` und ``required`` ausgeblendet werden können.
``DateComponents``
Ein *View*, der einige Hilfsmehtoden für Datumswidgets bereitstellt.
``LanguageDropdownChoiceWidget``
Ein Dropdown-Widget, das eine lokalisierte Sprachauswahl darstellt.
``UberSelectionWidget``
Widget zum Manipulieren von Auswahlfeldern.
Ein Proof of Concept findet sich in https://svn.plone.org/svn/plone/CMFPlone/branches/plip124-ueberselection-widget/
``WYSIWYGWidget``
Widget für die Verwendung des WYSIWYG-Editors Kupu zum Editieren von Formularfeldern.
Um ein WYSIWYG-Feld in einem Formular anzuzeigen, kann z.B. in ``enquiry.py`` folgendes angegeben werden::
from plone.app.form.widgets.wysiwygwidget import WYSIWYGWidget
...
class IEnquiryForm(Interface):
...
text = schema.Text(title=_(u"Text"),
description=_(u"A field which can contain rich text."),
required=True)
...
class EnquiryForm(base.EditForm):
form_fields = form.FormFields(IEnquiryForm)
...
form_fields['text'].custom_widget = WYSIWYGWidget
Weitere Widgets finden Sie auch in `z3c.widget`_, u.a.:
Country selection Widgets
Dropdown-Widget zur Auswahl eines Landes.
Date Selection Widget
Das ``DateSelectWidget``-Widget bietet drei Auswahlboxen für Tag, Monat und Jahr.
Flash Upload Widget
Konfigurierbares Flash-Frontend
Image Widget
Dieses Widget kann als ``custom_widget`` verwendet werden um Bilder hochzuladen.
Site action für unser Formular
==============================
Schließlich fügen wir in ``vs.policy`` noch eine *site action* hinzu. Hierzu erstellen wir die Datei ``src/vs.policy/vs/policy/profiles/default/actions.xml`` mit folgendem Inhalt::
Dabei wird mit ``name="contact"`` die Aktion, die auf das Plone-Kontaktformular verweist, ersetzt.
.. note::
In `Forms`_ erhalten Sie weitere Informationen zur ``zope.formlib``.
.. _`Forms`: http://pypi.python.org/pypi/zope.formlib/#forms
.. _`Portlet erstellen`: http://www.veit-schiele.de/dienstleistungen/technische-dokumentation/plone-entwicklerhandbuch/artikeltypen/portlet-erstellen.html
.. _`z3c.widget`: http://pypi.python.org/pypi/z3c.widget