diff --git a/product/ERP5Form/Form.py b/product/ERP5Form/Form.py index 236740a9725f4126a457f7dbcce15ddd8dba94cd..4eee4df54084b7ad47f60fe380688b64ec2ee09f 100644 --- a/product/ERP5Form/Form.py +++ b/product/ERP5Form/Form.py @@ -60,13 +60,29 @@ from Products.Formulator.TALESField import TALESMethod from zLOG import LOG, PROBLEM +def isCacheable(value): + value = aq_base(value) + if type(value) is BoundMethod: + return False + + jar = getattr(value, '_p_jar', None) + if jar is not None: + return False + + dic = getattr(value, '__dict__', None) + if dic is not None: + for i in dic.values(): + jar = getattr(i, '_p_jar', None) + if jar is not None: + return False + return True + + def copyMethod(value): if type(aq_base(value)) is Method: value = Method(value.method_name) elif type(aq_base(value)) is TALESMethod: value = TALESMethod(value._text) - elif type(aq_base(value)) is BoundMethod: - value = BoundMethod(value.object, value.method_name) return value @@ -77,8 +93,6 @@ class StaticValue: value as is) """ def __init__(self, value): - if isinstance(aq_base(value), (Method, TALESMethod)): - value = copyMethod(value) self.value = value def __call__(self, field, id, **kw): @@ -162,8 +176,6 @@ class TALESValue(StaticValue): class OverrideValue(StaticValue): def __init__(self, override): - if isinstance(aq_base(override), (Method, TALESMethod)): - override = copyMethod(override) self.override = override def __call__(self, field, id, **kw): @@ -172,8 +184,6 @@ class OverrideValue(StaticValue): class DefaultValue(StaticValue): def __init__(self, field_id, value): self.key = field_id[3:] - if isinstance(aq_base(value), (Method, TALESMethod)): - value = copyMethod(value) self.value = value def __call__(self, field, id, **kw): @@ -209,38 +219,42 @@ class EditableValue(StaticValue): def getFieldValue(self, field, id, **kw): """ - Return a callable expression + Return a callable expression and cacheable boolean flag """ tales_expr = self.tales.get(id, "") if tales_expr: # TALESMethod is persistent object, so that we cannot cache original one. # Becase if connection which original talesmethod uses is closed, # RuntimeError must occurs in __setstate__. - clone = TALESMethod(tales_expr._text) - return TALESValue(clone) + tales_expr = copyMethod(tales_expr) + return TALESValue(tales_expr), isCacheable(tales_expr) override = self.overrides.get(id, "") if override: - return OverrideValue(override) + override = copyMethod(override) + return OverrideValue(override), isCacheable(override) # Get a normal value. value = self.get_orig_value(id) + value = copyMethod(value) + cacheable = isCacheable(value) field_id = field.id if id == 'default' and field_id.startswith('my_'): - return DefaultValue(field_id, value) + return DefaultValue(field_id, value), cacheable # For the 'editable' value, we try to get a default value if id == 'editable': - return EditableValue(value) + return EditableValue(value), cacheable # Return default value in callable mode if callable(value): - return StaticValue(value) + return StaticValue(value), cacheable # Return default value in non callable mode - return StaticValue(value)(field, id, **kw) + return_value = StaticValue(value)(field, id, **kw) + return return_value, isCacheable(return_value) def get_value(self, id, **kw): REQUEST = get_request() @@ -257,20 +271,19 @@ def get_value(self, id, **kw): if self._p_oid is None: return self._original_get_value(id, **kw) - if 1: - value = getFieldValue(self, field, id, **kw) - else: - cache_id = ('Form.get_value', - self._p_oid, - field._p_oid, - id) + cache_id = ('Form.get_value', + self._p_oid, + field._p_oid, + id) - try: - value = _field_value_cache[cache_id] - except KeyError: - # either returns non callable value (ex. "Title") - # or a FieldValue instance of appropriate class - value = _field_value_cache[cache_id] = getFieldValue(self, field, id, **kw) + try: + value = _field_value_cache[cache_id] + except KeyError: + # either returns non callable value (ex. "Title") + # or a FieldValue instance of appropriate class + value, cacheable = getFieldValue(self, field, id, **kw) + if cacheable: + _field_value_cache[cache_id] = value if callable(value): return value(field, id, **kw) @@ -788,7 +801,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate): type_b = type(b) if type_a is not type_b: return False - elif type_a is Method or type_a is BoundMethod: + elif type_a is Method: return a.method_name==b.method_name elif type_a is TALESMethod: return a._text==b._text diff --git a/product/ERP5Form/ProxyField.py b/product/ERP5Form/ProxyField.py index 452b39b6f2b2317214938732d36c267266853cb7..dfdf2016ccf0386265f51d3a0dc77e82c7b2b57a 100644 --- a/product/ERP5Form/ProxyField.py +++ b/product/ERP5Form/ProxyField.py @@ -52,6 +52,7 @@ from Globals import DTMLFile from Products.Formulator.TALESField import TALESMethod from Products.ERP5Form.Form import StaticValue, TALESValue, OverrideValue, DefaultValue, EditableValue +from Products.ERP5Form.Form import copyMethod, isCacheable _USE_ORIGINAL_GET_VALUE_MARKER = [] @@ -512,14 +513,15 @@ class ProxyField(ZMIField): def getFieldValue(self, field, id, **kw): """ - Return a callable expression + Return a callable expression and cacheable boolean flag """ try: tales_expr = self.get_tales_expression(id) except ValueError: - return None + return None, False if tales_expr: - return TALESValue(tales_expr) + tales_expr = copyMethod(tales_expr) + return TALESValue(tales_expr), isCacheable(tales_expr) # FIXME: backwards compat hack to make sure overrides dict exists if not hasattr(self, 'overrides'): @@ -527,34 +529,39 @@ class ProxyField(ZMIField): override = self.overrides.get(id, "") if override: - return OverrideValue(override) + override = copyMethod(override) + return OverrideValue(override), isCacheable(override) # Get a normal value. try: template_field = self.getRecursiveTemplateField() # Old ListBox instance might have default attribute. so we need to check it. if checkOriginalGetValue(template_field, id): - return _USE_ORIGINAL_GET_VALUE_MARKER + return _USE_ORIGINAL_GET_VALUE_MARKER, True value = self.get_recursive_orig_value(id) except KeyError: # For ListBox and other exceptional fields. - return self._get_value(id, **kw) + return self._get_value(id, **kw), False field_id = field.id + value = copyMethod(value) + cacheable = isCacheable(value) + if id == 'default' and field_id.startswith('my_'): - return DefaultValue(field_id, value) + return DefaultValue(field_id, value), cacheable # For the 'editable' value, we try to get a default value if id == 'editable': - return EditableValue(value) + return EditableValue(value), cacheable # Return default value in callable mode if callable(value): - return StaticValue(value) + return StaticValue(value), cacheable # Return default value in non callable mode - return StaticValue(value)(field, id, **kw) + return_value = StaticValue(value)(field, id, **kw) + return return_value, isCacheable(return_value) security.declareProtected('Access contents information', 'get_value') def get_value(self, id, **kw): @@ -584,7 +591,9 @@ class ProxyField(ZMIField): except KeyError: # either returns non callable value (ex. "Title") # or a FieldValue instance of appropriate class - value = _field_value_cache[cache_id] = self.getFieldValue(field, id, **kw) + value, cacheable = self.getFieldValue(field, id, **kw) + if cacheable: + _field_value_cache[cache_id] = value if value is _USE_ORIGINAL_GET_VALUE_MARKER: return self.getTemplateField().get_value(id, **kw) diff --git a/product/ERP5Form/tests/testFields.py b/product/ERP5Form/tests/testFields.py index 859da312ed7074edc26b9153d2f6670ed70d1b48..befba0ba405a97ada2568bb46536b2e9b2c3d27c 100644 --- a/product/ERP5Form/tests/testFields.py +++ b/product/ERP5Form/tests/testFields.py @@ -52,7 +52,8 @@ ZopeTestCase.installProduct('ERP5Form') from Products.Formulator.StandardFields import FloatField from Products.Formulator.StandardFields import StringField -from Products.Formulator.MethodField import Method +from Products.Formulator.StandardFields import DateTimeField +from Products.Formulator.MethodField import Method, BoundMethod from Products.Formulator.TALESField import TALESMethod from Products.ERP5Type.Core.Folder import Folder @@ -280,10 +281,16 @@ class TestFieldValueCache(unittest.TestCase): form.proxy_field_tales._p_oid = makeDummyOid() form.proxy_field_tales.tales['form_id'] = TALESMethod('string:form') form.proxy_field_tales.tales['field_id'] = TALESMethod('string:field') + # datetime field (input style is list) + form.datetime_field = DateTimeField('datetime_field') + form.datetime_field._p_oid = makeDummyOid() + form.datetime_field._edit(dict(input_style='list')) + for i in form.datetime_field.sub_form.fields.values(): + i._p_oid = makeDummyOid() def test_method_field(self): field = self.root.form.field - value = getFieldValue(field, field, 'external_validator') + value, cacheable = getFieldValue(field, field, 'external_validator') self.assertEqual(False, value.value is field.values['external_validator']) self.assertEqual(True, type(value.value) is Method) @@ -312,6 +319,41 @@ class TestFieldValueCache(unittest.TestCase): self.root.form.proxy_field_tales.get_value('title') self.assertEqual(True, cache_size == len(ProxyField._field_value_cache)) + def test_datetime_field(self): + purgeFieldValueCache() + + # make sure that boundmethod must not be cached. + year_field = self.root.form.datetime_field.sub_form.get_field('year', include_disabled=1) + self.assertEqual(True, type(year_field.overrides['items']) is BoundMethod) + + cache_size = len(Form._field_value_cache) + year_field.get_value('items') + + # See Formulator/StandardFields.py(line:174) + # there are two get_value, start_datetime and end_datetime + cache_size += 2 + + # make sure that boundmethod is not cached(cache size does not change) + self.assertEqual(True, ('Form.get_value', + self.root.form.datetime_field._p_oid, + self.root.form.datetime_field._p_oid, + 'start_datetime' + ) in Form._field_value_cache) + self.assertEqual(True, ('Form.get_value', + self.root.form.datetime_field._p_oid, + self.root.form.datetime_field._p_oid, + 'end_datetime' + ) in Form._field_value_cache) + self.assertEqual(False, ('Form.get_value', + year_field._p_oid, + year_field._p_oid, + 'items' + ) in Form._field_value_cache) + self.assertEqual(cache_size, len(Form._field_value_cache)) + + year_field.get_value('size') + year_field.get_value('default') + self.assertEqual(cache_size+2, len(Form._field_value_cache)) def makeDummyOid(): import time, random