Für dieses Portlet erstellen wir ein Unterpaket entsprechend den Konventionen in plone.app.portlets. Für unser Registrants-Portlet wird das Page Template registrants.pt im Ordner portlets erstellt:
<dl class="portlet portletRegistrants" i18n:domain="vs.registration"> <dt class="portletHeader"> <span class="portletTopLeft"></span> Registrants <span class="portletTopRight"></span> </dt> <tal:items tal:repeat="registrant view/registrants"> <dd class="portletItem" tal:define="oddrow repeat/registrant/odd;" tal:attributes="class python:oddrow and 'portletItem even' or 'portletItem odd'"> <a tal:attributes="href registrant/url" tal:content="registrant/title" /> </dd> </tal:items> </dl>
Dies entspricht dem Aufbau der meisten Plone-Portlets. Dabei ist die Darstellungslogik, welche Registrant-Objekte angezeigt werden, in den View registrants.py ausgelagert worden.
Auch der Aufbau von registrants.py entspricht der üblichen Konvention. Zunächst wird einiges importiert, darunter auch das base-Modul von plone.app.portlets, das verschiedene Basisklassen wie Assignment und Renderer zum Erstellen eines neuen Portlets bereitstellt:
from zope import schema from zope.component import getMultiAdapter from zope.formlib import form from zope.interface import implements from plone.app.portlets.portlets import base from plone.memoize.instance import memoize from plone.portlets.interfaces import IPortletDataProvider from DateTime import DateTime from Acquisition import aq_inner from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.CMFCore.utils import getToolByName from vs.registration.interfaces import IRegistrant from vs.registration import RegistrationMessageFactory as _
Anschließend werden im Interface IRegistrantsPortlet die konfigurierbaren Aspekte des Portlets definiert. Wieder werden die Felder in zope.schema verwendet, um die verschiedenen Attribute anzugeben, aber diesmal werden sie tatsächlich auch zum Erstellen der add- und edit-Formulare für das Portlet verwendet:
class IRegistrantsPortlet(IPortletDataProvider): count = schema.Int(title=_(u'Number of registrants to display'), description=_(u'Maximum number of registrants to be shown'), required=True, default=5) sitewide = schema.Bool(title=_(u"Sitewide registrants"), description=_(u"If enabled, registrants from across the site will be found. " "If disabled, only registrants in this folder and its " "subfolders are eligible."), default=False)
Die Implementierung des Interfaces erfolgt dann in folgender Klasse:
class Assignment(base.Assignment): implements(IRegistrantsPortlet) def __init__(self, count=5, sitewide=False): self.count = count self.sitewide = sitewide @property def title(self): return _(u"Registrants")
Dieser portlet assignment-Typ ist ein persistentes Objekt, das die Konfiguration einer Instanz eines Portlets verwaltet. Die title-Eigenschaft ist definiert in plone.portlets.interfaces.IPortletAssignment, wird deklariert von base.Assignment und entsprechend von Plone im Web-Browser angezeigt.
In der Klasse Renderer wird render aufgerufen, um das Portlet anzuzeigen:
class Renderer(base.Renderer): render = ViewPageTemplateFile('registrants.pt')
Die Eigenschaft available wird verwendet, um zu bestimmen, ob das Portlet angezeigt werden soll:
@property def available(self): return len(self._data()) > 0
Mit der folgenden Definition für registrants wird das Page Template so einfach wie möglich, da nur ein Dictionary mit den benötigten informationen zurückgegeben wird:
def registrants(self): for brain in self._data(): registrant = brain.getObject() yield dict(title=registrant.name, url=brain.getURL())
Schließlich werden noch Klassen für AddForm und EditForm definiert, die Nutzern das Erstellen und Ändern des Registrant-Portlets erlauben. Diese Klassen nutzen zope.formlib, um eine Reihe von Formular-Widgets zu erzeugen (die in der form_fields-Klassenvariablen gehalten werden):
class AddForm(base.AddForm): form_fields = form.Fields(IRegistrantsPortlet) label = _(u"Add Registrants portlet") description = _(u"This portlet displays registrants.") def create(self, data): assignment = Assignment() form.applyChanges(assignment, self.form_fields, data) return assignment class EditForm(base.EditForm): form_fields = form.Fields(IRegistrantsPortlet) label = _(u"Edit Registrants portlet") description = _(u"This portlet displays registrants.")
Konfigurieren und Registrieren neuer Portlet-Typen
Um neue Portlet-Typen zu konfigurieren, wird die Datei portlets/configure.zcml mit folgendem Inhalt erstellt:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:plone="http://namespaces.plone.org/plone" i18n_domain="vs.registration"> <include package="plone.app.portlets" /> <plone:portlet name="vs.Registrants" interface=".registrants.IRegistrantsPortlet" assignment=".registrants.Assignment" renderer=".registrants.Renderer" addview=".registrants.AddForm" editview=".registrants.EditForm" /> </configure>
Damit werden einige Hilfsmethoden, Adapter und Views registriert, Und falls Sie ein nicht-editierbares Portlet erstellen wollen, können Sie auf das editview-Attribut verzichten und statt plone.app.portlets.portlets.base als Basisklasse NullAddForm für die AddForm-Klasse angeben.
Registrieren von Portlets
In Plone 3.1 muss das Portlet zusätzlich in src/vs.registration/vs/registration/profiles/default/portlets.xml angegeben werden:
<?xml version="1.0"?> <portlets> <portlet addview="vs.Registrants" title="Registrants" description="A portlet which can show registrants." /> </portlets>
Dabei entspricht die Angabe für addview dem Namen des Portlets, der in portlets/configure.zcml angegeben wurde.
Testen von Portlet-Typen
Der Test tests/test_portlet_registrants.py entspricht der Konvention für Tests, wie sie in plone.app.portlets.test definiert sind. Er enthält zwei Testfälle:
- TestPortlet
- überprüft, ob das Portlet registriert und installiert ist.
- TestRenderer
- überprüft die Darstellungslogik, die in der Renderer-Klasse definiert wurde.
Portlets automatisch zuweisen
Um zu gewährleisten, dass das Registrants-Portlet für jedes Registration-Objekt angezeigt wird, ändern wir content/registration.py. Dabei wird die Funktion als event handler registriert, der ein registrants-Portlet immer dann hinzufügt, wenn ein Registration-Objekt erstellt wird:
@adapter(IRegistration, IObjectInitializedEvent) def add_registrants_portlet(obj, event): column = getUtility(IPortletManager, name=REGISTRANTS_PORTLET_COLUMN) manager = getMultiAdapter((obj, column,), IPortletAssignmentMapping) assignment = registrants.Assignment() chooser = INameChooser(manager) manager[chooser.chooseName(None, assignment)] = assignment
Mit dem Portlet-Manager wird das Portlet der linken oder rechten Spalte zugewiesen. Anschließend wird mit einem MultiAdapter ein AssignmentMapping, d.h. eine Zuordnungstabelle von Objekt und Spalte erstellt. Und schließlich wird die Zuweisung vorgenommen und mit dem entsprechenden Namen dem Assignment-Manager mitgeteilt.
Schließlich wird der Event-Handler noch registriert in content/configure.zcml:
<subscriber handler=".registration.add_registrants_portlet" />