Commit 490a56ba authored by Jérome Perrin's avatar Jérome Perrin

core: support storing collections of ERP5 documents in session

Before f359f267 (Use Distributed Cache for Session, 2021-07-19)
when we were using RAM cache for sessions, it was possible to store
collections (eg. dicts or lists) with reference to ERP5 documents in
session, but after this change, using such collections was refused
with error:

    TypeError: Can't pickle objects in acquisition wrappers.

This change restore the possibility of using collections, by being
more clever when we store and retrieve collections
parent e0999427
...@@ -96,7 +96,7 @@ class SessionToolTestCase(ERP5TypeTestCase): ...@@ -96,7 +96,7 @@ class SessionToolTestCase(ERP5TypeTestCase):
self.assertEqual(str(i), attr.getId()) self.assertEqual(str(i), attr.getId())
self.assertEqual(0, len(attr.objectIds())) self.assertEqual(0, len(attr.objectIds()))
def test_store_recursive_temp_object(self): def test_store_temp_object_with_sub_object(self):
doc = self.portal.newContent( doc = self.portal.newContent(
temp_object=True, portal_type='Document', id='doc', title='Doc') temp_object=True, portal_type='Document', id='doc', title='Doc')
doc.newContent( doc.newContent(
...@@ -108,6 +108,35 @@ class SessionToolTestCase(ERP5TypeTestCase): ...@@ -108,6 +108,35 @@ class SessionToolTestCase(ERP5TypeTestCase):
self.assertEqual(doc.sub_doc.getTitle(), 'Sub doc') self.assertEqual(doc.sub_doc.getTitle(), 'Sub doc')
self.assertEqual(len(doc.contentValues()), 1) self.assertEqual(len(doc.contentValues()), 1)
def test_store_temp_object_in_list(self):
doc = self.portal.newContent(
temp_object=True, portal_type='Document', id='doc', title='Doc')
self.portal.portal_sessions.newContent(self.session_id, doc_list=[doc])
self.commit()
doc, = self.portal.portal_sessions[self.session_id]['doc_list']
self.assertEqual(doc.getTitle(), 'Doc')
def test_store_temp_object_in_dict(self):
doc = self.portal.newContent(
temp_object=True, portal_type='Document', id='doc', title='Doc')
self.portal.portal_sessions.newContent(self.session_id, doc_dict={'doc': doc})
self.commit()
doc = self.portal.portal_sessions[self.session_id]['doc_dict']['doc']
self.assertEqual(doc.getTitle(), 'Doc')
def test_store_temp_object_in_nested_container(self):
doc = self.portal.newContent(
temp_object=True, portal_type='Document', id='doc', title='Doc')
self.portal.portal_sessions.newContent(
self.session_id, data={'doc_set_list': [set([doc])]})
self.commit()
doc_set_list = self.portal.portal_sessions[self.session_id]['data']['doc_set_list']
self.assertIsInstance(doc_set_list, list)
doc_set, = doc_set_list
self.assertIsInstance(doc_set, set)
doc, = list(doc_set)
self.assertEqual(doc.getTitle(), 'Doc')
def test_modify_session(self): def test_modify_session(self):
""" Modify session and check that modifications are updated in storage backend.""" """ Modify session and check that modifications are updated in storage backend."""
portal_sessions = self.portal.portal_sessions portal_sessions = self.portal.portal_sessions
......
...@@ -32,6 +32,7 @@ from Products.ERP5Type.Tool.BaseTool import BaseTool ...@@ -32,6 +32,7 @@ from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Acquisition import aq_base from Acquisition import aq_base
from UserDict import UserDict from UserDict import UserDict
import collections
# the ERP5 cache factory used as a storage # the ERP5 cache factory used as a storage
SESSION_CACHE_FACTORY = 'erp5_session_cache' SESSION_CACHE_FACTORY = 'erp5_session_cache'
...@@ -45,6 +46,34 @@ _marker=[] ...@@ -45,6 +46,34 @@ _marker=[]
# global storage plugin # global storage plugin
storage_plugin = None storage_plugin = None
def remove_acquisition_wrapper(obj):
if isinstance(obj, basestring):
return obj
obj = aq_base(obj)
if isinstance(obj, collections.Mapping):
return obj.__class__({
remove_acquisition_wrapper(k): remove_acquisition_wrapper(v)
for k, v in obj.items()})
if isinstance(obj, (collections.Sequence, collections.Set)):
return obj.__class__([remove_acquisition_wrapper(o) for o in obj])
return obj
def restore_acquisition_wrapper(obj, context):
if isinstance(obj, basestring):
return obj
if hasattr(obj, '__of__'):
obj = obj.__of__(context)
if isinstance(obj, collections.Mapping):
return obj.__class__({
restore_acquisition_wrapper(k, context): restore_acquisition_wrapper(v, context)
for k, v in obj.items()})
if isinstance(obj, (collections.Sequence, collections.Set)):
return obj.__class__([restore_acquisition_wrapper(o, context) for o in obj])
return obj
class Session(UserDict): class Session(UserDict):
""" Session acts as a plain python dictionary stored in respecitve Cache Factory/Cache Plugin. """ Session acts as a plain python dictionary stored in respecitve Cache Factory/Cache Plugin.
Depending on cache plugin used as a storage some restrictions may apply. Depending on cache plugin used as a storage some restrictions may apply.
...@@ -80,11 +109,8 @@ class Session(UserDict): ...@@ -80,11 +109,8 @@ class Session(UserDict):
def __getitem__(self, key): def __getitem__(self, key):
if key in self.data: if key in self.data:
value = self.data[key] # returned it wrapped in aquisition context
if hasattr(value, '__of__'): return restore_acquisition_wrapper(self.data[key], self._aq_context)
# returned it wrapped in aquisition context
value = value.__of__(self._aq_context)
return value
raise KeyError(key) raise KeyError(key)
def _updateSessionDuration(self, session_duration): def _updateSessionDuration(self, session_duration):
...@@ -103,7 +129,7 @@ class Session(UserDict): ...@@ -103,7 +129,7 @@ class Session(UserDict):
def __setitem__(self, key, item): def __setitem__(self, key, item):
# save value without its acquisition context # save value without its acquisition context
UserDict.__setitem__(self, key, aq_base(item)) UserDict.__setitem__(self, key, remove_acquisition_wrapper(item))
def update(self, dict=None, **kwargs): # pylint: disable=redefined-builtin def update(self, dict=None, **kwargs): # pylint: disable=redefined-builtin
for k, v in (dict or kwargs).iteritems(): for k, v in (dict or kwargs).iteritems():
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment