--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/zeo.py Thu Mar 05 16:44:16 2015 +0100
@@ -0,0 +1,80 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from zope.interface import Interface, Attribute
+from zope.schema import TextLine, Int, Password, Bool
+
+from pyams_utils import _
+
+
+class IZEOConnection(Interface):
+ """ZEO connection settings interface"""
+
+ name = TextLine(title=_("Connection name"),
+ description=_("Registration name of ZEO connection"),
+ required=True)
+
+ server_name = TextLine(title=_("ZEO server name"),
+ description=_("Hostname of ZEO server"),
+ required=True,
+ default='localhost')
+
+ server_port = Int(title=_("ZEO server port"),
+ description=_("Port number of ZEO server"),
+ required=True,
+ default=8100)
+
+ storage = TextLine(title=_("ZEO server storage"),
+ description=_("Storage name on ZEO server"),
+ required=True,
+ default='1')
+
+ username = TextLine(title=_("ZEO user name"),
+ description=_("User name on ZEO server"),
+ required=False)
+
+ password = Password(title=_("ZEO password"),
+ description=_("User password on ZEO server"),
+ required=False)
+
+ server_realm = TextLine(title=_("ZEO server realm"),
+ description=_("Realm name on ZEO server"),
+ required=False)
+
+ blob_dir = TextLine(title=_("BLOBs directory"),
+ description=_("Directory path for blob data"),
+ required=False)
+
+ shared_blob_dir = Bool(title=_("Shared BLOBs directory ?"),
+ description=_("""Flag whether the blob_dir is a server-shared filesystem """
+ """that should be used instead of transferring blob data over zrpc."""),
+ required=True,
+ default=False)
+
+ connection = Attribute(_("Opened ZEO connection"))
+
+ def get_settings(self):
+ """Get ZEO connection setting as a JSON dict"""
+
+ def update(self, settings):
+ """Update internal fields with given settings dict"""
+
+ def get_connection(self, wait=False, get_storage=False):
+ """Open ZEO connection with given settings"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/zmi/zeo.py Thu Mar 05 16:44:16 2015 +0100
@@ -0,0 +1,120 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces.zeo import IZEOConnection
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import IDataExtractedEvent, DISPLAY_MODE
+from zope.component.interfaces import ISite
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_utils.registry import query_utility
+from pyams_utils.url import absolute_url
+from pyams_utils.zodb import ZEOConnectionUtility
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.control_panel import UtilitiesTable
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from pyramid.events import subscriber
+from pyramid.view import view_config
+from z3c.form import field
+from zope.interface import Invalid
+
+from pyams_utils import _
+
+
+@viewlet_config(name='add-zeo-connection.menu', context=ISite, layer=IAdminLayer,
+ view=UtilitiesTable, manager=IToolbarAddingMenu, permission='system.manage')
+class ZEOConnectionAddMenu(ToolbarMenuItem):
+ """ZEO connection add menu"""
+
+ label = _("Add ZEO connection...")
+ label_css_class = 'fa fa-fw fa-database'
+ url = 'add-zeo-connection.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-zeo-connection.html', context=ISite, layer=IPyAMSLayer, permission='system.manage')
+class ZEOConnectionAddForm(AdminDialogAddForm):
+ """ZEO connection add form"""
+
+ title = _("Utilities")
+ legend = _("Add ZEO connection")
+ icon_css_class = 'fa fa-fw fa-database'
+
+ fields = field.Fields(IZEOConnection)
+ ajax_handler = 'add-zeo-connection.json'
+ edit_permission = None
+
+ def create(self, data):
+ return ZEOConnectionUtility()
+
+ def add(self, object):
+ manager = self.context.getSiteManager()
+ manager['zeo::{0}'.format(object.name.lower())] = object
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'utilities.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=ZEOConnectionAddForm)
+def handle_new_connection_data_extraction(event):
+ """Handle new connection data extraction"""
+ manager = event.form.context.getSiteManager()
+ name = event.data['name']
+ if 'zeo::{0}'.format(name.lower()) in manager:
+ event.form.widgets.errors += (Invalid(_("Specified connection name is already used!")), )
+ connection = query_utility(IZEOConnection, name=name)
+ if connection is not None:
+ event.form.widgets.errors += (Invalid(_("A ZEO connection is already registered with this name!")), )
+
+
+@view_config(name='add-zeo-connection.json', context=ISite, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ZEOConnectionAJAXAddForm(AJAXAddForm, ZEOConnectionAddForm):
+ """ZEO connection add form, AJAX view"""
+
+
+@pagelet_config(name='properties.html', context=IZEOConnection, layer=IPyAMSLayer, permission='system.view')
+class ZEOConnectionPropertiesEditForm(AdminDialogEditForm):
+ """ZEO connection properties edit form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("ZEO connection: {0}")).format(self.context.name)
+
+ legend = _("Update SQLAlchemy engine properties")
+ icon_css_class = 'fa fa-fw fa-database'
+
+ fields = field.Fields(IZEOConnection)
+ ajax_handler = 'properties.json'
+ edit_permission = 'system.manage'
+
+ def updateWidgets(self, prefix=None):
+ super(ZEOConnectionPropertiesEditForm, self).updateWidgets(prefix)
+ self.widgets['name'].mode = DISPLAY_MODE
+
+
+@view_config(name='properties.json', context=IZEOConnection, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ZEOConnectionPropertiesAJAXEditForm(AJAXEditForm, ZEOConnectionPropertiesEditForm):
+ """ZEO connection properties edit form, AJAX view"""
--- a/src/pyams_utils/zodb.py Thu Mar 05 16:44:00 2015 +0100
+++ b/src/pyams_utils/zodb.py Thu Mar 05 16:44:16 2015 +0100
@@ -17,11 +17,26 @@
# import interfaces
from persistent.interfaces import IPersistent
+from pyams_utils.interfaces.site import IOptionalUtility
+from pyams_utils.interfaces.zeo import IZEOConnection
from transaction.interfaces import ITransactionManager
from ZODB.interfaces import IConnection
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+from zope.schema.interfaces import IVocabularyFactory
# import packages
+from persistent import Persistent
from pyams_utils.adapter import adapter_config
+from pyramid.events import subscriber
+from ZEO import ClientStorage
+from ZODB import DB
+from zope.componentvocabulary.vocabulary import UtilityVocabulary
+from zope.container.contained import Contained
+from zope.interface import implementer, provider
+from zope.schema import getFieldNames
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import getVocabularyRegistry
@adapter_config(context=IPersistent, provides=IConnection)
@@ -46,10 +61,98 @@
@adapter_config(context=IPersistent, provides=ITransactionManager)
def get_transaction_manager(obj):
conn = IConnection(obj) # typically this will be
- # zope.app.keyreference.persistent.connectionOfPersistent
+ # zope.keyreference.persistent.connectionOfPersistent
try:
return conn.transaction_manager
except AttributeError:
return conn._txn_mgr
# or else we give up; who knows. transaction_manager is the more
# recent spelling.
+
+
+@implementer(IZEOConnection)
+class ZEOConnection(object):
+ """ZEO connection object"""
+
+ _storage = None
+ _db = None
+ _connection = None
+
+ name = FieldProperty(IZEOConnection['name'])
+ server_name = FieldProperty(IZEOConnection['server_name'])
+ server_port = FieldProperty(IZEOConnection['server_port'])
+ storage = FieldProperty(IZEOConnection['storage'])
+ username = FieldProperty(IZEOConnection['username'])
+ password = FieldProperty(IZEOConnection['password'])
+ server_realm = FieldProperty(IZEOConnection['server_realm'])
+ blob_dir = FieldProperty(IZEOConnection['blob_dir'])
+ shared_blob_dir = FieldProperty(IZEOConnection['shared_blob_dir'])
+
+ def get_settings(self):
+ result = {}
+ for name in getFieldNames(IZEOConnection):
+ result[name] = getattr(self, name)
+ return result
+
+ def update(self, settings):
+ names = getFieldNames(IZEOConnection)
+ for key, value in settings.items():
+ if key in names:
+ setattr(self, key, value)
+
+ def get_connection(self, wait=False, get_storage=False):
+ storage = ClientStorage.ClientStorage((self.server_name, self.server_port),
+ storage=self.storage,
+ username=self.username or '',
+ password=self.password or '',
+ realm=self.server_realm,
+ blob_dir=self.blob_dir,
+ shared_blob_dir=self.shared_blob_dir,
+ wait=wait)
+ db = DB(storage)
+ return (storage, db) if get_storage else db
+
+ @property
+ def connection(self):
+ return self._connection
+
+ # Context manager methods
+ def __enter__(self):
+ self._storage, self._db = self.get_connection(get_storage=True)
+ self._connection = self._db.open()
+ return self._connection.root()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if self._connection is not None:
+ self._connection.close()
+ if self._storage is not None:
+ self._storage.close()
+
+
+@implementer(IOptionalUtility, IAttributeAnnotatable)
+class ZEOConnectionUtility(ZEOConnection, Persistent, Contained):
+ """Persistent ZEO connection utility"""
+
+
+@subscriber(IObjectAddedEvent, context_selector=IZEOConnection)
+def handle_added_connection(event):
+ """Register new ZEO connection when added"""
+ manager = event.newParent
+ manager.registerUtility(event.object, IZEOConnection, name=event.object.name)
+
+
+@subscriber(IObjectRemovedEvent, context_selector=IZEOConnection)
+def handle_removed_connection(event):
+ """Un-register ZEO connection when deleted"""
+ manager = event.oldParent
+ manager.unregisterUtility(event.object, IZEOConnection, name=event.object.name)
+
+
+@provider(IVocabularyFactory)
+class ZEOConnectionVocabulary(UtilityVocabulary):
+ """ZEO connections vocabulary"""
+
+ interface = IZEOConnection
+ nameOnly = True
+
+getVocabularyRegistry().register('PyAMS ZEO connections', ZEOConnectionVocabulary)