Zope3: Nutzung von Datenbank-Generationen (database generations)
Zope3 bietet einen genialen Weg, Objekte auf Stand zu bringen, wenn sich das Schema geändert hat.
Einleitung
Wenn man mit Zope3 eine Anwendung entwickelt. und diese weiterpflegt nachdem sie (eventuell in mehreren Instanzen) produktiv gegangen ist, kommt es vor, dass sich Schemas von Objekten ändern. Zope3 bietet mit database generations einen Weg die bestehenden Objekte in der ZODB auf den aktuellen Stand zu bringen.
Beispiel
Nehmen wir folgende Beispiel-Applikation:
interfaces.py:
from zope.interface import Interface
from zope.schema import TextLine
class IPerson(Interface):
"""Eine Person."""
vorname = TextLine(title=u"Vorname")
nachname = TextLine(title=u"Nachname")
person.py:
from zope.interface import implements
from persistent import Persistent
from zope.app.containter.contained import Contained
from myapp.interfaces import IPerson
class Person(Persistent, Contained):
"Implementation von IPerson"
implements(IPerson)
vorname = None
nachname = None
Update des Schemas
Nachdem diese Anwedung produktiv gegangen ist und schon viele Personen angelegt wurden. Wird festgelegt, dass auch das Geburtsdatum erfasst werden können muss. Also ändert man die Applikation wie folgt:
interfaces.py:
from zope.interface import Interface
from zope.schema import TextLine, Date
class IPerson(Interface):
"""Eine Person."""
vorname = TextLine(title=u"Vorname")
nachname = TextLine(title=u"Nachname")
gebdat = Date(title=u"Geburtsdatum", required=False) # muss nicht angegeben werden
person.py:
from zope.interface import implements
from persistent import Persistent
from zope.app.containter.contained import Contained
from myapp.interfaces import IPerson
class Person(Persistent, Contained):
"Implementation von IPerson"
implements(IPerson)
vorname = None
nachname = None
gebdat = None
Damit ändern sich aber nicht die bereits in der ZODB existierenden Objekte. Hier kommen dann database generations ins Spiel.
Database Generations
Man legt innerhalb des Paktes ein Verzeichnis an. Für das Beispiel nennen wir es myappgenerations.
In diesem Verzeichnis wird eine Datei __init__.py angelegt:
from zope.app.generations.generations import SchemaManager
key = 'myapp.myappgenerations' # Name des Paketes Punkt Name des Verzeichnisses
MyAppSchemaManager = SchemaManager(
minimum_generation=0,
generation=1,
package_name=key)
Unter minimum_generation wird eingetragen, welche Datenbankgeneration mindestens benötigt wird, um mit der Anwendung zu arbeiten.
Unter generation wird eingetragen, welche Datenbankgeneration mit dem derzeitigen Stand maximal möglich ist.
Es wird eine configure.zcml benötigt:
<configure xmlns="http://namespaces.zope.org/zope">
<utility
name="mycompany.myapp"
provides="zope.app.generations.interfaces.ISchemaManager"
component=".MyAppSchemaManager"
/>
</configure>
Der Name des Utilities sollte die Anwendung eindeutig identifizieren.
Um von Datenbankgeneration 0 (was der Default ist) auf Generation 1 zu kommen, wird das Script evolve1.py im Verzeichnis myappgenerations ausgeführt:
from zope.app.zopeappgenerations import getRootFolder
from zope.app.generations.utility import findObjectsProviding
from myapp.interfaces import IPerson
generation = 1 # gibt die Generation der ZODB an, die mit diesem Script erreicht wird
def evolve(context):
u"""Fügt IPerson das Geburtsdatum hinzu.
""" # Dieser Doc-String wird später im ZMI angezeigt.
root = getRootFolder(context) # Ermitteln des Zope-Root-Folders
for person in findObjectsProviding(root, IPerson):
# Schleife über alle Objekte, die IPerson erfüllen.
person.gebdat = None # Setzen des Defaults
Achtung: findObjectsProviding sollte nur benutzt werden, wenn alle zu findenden Objekte sich in Containern befinden bzw. über eine ununterbrochene Kette von values()-Aufrufen vom root aus erreichbar sind!
Database Generations benutzen
Da wir in unserem SchemaManager gesagt haben, dass die minimale Version die 0 sein soll, wird das Update nicht automatisch asgeführt, da ja Generation 0 ausreicht. Man kann aber per Hand ein Upgrade auf Generation 1 machen.
Dazu meldet man sich als Manager am ZMI (Rotterdam-Skin) an. Einer der Links ist mit "Prozess verwalten" bzw. im Englischen mit "Manage process" beschriftet. Ein Klick darauf führt zu einer Seite auf der es ein Tab "Datenbank-Schemas" gibt. Dieses dient zur Verwaltung der Datenbankgenerationen. Wenn ein Paket, welches Datenbankgenerationen benutzt nicht aktuell ist, kann man die ZODB mittels eines Klicks auf "evolve" aktualisieren. Es wird dann genau um eine Datenbankgeneration aktualisiert.
Weitere Datenbankgenerationen
Wenn dann die nächste Datenbankgeneration ansteht, ist in __init__.py im Schema-Manager die generation zu ändern.
Es wird ein neues evolven.py benötigt. (n ist die Nummer der Datenbankgeneration.) Zope ist neu zu starten und man kann auf die neue Generation aktualisieren.
Weitere Möglichkeiten
Mitunter ist die neue Datenbankgeneration zwingend erforderlich, damit die Anwendung noch funktioniert. Dann setzt man im SchemaManager den Parameter minimum_generation auf die Generation, die mindestens benötigt wird. Beim nächsten Neustart führt Zope das Update bis zu dieser Version automatisch aus.
- Category(s)
- Zope 3
- The URL to Trackback this entry is:
- http://blog.gocept.com/zope3-nutzung-von-datenbank-generationen-database-generations/tbping
Ein Beispiel das Generations braucht wäre etwa:
class MyContainer(Container):
def __init__(self):
self['etwas'] = Etwas()
for person in findObjectsProviding(root, IPerson):
# Schleife über alle Objekte, die IPerson erfüllen.
person.gebdat = None # Setzen des Defaults
wird None auf jedes person object gesetzt, aber würde man vor dem setzen
person.gebdat ausgeben, gibt es auch None zurück
es ändert sich durch dieses evolve nichts ausser dass gebdat auf None im objekt gesetzt werd, da aber None immutable ist, ist das irrelevant
btw: die seite hier ist sehr nüztlich, ich hab schon einiges abgekuckt hier :)
sg
Ich benutze zope.formlib zur Anzeige der Formulare. Wenn ich nun zu einer Klasse für die schon Objekte in der ZODB existieren ein neues Schema-Feld hinzufüge (im Interface und als Attribut auf der Klasse), dann bekomme ich regelmäßig einen AttributeError, wenn ich mir das Formular zu einem dieser Objekte anzeigen lasse. Der AttributeError betrifft immer das neue Attribut, welches es laut zope.formlib nicht gibt, obwohl es auf der Klasse definiert ist.