Commit 977bf60f authored by Jérome Perrin's avatar Jérome Perrin Committed by Xiaowu Zhang

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 8a4d5796
......@@ -96,7 +96,7 @@ class SessionToolTestCase(ERP5TypeTestCase):
self.assertEqual(str(i), attr.getId())
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(
temp_object=True, portal_type='Document', id='doc', title='Doc')
doc.newContent(
......@@ -108,6 +108,35 @@ class SessionToolTestCase(ERP5TypeTestCase):
self.assertEqual(doc.sub_doc.getTitle(), 'Sub doc')
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):
""" Modify session and check that modifications are updated in storage backend."""
portal_sessions = self.portal.portal_sessions
......
......@@ -32,6 +32,7 @@ from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions
from Acquisition import aq_base
from UserDict import UserDict
import collections
# the ERP5 cache factory used as a storage
SESSION_CACHE_FACTORY = 'erp5_session_cache'
......@@ -45,6 +46,34 @@ _marker=[]
# global storage plugin
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):
""" 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.
......@@ -80,11 +109,8 @@ class Session(UserDict):
def __getitem__(self, key):
if key in self.data:
value = self.data[key]
if hasattr(value, '__of__'):
# returned it wrapped in aquisition context
value = value.__of__(self._aq_context)
return value
# returned it wrapped in aquisition context
return restore_acquisition_wrapper(self.data[key], self._aq_context)
raise KeyError(key)
def _updateSessionDuration(self, session_duration):
......@@ -103,7 +129,7 @@ class Session(UserDict):
def __setitem__(self, key, item):
# 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
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