Portlet erstellen

Wir haben nun zwei Artikeltypen erstellt, die eine Registrierung ermöglichen. Um sich jedoch einen Überblick über die verschiedenen Registrierungen zu verschaffen, müsste man sich die Inhalte jedes Registrierung-Ordners anzeigen lassen. Damit Sie schnell und einfach einen Überblick über Anmeldungen zu den verschiedenen Registrierungen erhalten könen, erstellen wir nun ein Portlet, das diese Aufgabe übernimmt.

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" />