Commit 2e40d241 authored by Jérome Perrin's avatar Jérome Perrin

Fix sessions using newTempBase or collections

Fixes after nexedi/erp5!1451 

With the RAM implementation it was possible to use in session:
 - list or dict of temp documents
 - temp documents created with `newTempBase` ( not just `newContent(temp_object=True, ...)` )

This repairs these two use cases.

See merge request nexedi/erp5!1533
parents 30d115c8 86951172
Pipeline #19350 failed with stage
in 0 seconds
......@@ -82,6 +82,14 @@ class SessionToolTestCase(ERP5TypeTestCase):
session = self.portal.portal_sessions[self.session_id]
self.assertEqual(primitives_kw, session)
def test_store_temp_base(self):
portal_sessions = self.portal.portal_sessions
from Products.ERP5Type.Document import newTempBase
session = portal_sessions.newContent(
self.session_id,
temp_base=newTempBase(self.portal, 'temp_base', title='Temp Base'))
self.assertEqual(session['temp_base'].getTitle(), 'Temp Base')
def test_store_temp_object(self):
portal_sessions = self.portal.portal_sessions
session = portal_sessions.newContent(
......@@ -96,7 +104,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 +116,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():
......
......@@ -70,6 +70,7 @@ from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from Products.ERP5Type.Accessor import Base as BaseAccessor
from Products.ERP5Type.mixin.property_translatable import PropertyTranslatableBuiltInDictMixIn
from Products.ERP5Type.mixin.temporary import TemporaryDocumentMixin
from Products.ERP5Type.XMLExportImport import Base_asXML
from Products.ERP5Type.Cache import CachingMethod, clearCache, getReadOnlyTransactionCache
from .Accessor import WorkflowState
......@@ -3626,51 +3627,16 @@ def removeIContentishInterface(cls):
removeIContentishInterface(Base)
class TempBase(Base):
"""A version of Base that does not persist in ZODB.
class TempBase(TemporaryDocumentMixin, Base):
"""A version of Base that does not persist in ZODB.
This class only has the Base methods, so most of the times it is
preferable to use a temporary portal type instead.
"""
isIndexable = ConstantGetter('isIndexable', value=False)
isTempDocument = ConstantGetter('isTempDocument', value=True)
# Declarative security
security = ClassSecurityInfo()
def reindexObject(self, *args, **kw):
pass
def recursiveReindexObject(self, *args, **kw):
pass
def activate(self, *args, **kw):
return self
def setUid(self, value):
self.uid = value # Required for Listbox so that no casting happens when we use TempBase to create new objects
def setTitle(self, value):
"""
Required so that getProperty('title') will work on tempBase objects
The dynamic acquisition work very well for a lot of properties, but
not for title. For example, if we do setProperty('organisation_url'), then
even if organisation_url is not in a propertySheet, the method getOrganisationUrl
will be generated. But this does not work for title, because I(seb)'m almost sure
there is somewhere a method '_setTitle' or 'setTitle' with no method getTitle on Base.
That why setProperty('title') and getProperty('title') does not work.
"""
self.title = value
def getTitle(self):
"""
Returns the title of this document
"""
return getattr(aq_base(self), 'title', None)
security.declarePublic('setProperty')
security.declarePublic('getProperty')
security.declarePublic('edit')
# Persistence.Persistent is one of the superclasses of TempBase, and on Zope2.8
......
......@@ -41,7 +41,7 @@ from OFS.Folder import Folder as OFS_Folder
from persistent import Persistent, wref
from ZODB.serialize import ObjectWriter, ObjectReader
from Products.ERP5Type import Permissions
from Products.ERP5Type.Base import Base, WorkflowMethod
from Products.ERP5Type.Base import Base, TempBase, WorkflowMethod
log = logging.getLogger('ERP5Type')
log.trace = lambda *args, **kw: log.log(5, *args, **kw)
......@@ -165,6 +165,8 @@ if 1:
if klass.__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'):
return Base__setstate__(self, value)
if klass is TempBase:
return Base__setstate__(self, value)
try:
portal_type = value.get('portal_type') or klass.portal_type
except AttributeError:
......
......@@ -40,7 +40,7 @@ class TemporaryDocumentMixin(object):
"""
Setters and attributes that are attached to temporary documents.
"""
isIndexable = 0
isIndexable = PropertyConstantGetter('isIndexable', value=False)
isTempDocument = PropertyConstantGetter('isTempDocument', value=True)
__roles__ = None
......@@ -85,7 +85,9 @@ class TemporaryDocumentMixin(object):
self.title = value
def getTitle(self):
return getattr(self,'title',None)
"""Returns the title of this document
"""
return getattr(aq_base(self), 'title', None)
def edit(self, *args, **kw):
if getattr(self, "_original", None) is None:
......
......@@ -236,7 +236,7 @@ class TestERP5Type(ERP5TypeTestCase, LogInterceptor):
'newTemp*(self, ID) will be removed, use self.newContent(temp_object=True, id=ID, portal_type=...)',
DeprecationWarning, 2)
def test_03_NewTempObject(self):
def test_newTempBase(self):
# Products.ERP5Type.Document.newTempBase is another (not recommended) way
# of creating temp objects
import Products.ERP5Type.Document
......@@ -246,19 +246,33 @@ class TestERP5Type(ERP5TypeTestCase, LogInterceptor):
self.assertTrue(o.isTempObject())
self.assertTrue(guarded_import("Products.ERP5Type.Document", fromlist=["newTempBase"]))
def test_TempObjectPersistent(self):
def _test_temp_object_persistent(self, temp_object):
# Temp objects can not be stored in ZODB
temp_object = self.portal.person_module.newContent(portal_type='Person', temp_object=True)
self.assertTrue(temp_object.isTempObject())
# they can be pickled
self.assertTrue(pickle.dumps(aq_base(temp_object)))
# they can be unpickled
import ZODB.broken
self.assertNotIsInstance(
pickle.loads(pickle.dumps(aq_base(temp_object))),
ZODB.broken.Broken,
)
# but they can not be saved in ZODB accidentally
self.portal.person_module.oops = temp_object
self.assertRaisesRegexp(Exception, "Temporary objects can't be pickled", self.commit)
self.abort()
def test_temp_object_persistent(self):
temp_object = self.portal.person_module.newContent(portal_type='Person', temp_object=True)
self._test_temp_object_persistent(temp_object)
def test_newTempBase_persistent(self):
import Products.ERP5Type.Document
temp_object = Products.ERP5Type.Document.newTempBase(self.portal, 'id')
self._test_temp_object_persistent(temp_object)
def test_warnings_redirected_to_event_log(self):
self._catch_log_errors()
self.addCleanup(self._ignore_log_errors)
......
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