Tabellen mit z3c.table
Im Moment ist die Startseite der ZContact-Anwendung nicht besonders gut geeignet für die Darstellung einer großen Anzahl von Kontakten. Wenn z. B. 3000 Kontakte vorhanden wären, würde die Startseite sehr lang werden und eine entsprechende Ladezeit haben. Darüber hinaus fehlt eine sortierte Ausgabe nach “Vorname” oder “zuletzt geändert”. Die Lösungen der eben beschriebenen Probleme lassen sich mit der Funktionalität von z3c.table realisieren, welches die Sortierung von Spalten sowie eine eingebaute Stapel-Verarbeitung (batching) durch Erweiterung und Anpassung erlaubt.
Wie üblich ist zu Beginn, die Abhängingkeit von z3c.table in der Datei setup.py einzutragen. Danach muß der buildout-Prozess neu durchlaufen werden, der das neue Paket installiert:
$ ./bin/buildout -N
Nicht vergessen, die zcml-Konfiguration in zcontact/configure.zcml einzutragen.
<include package="z3c.table" />
Eine Tablle erzeugen
Jetzt können Sie zcontact/browser/contact.py editieren, um eine Tabelle zu erzeugen. Zunächst importieren Sie z3c.table und hier interessieren vor allem die Module table und column.
from z3c.table import table, column
Wenn die Klasse FrontPage von der Klasse Table erbt, sollten alle Tabellenfunktionalitäten in den Ansichten verfügbar sein. Und so sollte es aussehen:
class FrontPage(BrowserPagelet, table.Table):
"""Pagelet for the front page."""
update = table.Table.update
Beachten Sie die Reihenfolge der Vererbung, die von Bedeutung ist! Beide Klassen BrowserPagelet und Table haben die Methoden update und render. Die render Methode von BrowserPagelet soll weiter genutzt werden, weil sie automatisch das frontpage.pt-Template findet, das mit zcml registriert wurde. Von Table nutzen Sie die update-Methode, weil sie die Tabellen-Zellen mit Daten füllt. Es ist zu betonen, das beide Klassen eine __init__-Methode besitzen und damit den Context und den Request als Parameter erhalten. Die Klasse Table nutzt den Context als ein Container-Objekt, dessen Werte zur Erzeugung der Zeilen einer Tabelle genutzt werden.
Jetzt muß zcontact/browser/frontpage.pt geändert werden, damit die Tabelle dort eingefügt wird, wo bisher die Liste erschien. Das Template sollte etwa so aussehen:
<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: <div tal:replace="structure view/renderTable" /></li> </ul>
Die Klasse Table verwendet die Methode renderTable um den html-Code einer Tabelle zu erzeugen. Diese Methode wird mit dem Schlüsselwort structure aufgerufen, um die html-Repräsentation als Ausgabe zu erhalten.
Wenn Sie jetzt den Server neu starten und die Startseite betrachten, sollten Sie sehen, daß etwas fehlt! Es wurden noch keine Spalten definiert.
Spalten erzeugen
Beginnen Sie mit der Erzeugung von zwei Spalten für die Kontakte, mit Nachname und Vorname. Tabellenspalten werden als eigenständige Objekte unter Verwendung des Interfaces z3c.table.interfaces.IColumn implentiert. Sie erreichen das durch die Verwendung von Basis-Klassen, die erweitert werden. Eine solche Basis-Klasse ist GetAttrColumn. Diese füllt Zellen mit Werten eines bestimmten Attributes. Damit lassen sich sehr schnell die zwei Spalten definieren:
class FirstNameColumn(column.GetAttrColumn):
header = u'First Name'
attrName = 'firstName'
weight = 1
class LastNameColumn(column.GetAttrColumn):
header = u'Last Name'
attrName = 'lastName'
weight = 2
Die Klasse GetAttrColumn verwendet den Wert von attrName, um die Eigenschaft zu finden. Mit dem header erzeugen Sie eine Spaltenüberschrift. Die Eigenschaft weight legt die Reihenfolge der Spalten fest, in unserem Fall in der Reihenfolge: Vorname, Nachname.
Um diese Spalten zur Tabelle hinzufügen zu können, müssen sie als benannte Multiadapter registriert werden. Wenn die Tabelle gerendert wird, werden die Spalten mit Unterstützung von Context, Request und Tabelle nachgeschlagen. Das macht die Tabelle modular (plugable). Fügen Sie die folgenden Elemente in die Datei zcontact/browser/configure.zcml ein.
<zope:adapter
name="firstName"
for="zope.app.folder.interfaces.IRootFolder
zcontact.layer.IZContactBrowserLayer
z3c.table.interfaces.ITable"
provides="z3c.table.interfaces.IColumn"
factory=".contact.FirstNameColumn"
/>
<zope:adapter
name="lastName"
for="zope.app.folder.interfaces.IRootFolder
zcontact.layer.IZContactBrowserLayer
z3c.table.interfaces.ITable"
provides="z3c.table.interfaces.IColumn"
factory=".contact.LastNameColumn"
/>
Sie definieren hier, daß die Spalten registriert werden, wenn sie für einen Context der IRootFolder und Requests die IZContactBrowserLayer und Tabellen die ITable unterstützen. Das bedeutet, Sie könnten Tabellen mit unterschiedlichen Spalten für unterschiedliche Skins erzeugen. Ein Administrations-Skin könnte mehr Informationen zeigen, es gibt unendlich viele Möglichkeiten.
Hier benutzen Sie das erste Mal den zope Namensraum, und zwar in der Datei zcontact/browser/configure.zcml, weshalb er im configure-Element deklariert werden muß. Das sollte dann so aussehen:
<configure xmlns="http://namespaces.zope.org/browser"
xmlns:zope="http://namespaces.zope.org/zope"
xmlns:z3c="http://namespaces.zope.org/z3c">
Jetzt kann der Server gestartet werden und folgendes ist zu sehen:
Es sieht vielleicht etwas anders aus, weil ich ein wenig css verwendet habe.
Zellinhalte anpassen
Vor der Verwendung einer Tabelle waren unsere Kontakte Verweise auf das Kontakt-Anzeige-Formular. Diese Verweise werden wieder aktiviert, indem die Erzeugung der Zellinhalt angepasst wird. Sie könnten das mit der Klasse z3c.table.column.LinkColumn tun, wollen aber zur Demonstartion eine eigene Lösung verwenden. Die Methode renderCell muß überschrieben werden. Das ist unglaublich einfach, wenn Sie den Code wie gezeigt in zcontact/browser/contact.py anpassen:
class FirstNameColumn(column.Column):
header = u'First Name'
weight = 1
def renderCell(self, item):
return '<a href="%s">%s</a>' % (absoluteURL(item, self.request),
item.firstName)
class LastNameColumn(column.Column):
header = u'Last Name'
weight = 2
def renderCell(self, item):
return '<a href="%s">%s</a>' % (absoluteURL(item, self.request),
item.lastName)
Beachten Sie, daß hier column.GetAttrColumn nicht mehr zur Anwendung kommt, weil diese Funktionalität nicht benötigt wird.
Modulare Tabellen
Nun geht es darum, weitere Daten in der Kontakt-Tabelle zu zeigen, wie das Datum der Neuanlage eines Kontaktes oder das Datum der letzten Änderung. Zum Glück, hat schon jemand eine generische Klasse geschrieben, die diesen Datentyp in einer Spalte darstellt. Da die Tabellen-Maschinerie die Zope-Komponenten-Architektur nutzt, brauchen Sie jetzt nur noch die zusätzlichen Spalten in zcml zu registrieren. In diesem konkreten Fall wurden die folgenden zwei zcml-Deklarationen zu zcontact/browser/configure.zcml hinzugefügt:
<zope:adapter
name="created"
for="zope.app.folder.interfaces.IRootFolder
zcontact.layer.IZContactBrowserLayer
z3c.table.interfaces.ITable"
provides="z3c.table.interfaces.IColumn"
factory="z3c.table.column.CreatedColumn"
/>
<zope:adapter
name="modified"
for="zope.app.folder.interfaces.IRootFolder
zcontact.layer.IZContactBrowserLayer
z3c.table.interfaces.ITable"
provides="z3c.table.interfaces.IColumn"
factory="z3c.table.column.ModifiedColumn"
/>
Diese Spalten nutzen das Dublin-Core-Metadaten-System von Zope, um das Erstellungs- oder Änderungsdatum zu erhalten. Dublin-Core funktioniert nur für Daten, die annotiert werden können, d.h. diese Objekte können über IAnnotions adaptiert werden. Damit die Kontakt-Objekte automatisch diese Metadaten unterstützen, muß die Kontakt-Klasse IAttributeAnnotatable implementieren. Das kann mit der zcml-Direktive implements erreicht werden, die dann wie folgt aussieht:
<class class="zcontact.contact.Contact">
<implements interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<require
interface=".interfaces.IContact"
permission="zope.View" />
<require
set_schema=".interfaces.IContact"
permission="zope.ManageContent" />
</class>
Starten Sie nun den Server und Sie werden etwa das Folgende sehen:
Spaltenweise Sortierung
Was ist eine Tabelle ohne Spaltensortierung? Alles was Sie dafür brauchen ist die Methode getSorkKey in der Spalten-Klasse. Eine solche Methode sieht wie folgt aus:
class FirstNameColumn(column.Column):
header = u'First Name'
weight = 1
def getSortKey(self, item):
return item.firstName
def renderCell(self, item):
return '<a href="%s">%s</a>' % (absoluteURL(item, self.request),
item.firstName)
class LastNameColumn(column.Column):
header = u'Last Name'
weight = 2
def getSortKey(self, item):
return item.lastName
def renderCell(self, item):
return '<a href="%s">%s</a>' % (absoluteURL(item, self.request),
item.lastName)
Nach dem Server-Neustart können auf der Startseite die Spalten über die Variablen des Requests sortiert werden. Der Request verwendet zwei Variablen, die vom Namen der Tabelle abhängen. Möchten Sie z. B. die Tabelle nach dem Nachnamen sortieren, verwenden Sie dazu die folgende URL:
http://localhost:8080/++skin++ZContact/@@index.html?table-sortOrder=ascending&table-sortOn=table-lastName-1
Nachdem die Sortierung aktiviert ist, müssen die Sortier-Optionen in die Benutzerführung integriert werden. Eine Möglichkeit ist es, die Überschrift einer jeden Spalte mit einer korrekten URL zu versehen. Dafür gibt es eine Klasse SortingColumnHeader die genau das macht. Dazu müssen Sie nur einen Adapter registrieren. Er nimmt Context, Request, Tabelle und Spalte als Argumente und unterstützt die IColumnHeader-Klasse. Die Registrierung in zcontact/browser/configure.zcml sieht wie folgt aus:
<zope:adapter
for="zope.app.folder.interfaces.IRootFolder
zcontact.layer.IZContactBrowserLayer
z3c.table.interfaces.ITable
z3c.table.interfaces.IColumn"
provides="z3c.table.interfaces.IColumnHeader"
factory="z3c.table.header.SortingColumnHeader"
/>
Nun kann nach dem Server-Neustart durch Klicken die magische Sortierung der Spalten beobachtet werden.
Kurs-Ende!