Hi all,
I had been joining the GetPaid sprint at the Plone conference in Budapest
for one day and we shortly discussed the session implementation. If I see
it correctly the problems of ticket 209 are resolved (except of broken
ZODBs maybe).
We could not come up with a completely sessionless cart implementation, I
think mainly because nobody knew the GetPaid design well enough. Anyway I
have implemented the session based approach using zope.app.session. It
consists of an ISession utility and an ISessionData local persistent
utility to store the data. This means that sessions (in our case those of
anonymous users) are written to the ZODB the portal is saved in. This can
give you performance problems, depending on the number of anonymous
shopping cart users. I don't know if there are bigger GetPaid installations.
But it should be possible replace the local persistent utility with another
implementation that does the loads/dumps dance internaly and saves the
data somewhere else.
Attached you find a patch converting the implementation and the tests to
use zope.app.session. The functional anon cart tests are failing atm, but I
did not have time to look at them nor do I know if they failed before
already.
..Carsten
--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) |
http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups
"getpaid-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---
Index: Products/PloneGetPaid/configure.zcml
===================================================================
--- Products/PloneGetPaid/configure.zcml (Revision 3169)
+++ Products/PloneGetPaid/configure.zcml (Arbeitskopie)
@@ -222,11 +222,58 @@
/>
+<!-- zope.app.session for shopping carts -->
-<subscriber
- for="*
- getpaid.core.interfaces.IPayableCreationEvent"
- handler=".events.payable_created"
- />
-
+
+<adapter factory=".cart.ClientId"
+ permission="zope.Public" />
+
+<configure package="zope.app.session">
+
+ <adapter
+ factory=".session.Session"
+ provides="zope.app.session.interfaces.ISession"
+ permission="zope.Public"
+ />
+
+ <adapter
+ factory=".session.Session"
+ provides="zope.traversing.interfaces.IPathAdapter"
+ name="session"
+ />
+
+ <class class=".session.Session">
+ <allow interface=".interfaces.ISession" />
+ <implements interface="zope.traversing.interfaces.IPathAdapter" />
+ </class>
+
+
+ <class class=".session.PersistentSessionDataContainer">
+ <require
+ interface=".interfaces.ISessionDataContainer"
+ permission="zope.Public" />
+ <!-- <require -->
+ <!-- set_schema=".interfaces.ISessionDataContainer" -->
+ <!-- permission="zope.ManageServices" /> -->
+ </class>
+
+
+ <class class=".session.SessionData">
+ <allow interface=".interfaces.ISessionData" />
+ </class>
+
+ <class class=".session.SessionPkgData">
+ <allow interface=".interfaces.ISessionPkgData" />
+ </class>
+
+ <subscriber
+ for="zope.app.appsetup.IDatabaseOpenedEvent"
+ handler=".bootstrap.bootStrapSubscriber"
+ />
+
+ <subscriber
+ for="zope.publisher.interfaces.http.IHTTPVirtualHostChangedEvent"
+ handler=".http.notifyVirtualHostChanged"
+ />
</configure>
+</configure>
\ No newline at end of file
Index: Products/PloneGetPaid/config.py
===================================================================
--- Products/PloneGetPaid/config.py (Revision 3169)
+++ Products/PloneGetPaid/config.py (Arbeitskopie)
@@ -4,3 +4,6 @@
PLONE3 = True
except ImportError:
PLONE3 = False
+
+SESSION_KEY = 'PloneGetPaid'
+CART_KEY = 'getpaid.cart'
Index: Products/PloneGetPaid/tests/test_cart.py
===================================================================
--- Products/PloneGetPaid/tests/test_cart.py (Revision 3169)
+++ Products/PloneGetPaid/tests/test_cart.py (Arbeitskopie)
@@ -5,6 +5,7 @@
from zope.component import getUtility
from Testing.ZopeTestCase import ZopeDocTestSuite
+from Testing.ZopeTestCase.utils import setupCoreSessions
from Products.Five.utilities.marker import mark
from utils import optionflags
@@ -13,8 +14,24 @@
from Products.PloneGetPaid import interfaces
from getpaid.core.interfaces import IShoppingCartUtility
+
+class DummySessionAndBrowserIdManager:
+
+ __test_browser_id__ = 'funkyTestBrowserId'
+
+ def getBrowserIdManager(self):
+ return self
+
+ def getBrowserId(self):
+ return self.__test_browser_id__
+
+
class TestCart(PloneGetPaidTestCase):
+ def afterSetUp(self):
+ super(TestCart, self).afterSetUp()
+ setattr(self.app.REQUEST, 'SESSION', DummySessionAndBrowserIdManager())
+
def mySetup(self):
self.setRoles(('Manager',))
id = self.portal.invokeFactory('Document', 'doc')
@@ -161,18 +178,18 @@
...
ValueError: Malformed key: example:malformed:key
"""
-
+
def pauseBrowserSession(self):
"""Utility that disables the current session. The session can
be resumed using resumeBrowserSession.
"""
- self._paused_browser_id = self.portal.REQUEST.browser_id_
- self.portal.REQUEST.browser_id_ = None
+ self._paused_browser_id = self.portal.REQUEST.SESSION.__test_browser_id__
+ self.portal.REQUEST.SESSION.__test_browser_id__ = None
def resumeBrowserSession(self):
"""Utility to restore the previous session.
"""
- self.portal.REQUEST.browser_id_ = self._paused_browser_id
+ self.portal.REQUEST.SESSION.__test_browser_id__ = self._paused_browser_id
def test_suite():
Index: Products/PloneGetPaid/setuphandlers.py
===================================================================
--- Products/PloneGetPaid/setuphandlers.py (Revision 3169)
+++ Products/PloneGetPaid/setuphandlers.py (Arbeitskopie)
@@ -14,11 +14,11 @@
from Products.PloneGetPaid.Extensions.install import install_plone3_portlets
from Products.PloneGetPaid.Extensions.install import setup_payment_options
from Products.PloneGetPaid.Extensions.install import register_shopping_cart_utility
-
from Products.PloneGetPaid.Extensions.install import setup_addressbook
from Products.PloneGetPaid.Extensions.install import setup_named_orders
from Products.PloneGetPaid.Extensions.install import setup_settings
from Products.PloneGetPaid.Extensions.install import register_shopping_cart_utility
+from Products.PloneGetPaid.Extensions.install import register_session_data_utility
from Products.PloneGetPaid.config import PLONE3
@@ -82,6 +82,9 @@
print >> out, "Registering shopping cart utility"
register_shopping_cart_utility(site)
+ print >> out, "Registering session data utility"
+ register_session_data_utility(site)
+
print >> out, "Notifying Installation"
notify_install( site )
Index: Products/PloneGetPaid/cart.py
===================================================================
--- Products/PloneGetPaid/cart.py (Revision 3169)
+++ Products/PloneGetPaid/cart.py (Arbeitskopie)
@@ -3,16 +3,21 @@
$Id$
"""
-from zope.component import getUtility
+from zope.app.session.interfaces import IClientId
+from zope.app.session.interfaces import ISession
+from zope.component import adapts, getUtility
from zope.interface import implements
+from zope.publisher.interfaces import IRequest
from getpaid.core.cart import ShoppingCart
from getpaid.core.interfaces import IShoppingCartUtility
from Products.CMFCore.utils import getToolByName
+from Products.PloneGetPaid.config import CART_KEY, SESSION_KEY
from persistent import Persistent
from BTrees.OOBTree import OOBTree
from AccessControl import getSecurityManager
+
class ShoppingCartUtility(Persistent):
implements(IShoppingCartUtility)
@@ -73,40 +78,30 @@
self._sessions[uid] = cart
return cart
-
def _getCartForSession(self, context, create=False, browser_id=None):
- session_manager = getToolByName(context, 'session_data_manager')
- if browser_id is None:
- if not session_manager.hasSessionData() and not create:
- return
- session = session_manager.getSessionData()
- else:
- session = session_manager.getSessionDataByKey(browser_id)
- if session is None:
- return
- if not session.has_key('getpaid.cart'):
- if create:
- session['getpaid.cart'] = cart = ShoppingCart()
- else:
- return None
- return session['getpaid.cart']
+ session = ISession(context.REQUEST)
+ if browser_id is not None:
+ # we overwrite the client_id that was set during the ISession
+ # initialization
+ session.client_id = browser_id
+
+ sessiondata = session[SESSION_KEY]
+ cart = sessiondata.get(CART_KEY, None)
+ if create and cart is None:
+ sessiondata[CART_KEY] = cart = ShoppingCart()
+ return cart
+
def _getDisposableCart(self, context, browser_id=None):
return ShoppingCart()
def _getMultiShotCart(self, context, cart_id=None):
- session_manager = getToolByName(context, 'session_data_manager')
+ sessiondata = ISession(context.REQUEST)[SESSION_KEY]
+ key = "%s.%s" % (CART_KEY, cart_id)
+ if not sessiondata.has_key(key):
+ sessiondata[key] = ShoppingCart()
+ return sessiondata[key]
- key = "getpaid.cart.%s" % cart_id
-
- session = session_manager.getSessionData()
-
- if not session.has_key(key):
- session[key] = cart = ShoppingCart()
-
- return session[key]
-
-
def destroy(self, context, key=None):
""" Destroy the cart.
"""
@@ -125,37 +120,26 @@
else:
return self._destroyCartForSession(context)
-
def _destroyCartForUser(self, context, uid):
if self._sessions.has_key(uid):
del self._sessions[uid]
-
def _destroyCartForSession(self, context, browser_id=None):
- session_manager = getToolByName(context, 'session_data_manager')
- if browser_id is None:
- if not session_manager.hasSessionData(): #nothing to destroy
- return None
- session = session_manager.getSessionData()
- else:
- session = session_manager.getSessionDataByKey(browser_id)
- if session is None:
- return
- if not session.has_key('getpaid.cart'):
- return
- del session['getpaid.cart']
+ session = ISession(context.REQUEST)
+ if browser_id is not None:
+ # overwrite the client_id that was set
+ # during initialization
+ session.client_id = browser_id
+ sessiondata = session[SESSION_KEY]
+ if sessiondata.has_key(CART_KEY):
+ del sessiondata[CART_KEY]
def _destroyMultiShotCart(self, context, cart_id=None):
- session_manager = getToolByName(context, 'session_data_manager')
+ sessiondata = ISession(context.REQUEST)[SESSION_KEY]
+ key = "%s.%s" % (CART_KEY, cart_id)
+ if sessiondata.has_key(key):
+ del session[key]
- key = "getpaid.cart.%s" % cart_id
- session = session_manager.getSessionData()
-
- if not session.has_key(key):
- return
-
- del session[key]
-
def getKey(self, context):
"""Return key that can be used to recover the cart for the
current user or session.
@@ -164,15 +148,12 @@
if uid is not None:
return 'user:%s' % uid
else:
- session_manager = getToolByName(context, 'session_data_manager')
- if not session_manager.hasSessionData():
+ session = ISession(context.REQUEST)
+ sessiondata = session[SESSION_KEY]
+ if not sessiondata.has_key(CART_KEY):
return None
- session = session_manager.getSessionData()
- if not session.has_key('getpaid.cart'):
- return None
- return 'session:%s' % session.token
+ return 'session:%s' % session.client_id
-
def _decodeKey(self, key):
try:
name, value = key.split(':', 1)
@@ -185,3 +166,19 @@
def manage_fixupOwnershipAfterAdd(self):
pass
+
+
+class ClientId(str):
+ """See zope.app.interfaces.utilities.session.IClientId
+
+ An IClientId Utility that uses Zope2's browser_id_manager
+ implementation.
+ """
+ implements(IClientId)
+ adapts(IRequest)
+
+ def __new__(cls, request):
+ return str.__new__(
+ cls, request.SESSION.getBrowserIdManager().getBrowserId()
+ )
+
Index: Products/PloneGetPaid/Extensions/install.py
===================================================================
--- Products/PloneGetPaid/Extensions/install.py (Revision 3169)
+++ Products/PloneGetPaid/Extensions/install.py (Arbeitskopie)
@@ -12,6 +12,9 @@
from zope.event import notify
from zope.app.component.hooks import setSite
from zope.app.component.interfaces import ISite
+from zope.app.session.interfaces import ISessionDataContainer
+from zope.app.session.session import PersistentSessionDataContainer
+
from Products.PloneGetPaid import generations, preferences, addressbook, namedorder
from Products.PloneGetPaid.interfaces import IGetPaidManagementOptions, IAddressBookUtility, INamedOrderUtility
from Products.PloneGetPaid.config import PLONE3
@@ -61,6 +64,21 @@
# BBB for Zope 2.9
sm.registerUtility(interface=IOrderManager, utility=manager)
+def register_session_data_utility( self ):
+ portal = getToolByName( self, 'portal_url').getPortalObject()
+ sm = portal.getSiteManager()
+ is_already_registered = [u for u in sm.getUtilitiesFor(ISessionDataContainer)]
+ if not len(is_already_registered):
+ session_data = PersistentSessionDataContainer()
+ session_data.timeout = 604800 # 7 days
+ session_data.resolution = 100
+ sm.registerUtility(session_data, ISessionDataContainer)
+ try:
+ sm.registerUtility(component=session_data, provided=ISessionDataContainer)
+ except TypeError:
+ # BBB for Zope 2.9
+ sm.registerUtility(interface=ISessionDataContainer, utility=session_data)
+
def setup_intid( self ):
portal = getToolByName(self, 'portal_url').getPortalObject()
try: