Zope 3
Up one levelAktionen 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.)
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.
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.
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.
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 ...
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.
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.
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.
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, invariantEs werden die beiden Felder definiert und zum Schluss wird die Funktion arePasswordsEqual als Invariante des Interfaces definiert.
class IUser(Interface):
password = zope.schema.Password(title=u"Passwort")
password2 = zope.schema.Password(title=u"Wiederholung des Passworts")
arePasswordsEqual = invariant(arePasswordsEqual)
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