Formulare mit z3c.form

Der nächste logische Schritt ist das Hinzufügen von neuen Kontakten mit einem Formular für Vorname und Nachname. Dieses Formular kann mit dem Schema (IContact) aus interfaces.py generiert werden, wenn Sie die z3c.form-Komponente verwenden.

Verfügbar machen von z3c.form and z3c.formui

Vor der Verwendung von z3c.form muss die Komponente in die Anwendung integriert werden.

Dies erreichen Sie, indem Sie die Datei setup.py öffnen und 'z3c.form' zum ``install_requires-Parameter (etwa bei Zeile 25) hinzufügen. Für die Darstellung des Layouts sollten auch noch die Komponenten z3c.formui und z3c.layer in das System integriert werden. Danach muss noch die Konfiguration für z3c.form und z3c.formui in der Datei zcontact/src/zcontact/configure.zcml erfolgen. z3c.form nutzt zahlreiche andere z3c-Komponenten, welche wiederum neue zcml-Direktiven zur Verfügung stellen. Um diese Direktiven später nutzen zu können, sind die Meta-Dateien am Anfang von configure.zcml, noch vor den anderen include-Anweisungen einzufügen:

<include package="zope.viewlet" file="meta.zcml" />
<include package="z3c.form" file="meta.zcml" />
<include package="z3c.macro" file="meta.zcml" />
<include package="z3c.template" file="meta.zcml" />

Danach sollten dann am Ende der Datei configure.zcml die Komponenten z3c.form and z3c.formui eingefügt werden:

<include package="z3c.form" />
<include package="z3c.formui" />

Sie müssen jetzt den Buildout-Prozess erneut starten, damit die neuen egg-Pakete für z3c.form und z3c.formui heruntergeladen und verfügbar gemacht werden. Starten Sie den Vorgang einfach mit:

$ ./bin/buildout -Nv

Mit der Option -N verhindern Sie das nochmalige Herunterladen von Paketen, die Sie bereits haben (und das ist nur zu empfehlen).

Konfiguration der Anwendung für die Benutzung von z3c.form

Leider ist die Nutzung von z3c.form noch nicht möglich. Weil die z3c-Komponenten ein etwas anderes Muster für die Anwendungsentwicklung verwenden, sind einige zusätzliche Arbeitsschritte notwendig, um eigene Anwendungen zum Laufen zu bringen. Einer dieser Schritte ist die Erstellung eines eigenen Layer und Skin. Leider zwingen Sie die meisten zcml-Direktiven nicht zur direkten Angabe des zu verwendenden Layer, wenn eine Seite oder Sicht (View) registriert wird. Wenn Sie keinen Layer angeben, wird die Seite für den Standard-Layer erzeugt. Es bleibt festzuhalten, dass viele Komponenten (inclusive Rotterdam) ihre Sichten für den Standard-Layer registrieren, was diesen Layer mit unötigen Dingen überfrachtet, die nicht gebraucht werden. Um diese Überfrachtung zu vermeiden registrieren alle z3c-Komponenten ihre Sichten an speziellen komponenten-spezifischen Layern. Die mit den z3c.*-Komponenten gelieferten Layer müssen für die eigenen Komponenten erweitert werden. Deshalb erstellen Sie einen eigenen Layer, der es erlaubt, die Sichten der z3c-Komponente zu verwenden.

Definition eines Layer

Erstellen Sie eine neue Datei , src/zcontact/layer.py und fügen Sie den folgenden Code hinzu:

from z3c.form.interfaces import IFormLayer
from z3c.layer.pagelet import IPageletBrowserLayer

class IZContactBrowserLayer(IFormLayer, IPageletBrowserLayer):
    """ZContact browser layer with form support."""

IFormLayer hat Sichten für alle Widgets die in den generierten Formularen verwendet werden, und IPageletBrowserLayer liefert einige Fehler-Seiten und andere nützliche Ausgaben.

Einen Skin erstellen

Für den Zugriff auf den Layer über den Browser wird ein Skin verwendet. Erstellen Sie dazu eine weitere Datei, src/zcontact/skin.py und fügen Sie den folgenden Code ein:

import z3c.formui.interfaces

from zcontact import layer

class IZContactBrowserSkin(z3c.formui.interfaces.IDivFormLayer,
                           layer.IZContactBrowserLayer):
    """The ZContact browser skin using the div-based layout."""

Beachten Sie, dass unser Skin vom IDivFormLayer erbt, der in der z3c.formui Komponente definiert ist. Wenn die Formulare erzeugt werden, sind die Felder in <div> Tags und nicht in einer Tabelle eingebettet. Es gibt einen weiteren Layer, für ein tabellenbasiertes Layout. Dank der Komponenten-Architektur wäre es auch möglich einen eigenen Layout-Layer zu schreiben, aber das soll jetzt gezeigt werden.

Nun müssen Sie den Skin mit einer neuen Datei src/zcontact/skin.zcml registrieren. Der Skin kann dann wie folgt verwendet werden: http://localhost:8080/++skin++ZContact/ und der Inhalt der Datei sieht wie folgt aus:

<configure xmlns="http://namespaces.zope.org/zope">

  <interface
      interface=".skin.IZContactBrowserSkin"
      type="zope.publisher.interfaces.browser.IBrowserSkinType"
      name="ZContact"
      />
</configure>

Bitte nicht vergessen in die neue zcml-Datei zcontact/configure.zcml die folgende Zeile einzufügen: <include package="zcontact" file="skin.zcml" />.

Ein Formular für die Neuanlage

Sie beginnen mit der Erstellung eines neuen Modules zcontact.browser, durch Anlage eines Ordners unterhalb von src/zcontact/. Der Ordner browser erhält eine leere Datei __init__.py . Danach erstellen Sie die Datei zcontact/browser/contact.py in der alle Formulare definiert werden.

Beginnen Sie mit dem Einfügen des folgenden Codes in die Datei browser/contact.py:

from z3c.form import form, field

from zcontact import interfaces


class ContactAddForm(form.AddForm):
    """A simple add form for contacts."""

    fields = field.Fields(interfaces.IContact)

Die Klasse form.AddForm von der geerbt wird, definiert einige Methoden, für das Hinzufügen und Anlegen unserer neuen Objekte, die Sie später überschreiben werden und einige typische Schalter für ein solches Formular.

Als nächstes wird eine Seite erstellt, die unsere Klasse verwendet und das Formular zur Anzeige bringt. Öffnen Sie die Datei zcontact/browser/configure.zcml und fügen Sie folgendes hinzu:

<configure xmlns="http://namespaces.zope.org/browser">

  <page
      name="addContact.html"
      for="zope.app.folder.interfaces.IFolder"
      permission="zope.Public"
      layer="zcontact.layer.IZContactBrowserLayer"
      class=".contact.ContactAddForm"
      />

</configure>

Das Browser-Modul muß nun am Ende der Datei zcontact/configure.zcml durch Hinzufügen der folgenden Zeile <include package=".browser" /> bekannt gemacht werden.

Für den Test wird jetzt der Server mit ./bin/paster serve deploy.ini (oder debug.ini wenn Sie möchten) gestartet und im Browser die folgende Seite aufgerufen: http://localhost:8080/++skin++ZContact/@@addContact.html.

Hier wird eine Seite mit dem Namen addContact.html für das IFolder-Interface registriert. Weil jeder Root-Ordner das Interface IFolder implementiert, sollte das folgende Formular angezeigt werden:

++resource++_images/addFormSimple.png

Das Formular vollenden

Wenn Sie gerade versucht haben das Formular abzuschicken, indem Sie auf den add-Schalter geklickt haben, sollten Sie eine Fehlermeldung NotImplemented erhalten haben. Wenn Sie paster mit debug.ini statt deploy.ini starten, erhalten Sie vielleicht eine etwas schönere Ausgabe wie hier gezeigt:

++resource++_images/serverError.png

Mit dieser Ausgabe können Sie jede Zeile im traceback erweitern und Python-Code eingeben um das Probem zu untersuchen. Ich war sehr beeindruckt, als ich diese Möglichkeit das erste mal sah.

Um das Problem beheben zu können, müssen drei Methoden für die Klasse ContactAddForm implementiert werden: create, add und nextURL. Um es kurz zu halten, kann es wie folgt implementiert werden, Sie können aber auch einen anderen Weg wählen.

from z3c.form import form, field

from zcontact import interfaces
from zcontact.contact import Contact


class ContactAddForm(form.AddForm):
    """A simple add form for contacts."""

    fields = field.Fields(interfaces.IContact)

    def create(self, data):
        contact = Contact()
        form.applyChanges(self, contact, data)
        return contact

    def add(self, contact):
        self._name = "%s-%s" % (contact.lastName.lower(), contact.firstName.lower())
        self.context[self._name] = contact

    def nextURL(self):
        return '/'

In der create-Methode, beutzen Sie form.applyChanges zum Speichern der Attribute für firstName und lastName eines neuen Kontakt-Objektes. Die Daten, die an die create-Methode übergeben werden, sind eine Zuordnung von Feldnahmen zu den eingegebenen Werten (Mapping) und bereits in die richtigen Python-Datentypen konvertiert. Es gibt auch folgende Möglichkeit: contact.firstName = data['firstName'].

In der nextURL-Methode ist ein hart-codierter Pfad anzugeben, der die Anwendung zum Rotterdam-Standard-Skin umschaltet und dort sollten Sie in der Inhalts-Ansicht den neu erstellten Kontakt sehen können. Dies ist erforderlich, weil wir noch keine eigene Inhalts-Ansicht für den eigenen Layer/Skin geschrieben haben.

Formulare für die Anzeige und Bearbeitung

Anzeige- und Bearbeitungs-Formulare sind einfacher als Formulare für die Neuanlage, weil keine Methoden implementiert werden müssen. Beginnen Sie mit einem Anzeige-Formular.

Erstellung eines Anzeigeformulars

Für das Anzeige-Formular wird eine neue Klasse benötigt, die von form.Form erbt. Um die Formular-Bestandteile (Widgets) als reinen Text und nicht als Eingabefelder darstellen zu können, muß der Modus für das Formular auf DISPLAY_MODE gesetz werden. Es ist eine Konstante, die von z3c.form.interfaces importiert werden kann. Öffnen Sie zcontact/browser/contact.py und fügen Sie den folgenden Code ein:

class ContactDisplayForm(form.Form):
    """A simple display form for contacts."""

    fields = field.Fields(interfaces.IContact)
    mode = DISPLAY_MODE

Vergessen Sie nicht, das neue Formular in configure.zcml wie folgt zu registrieren:

<page
    name="index.html"
    for="..interfaces.IContact"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.ContactDisplayForm"
    />

Nun, können Sie die nextURL-Methode von ContactAddForm auf das neu erstellte Kontakt-Objekt zeigen lassen. Der Code sollte wie folgt aussehen:

def nextURL(self):
    return absoluteURL(self.context[self._name], self.request)

Vergessen Sie bitte nicht die folgende Zeile am Anfang der Datei einzufügen: from zope.traversing.browser.absoluteurl import absoluteURL

Wenn soweit alles geklappt hat, sollte man nach einem Server-Neustart, mit der folgenden URL einen neuen Kontakt angelegt könenen, der dann zum Anzeige-Forumlar weitergeleitet wird: http://localhost:8080/++skin++ZContact/@@addContact.html

Es sollte dann wie hier gezeigt aussehen:

++resource++_images/DisplayFormScreenShot.png

Schalter zum Formular hinzufügen

Nun wird das Formular um zwei Schalter ergänzt, einen zum Bearbeiten und den anderen zum Löschen eines Kontakt-Objektes. Beginnen Sie mit dem Hinzufügen der folgenden import-Anweisung am Anfang der Datei contact.py. Wenn der Anwender auf einen der Schalter klickt, schickt er die Formulardaten zu einem definierten Attribut des Formulars. Standardmäßig wird die URL des Formulars selbst verwendet, wenn ein Schalter gedrückt wird und das Formular wird neu geladen. Wenn das Formular verarbeitet wird, prüft es, welcher Schalter gedrückt wurde und ruft den passenden “Akteur” auf, der als Methode der ContactDisplayForm-Klasse definiert wird.

Mit z3c.form definieren wir einen Schalter und den dazugehörigen Akteur durch Verwendung eines Dekorators. Der Löschen-Schalter soll den Kontakt entfernen und zum Hinzufügen-Formular zurückkehren. Deshalb fügen Sie bitte den folgenden Code zur Klasse ContactDisplayForm hinzu:

@button.buttonAndHandler(u'Delete', name='delete')
def handleDelete(self, action):
    name = getName(self.context)
    parent = getParent(self.context)
    del parent[name]
    nextURL = absoluteURL(parent, self.request)+'/@@addContact.html'
    self.request.response.redirect(nextURL)

Am Anfang der Datei werden noch die folgenden Import-Anweisungen benötigt:

from z3c.form import form, field, button
from z3c.form.interfaces import DISPLAY_MODE
from zope.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.api import getParent, getName

Jetzt fügen Sie noch einen Bearbeiten-Button (‘Edit’) hinzu. Dafür gibt es noch kein Bearbeiten-Formular, dieser Schritt wir hier aber schon vorbereitet. Fügen Sie deshalb folgendes zur ContactDisplayForm-Klasse hinzu:

@button.buttonAndHandler(u'Edit', name="edit")
def handleEdit(self, action):
    nextURL = absoluteURL(self.context, self.request) + '/@@editContact.html'
    self.request.response.redirect(nextURL)

Starten Sie jetzt den Server neu und testen Sie die Schalter. Das Formular sollte wie hier gezeigt aussehen:

++resource++_images/DisplayFormButtonScreenShot.png

Die Erstellung des Bearbeiten-Formulars

Der Vorteil der automatisch generierten Formulare sollte nun sichtbar werden. Der abschließende Schritt, für die Erstellung des Formulars, ist der einfachste von allen. Hier ist der notwendige Code:

class ContactEditForm(form.EditForm):
    """A simple edit form for contacts."""

    fields = field.Fields(interfaces.IContact)

mit der dazugehörigen zcml-Konfiguration:

<page
    name="editContact.html"
    for="..interfaces.IContact"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.ContactEditForm"
    />

Probieren Sie nun das Bearbeiten-Formular aus, nachdem Sie das Anzeige-Formular aufgerufen haben. Beachten Sie den “Apply”-Schalter zum Abschicken der Daten. Als Anwort erhalten Sie eine Statusmeldung für den Erfolg oder Misserfolg der Speicheraktion. An dieser Stelle wollen wir aber zurück zum Hinzufügen-Formular, deshalb fügen wir noch einen “Done”-Schalter mit dem folgenden Code zur ContactEditForm-Klasse hinzu:

@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
    self.request.response.redirect(absoluteURL(self.context, self.request))

Aber halt! Sobald man einen eigenen Schalter erstellt, werden die von form.EditForm Klasse definierten Schalter überschrieben. Um dies zu verhindern, wird die form.EditForm -Klasse erweitert indem Sie form.extends(form.EditForm) direkt hinter der ContactEditForm-Klasse deklarieren. Sie sollten nun ein Bearbeiten-Formular erhalten, das wie folgt aussieht:

++resource++_images/EditFormScreenShot.png

Eine Startseite für die Anwendung

Als vorläufigen Abschluss, erstellen Sie für die Anwendung eine Startseite, bevor Sie sich weiter mit dem “Skinning” beschäftigen. Die nächste Seite soll eine Startseite werden, die eine Möglichkeit zum Hinzufügen eines neuen Kontakts sowie jeweils einen Link zu den vorhandenen Kontakten enthält. Dies alles ist mit einem einfachen page template zcontact/browser/frontpage.pt möglich:

<h3>Welcome to ZContact</h3>
<p>Please tell me what you would like to do:</p>
<ul>
  <li><a href="@@addContact.html">Add a Contact</a></li>
  <li>Look at contacts:
    <ul>
      <li tal:repeat="contact context/values">
      <a tal:attributes="href contact/@@absolute_url"
         tal:content="string:${contact/lastName}, ${contact/firstName}">Last, First</a>
      </li>
  </ul>
  </li>
</ul>

Dieses page template wird über zcml für das IRootFolder-Interface registriert. Fügen Sie in zcontact/browser/configure.zcml folgende Zeilen hinzu:

<page
    name="index.html"
    for="zope.app.folder.interfaces.IRootFolder"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    template="frontpage.pt"
    />

Nach dem Server-Neustart kann die Startseite wie folgt getestet werden: http://localhost:8080/++skin++ZContact/ und Sie sollten in etwa die folgnede Ansicht erhalten:

++resource++_images/FrontPageScreenShot.png

Mit ein paar Formularen und einer funktionierenden Anwendung können Sie sich nun andere z3c.*-Komponenten ansehen.