Zope 3

Up one level
Stuff about Zope 3

Aktionen beim Hochfahren des Zope3-Servers ausführen

Wenn man in einer Zope-Applikation sicherstellen möchte, dass die Umgebung korrekt aufgesetzt ist, kann man beim Hochfahren des Zope-Servers entsprechende Aktionen ausführen. (Zum Beispiel, Utilities programmatisch, statt manuell anzulegen.)

by Michael Howitz posted at 2006-01-30 10:12 last modified 2006-01-30 10:12

Um sich in das Hochfahren von Zope einzuklinken, ist es nötig, sich an den IDatabaseOpenedEvent zu subscriben. Das geschieht in der configure.zcml so:

<subscriber
    for="zope.app.appsetup.IDatabaseOpenedEvent"
    handler=".bootstrap.bootStrapSubscriber"
    />

Der Handler ist eine Python-Funktion, die in der Datei bootstrap.py steht:

import transaction
from zope.app.intid.interfaces import IIntIds
from zope.component.interfaces import ISiteManager

from zope.app.appsetup.bootstrap import ensureUtility, getInformationFromEvent
from zope.app.intid import IntIds

def bootStrapSubscriber(event):
"""Subscriber to the IDataBaseOpenedEvent

Create local utilities if not yet present
"""
db, connection, root, root_folder = getInformationFromEvent(event)

# installiere als Beispiel ein UID-Utility
ensureUtility(root_folder, IIntIds, '',
IntIds, copy_to_zlog=False)
transaction.commit()
connection.close()

In diesem Beispiel wird ein "Utility für eindeutige ID" (Unique Id Utility) installiert, wenn es noch nicht vorhanden ist.

Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/aktionen-beim-hochfahren-des-zope-servers-ausfuehren/tbping

Installation von Zope3-Utilities, die Kontext benötigen

Um ein Utility, welches Kontext benötigt (beispielsweise ein Index eines Katalogs) beim Zope-Start oder programmatisch in einer Site zu installieren sind einige Kniffe nötig.

by Michael Howitz posted at 2006-01-30 10:29 last modified 2006-01-30 11:31

Zur Installation von lokalen Utilities beim Zope-Start siehe Installation von Utilities beim Zope-Start.
Hier nur der nötige Programmcode, um den Kontext zu erhalten.

import transaction
from zope.app.intid.interfaces import IIntIds
from zope.app.catalog.interfaces import ICatalog
from zope.app.component import hooks

from zope.app import zapi
from zope.app.appsetup.bootstrap import ensureUtility, getInformationFromEvent
from zope.app.intid import IntIds

from zope.app.catalog.catalog import Catalog
from zope.app.catalog.field import FieldIndex

from exampleapp.interfaces import IColor

def bootStrapSubscriber(event):
"""Subscriber to the IDataBaseOpenedEvent

Create local utilities if not yet present
"""
db, connection, root, root_folder = getInformationFromEvent(event)
ensureUtility(root_folder, IIntIds, '', IntIds, copy_to_zlog=False)
ensureUtility(root_folder, ICatalog, '', Catalog, copy_to_zlog=False)
catalog = zapi.getUtility(ICatalog, context=root_folder)
if not catalog.get('interfaces'):
try:
# Die Installation eines Index erwartet Kontext, den Zope in
# einer thread-globalen Variablen vorhält. Diese muss jetzt
# gesetzt werden, weil die Site ja gerade erst erstellt wird.
old_site = hooks.getSite()
hooks.setSite(root_folder)
# add index
catalog['color'] = FieldIndex('color', IColor, field_callable=False)
finally:
# Die alte Site wieder herstellen
hooks.setSite(old_site)

transaction.commit()
connection.close()


Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/installation-von-zope3-utilities-die-kontext-benoetigen/tbping

Page-Template-Datei in der Zope3-View-Klasse ändern

Wenn man eine Page unterschiedliche Templates anzeigen haben soll, kann man das im ZCML festgelegte Template in der View-Klasse überschreiben.

by Michael Howitz posted at 2006-01-30 11:17 last modified 2006-01-30 11:17

Wenn man in Abhängigkeint von bestimmten Zustandsvariablen (z. B. Nutzereingaben oder Nutzerrechte) unterschiedliche Page-Templates anzeigen will, es aber nicht möglich oder erwünscht ist, einen Redirect zu machen, kann man das zu im Code der View-Klasse statt der Page-Template-Datei, die per ZCML festgelegt wurde, eine andere benutzen.

Das muss natürlich passieren bevor das Template gerendert wird. Das geschieht beim Aufruf der Methode von __call__. Also überschreibt man diese Methode und ändert das Template vor dem Rendern.

from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile, BoundPageTemplate

class MyView(object):
"""Meine View Klasse"""

def __call__(self, *args, **kw):
if irgendeineBedingung:
# wenn Bedingung zutrifft, anderes template verwenden als in configure.zcml angeben
self.index = BoundPageTemplate(
ViewPageTemplateFile('anderesTemplate.pt'), self)

# Überschriebene __call__-Methode aufrufen zum Rendern des Templates.
return super(MyView, self).__call__(*args, **kw)

ViewPageTemplateFile kann als Argument einen relativen Pfad (ausgehend von dem Verzeichnis in dem die View-Klasse liegt) oder einen absoluten Pfad erhalten.

Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/page-template-datei-in-der-view-klasse-aendern/tbping

Tipps zum Entwurf einer Zope3-View-Klasse

Man kann View-Klassen auf recht unterschiedliche Art und Weise schreiben. Hier folgt der Ansatz, der sich als praktikabel erwiesen hat.

by Michael Howitz posted at 2006-01-30 11:19 last modified 2006-01-30 11:19

Wer von Zope 2 kommt, wird vielleicht viel mit redirects arbeiten (wollen), aber das ist gar nicht nötig.

Wenn eine Browser-Page keine Verzweigungen (z.B. mehrstufiges Formular) oder Fehlerbehandung hat, braucht man beim Entwurf der View-Klasse nichts weiter zu beachten.
Das folgende Beispiel soll zeigen, wie man effizient mit mehreren Submit-Buttons umgehen kann und auch eine Fehlerbehandlung machen kann.

Das Beispiel-Template hat zwei Submit-Buttons. Einen darf der Nutzer anklicken, der andere führt zu einer Fehlermeldung.
Im Quellcode sieht das so aus:

<html metal:use-macro="context/@@standard_macros/view">
<body metal:fill-slot="body">

<!-- Fehleranzeige -->
<div class="page_error"
tal:define="error view/error"
tal:condition="error"
tal:content="error"
i18n:translate="">
Page Error
</div>
<form action="@@services.html" method="post">
<input type="submit" name="submit_me" value="Klick mich" />
<input type="submit" name="not_submit" value="Klicken verboten!" />
 </form>
</body>
</html>

Die zugehörige View-Class hat folgendes Aussehen:

class ExampleView(object):
error = '' # der Standard für error ist '', also kein Fehler (siehe Template)

def __call__(self, *args, **kw):
"""Methode, die aufgerufen wird, um den Submit zu behandeln."""
if "submit_me" in self.request:
# wenn der Button mit dem Namen "submit_me" angeklickt wurde
# gibt es einen Eintrag dafür in self.request
self._handleSubmitMe()
elif "not_submit" in self.request:
self._handleForbiddenSubmit()

# Rendern des Templates
return super(ExampleView, self).__call__(*args, **kw)

def _handleSubmitMe(self):
# Code zur Behandlung des Anklickens von "submit_me"
pass

def _handleForbiddenSubmit(self):
# beschreiben des error-Attributes, welches nachher im Template ausgelesen wird
self.error = "Erwischt! Das ist verboten!"


Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/tipps-zum-entwurf-einer-zope3-view-klasse/tbping

Zope3-Adapter für python-builtins

Manchmal muss man von einem python-builtin (z.b. str) oder einer von python importierten Klasse (z.b. decimal.Decimal) adaptieren ...

by Michael Howitz posted at 2006-01-30 11:30 last modified 2006-01-30 11:30

Definition

Im Beispiel wird ein Adapter für str gebaut. Andere Klassen können analog adaptiert werden.
Für das Beispiel soll von str nach ITest adaptiert werden:

interfaces.py:

from zope.interface import Interface

class ITest(Interface):
"Das Interface auf das adaptiert werden soll."

def makeUpper():
"Die Methode, für die Großschreibung"

Der Adapter von str nach ITest kommt nach adapter.py:

class StrTest(object):
"Adapterklasse von IStr nach ITest."

def __init__(self, context):
self.context = context

def makeUpper(self):
return self.context.upper()

In der configure.zcml wird alles zusammengebracht:

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

<adapter
for="str"
provides=".interfaces.ITest"
factory=".adapter.strtest"
/>

</configure>

Das for-Attribut des Adapter-Tags kann als Parameter ein Interface oder eine Klasse erhalten.

Unittest

Um den Adapter in einem Unittest benutzen zu können ist folgendes notwendig:

zusätzliche Imports (zu denen von ITest und StrTest):

from zope.app.testing import ztapi

Registrierung des Adapters:

ztapi.provideAdapter(str, ITest, StrTest)

Benutzung des Adatpers:

a = ITest('asdf')

Probleme mit der Sicherheitsmaschinerie von Zope3

Wenn es sich bei dem python-builtin um keinen "Rock" im Sinne der Sicherheitsmaschinerie von Zope3 handelt, das python-builtin also in einen Security-Proxy verpackt wird, sind außerdem folgende Arbeiten notwendig. (Das trifft zum Beispiel für decimal.Decimal zu.)

Ich zeige die Benutzung ausgehend von obigen str-Beispiel, auch wenn das dafür nicht notwendig ist. Aber es schadet auch nicht.

Per ZCML muss der Zugriff auf die Methoden des Builtins freigegeben werden. Das ist aber nur auf Interface-Ebene möglich. Also wird ein Interface für das python-builtin benötigt, welches alle Methoden definiert, die benutzt werden sollen.

class IStr(Interface):
"Interface für das python-builtin str."

def upper(self):
"Methode upper als Beispiel herausgegriffen."

Im configure.zcml ist das interface dem python-builtin zuzuordnen und freizugeben. Am einfachsten ist sicher die Freigabe mit allow, aber auch einer gezielteren Freigabe mit acquire-pemission sollte nichts im Weg stehen.

<class class="str">
<implements interface=".interfaces.IStr" />
<allow interface=".interfaces.IStr" />
</class>
Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/zope3-adapter-fuer-python-builtins/tbping

Anzeigen oder Weglassen der Zope3-ZMI-Tabs

Auf manchen Seiten erscheinen keine Tabs (Menü "zmi_views"). Dafür gibt es einen einfachen Grund.

by Michael Howitz posted at 2006-01-30 11:33 last modified 2006-01-30 11:33

Wenn man folgendes Macro (view) verwendet, werden die Tabs angezeigt:

<html metal:use-macro="context/@@standard_macros/view">

Mit dem page-Macro hingegen nicht:

<html metal:use-macro="context/@@standard_macros/page">

Warum das so ist, habe ich aber noch nicht herausgefunden.

Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/anzeigen-oder-weglassen-der-zope3-zmi-tabs/tbping

Zope3: Inhaltsobjekt nur innerhalb bestimmter Container zulassen

Anleitung, um ein Inhaltsobjekt nur innerhalb eines bestimmten Container-Typs zuzulassen und für einen Container die zugelassenen Unterobjekte zu beschränken.

by Michael Howitz posted at 2006-01-30 11:50 last modified 2006-01-30 11:50

Definition der Interfaces

interfaces.py:

from zope.app.container.constraints import containes, containers


class IChild:
"Interface für die Kind-Klasse."

class IParent:
"Interface für die Eltern-Klasse."
contains(IChild)

class IChildContained:
"""Interface welches besagt, dass Objekte, die es
implementieren bestimmte Bedingungen erfüllen."""
containers(IParent)

Die Klasse, die IChild implementiert, muss auch IChildContained implementieren, damit Objekte dieser Klasse nur in IParent implementierenden Objekten angelegt werden können.

Die Auftrennung in die Interfaces IChild und IChildContained ist nicht zwingend erforderlich, da die Funktionen contains und containers auch Strings als Argumente annehmen. Das sähe dann so aus:

from zope.app.container.constraints import containes, containers


class IChild:
"Interface für die Kind-Klasse."
containers('.IParent')

class IParent:
"Interface für die Eltern-Klasse."
contains(IChild)

Allerdings wird so durch containers ein Feld __parent__ im Schema von IChild erzeugt, welches zu einem PageError führt, wenn man für das Interface mit zope.formlib die Benutzungsoberfläche erzeugen will. (Für das Feld __parent__ gibt es kein Widget.)

Einbindung mit ZCML

Damit das ZMI die ferstgelegten Constraints korrekt wiederspiegelt, müssen  browser:containerViews für IParent registriert werden, sowie ein browser:addMenuItem für IChild existieren:

<!-- Hinzufügen von Child -->
<browser:addMenuItem
factory="mypackage.Child"
title="Kind"
description="Ein Kind."
/>

<!-- Anzeige von IChild zum Anlegen in IParent -->
<browser:containerViews
for=".interfaces.IParent"
add="zope.ManageContent"
/>
Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/zope3-inhaltsobjekt-nur-innerhalb-bestimmter-container-zulassen/tbping

Zope3: Testen von felderübergreifenden Bedingungen in Interfaces mit zope.formlib

Um felderübergreifende Bedinungen (interface invariants) mit zope.formlib testen zu können sind in Zope 3.2 einige Anstrengungen nötig.

by Michael Howitz posted at 2006-01-30 11:57 last modified 2006-01-31 14:09

Definition

Feldübergreiffende Bedingungen werden in Interfaces als invariants definiert. Als Beispiel soll ein Passwortfeld und eins für die Wiederholung des Passworts dienen. Beide Werte müssen also gleich sein.

Wenn die Bedingung nicht erfüllt ist, soll eine Exception geworfen werden. Da es sich um einen Validierungsfehler handelt, nutze ich zope.schema.ValidationError als Basisklasse:

class PasswordsAreNotEqual(zope.schema.ValidationError):
u"""Das Passwort und die Wiederholung sind nicht gleich."""
zope.interface.implements(zope.app.form.interfaces.IWidgetInputError)

Der docstring der Klasse wird dem Benutzer angezeigt. (Weil die Klasse von zope.schema.ValidationError erbt.)
Es ist notwendig, zope.app.form.interfaces.IWidgetInputError zu implementieren, da nur für dieses Interface in Adapter besteht, um den Fehler in der Benutzungsoberfläche anzuzeigen. (Ein ValidationError erfüllt das angegebene Interface, so dass das implements-Statement der Wahrheit entspricht.)

Folgende Funktion testet die Gleichheit der Passwörter. Als Argument wird ihr das Objekt übergeben. Im Fehlerfall wird die angegebene Exception geworfen.

def arePasswordsEqual(obj)
if obj.password != obj.password2:
raise PasswordsAreNotEqual

Das Interface hat dann als Minimum folgende Gestalt:

from zope.interface import Interface, invariant

class IUser(Interface):
password = zope.schema.Password(title=u"Passwort")
password2 = zope.schema.Password(title=u"Wiederholung des Passworts")

arePasswordsEqual = invariant(arePasswordsEqual)
Es werden die beiden Felder definiert und zum Schluss wird die Funktion arePasswordsEqual als Invariante des Interfaces definiert.

Benutzung mit zope.formlib

zope.formlib testet die Invarianten automatisch und zeigt Fehler oberhalb des Fomulars an, indem die Methode doc der Instanz der Fehlerklasse aufgerufen wird. (Diese Liefert im Fall von zope.schema.ValidationError den docstring der Exception-Klasse)

Wenn man auf dem Exception-Objekt das Attribut widget_title setzt, wird dieses mit einem Doppelpunkt getrennt vor der Fehlermeldung angezeigt. Eine Zuordnung zu einem Feld im Formular findet nicht statt.

Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/zope3-testen-von-felderuebergreifenden-bedingungen-in-interfaces-mit-zope-formlib/tbping