[Erp5-report] r17030 - in /erp5/trunk/products/ERP5Form: ./ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Wed Oct 17 17:47:24 CEST 2007


Author: yusei
Date: Wed Oct 17 17:47:23 2007
New Revision: 17030

URL: http://svn.erp5.org?rev=17030&view=rev
Log:
Fixed a bug that BoundMethod instance in DateTimeField is cached.

Modified:
    erp5/trunk/products/ERP5Form/Form.py
    erp5/trunk/products/ERP5Form/ProxyField.py
    erp5/trunk/products/ERP5Form/tests/testFields.py

Modified: erp5/trunk/products/ERP5Form/Form.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Form/Form.py?rev=17030&r1=17029&r2=17030&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Form/Form.py (original)
+++ erp5/trunk/products/ERP5Form/Form.py Wed Oct 17 17:47:23 2007
@@ -60,13 +60,29 @@
 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 @@
     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 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 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 @@
 
 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 @@
   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)
-
-    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)
+  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, 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 @@
             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

Modified: erp5/trunk/products/ERP5Form/ProxyField.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Form/ProxyField.py?rev=17030&r1=17029&r2=17030&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Form/ProxyField.py (original)
+++ erp5/trunk/products/ERP5Form/ProxyField.py Wed Oct 17 17:47:23 2007
@@ -52,6 +52,7 @@
 
 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 @@
 
   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 @@
 
     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 @@
     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)

Modified: erp5/trunk/products/ERP5Form/tests/testFields.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Form/tests/testFields.py?rev=17030&r1=17029&r2=17030&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Form/tests/testFields.py (original)
+++ erp5/trunk/products/ERP5Form/tests/testFields.py Wed Oct 17 17:47:23 2007
@@ -52,7 +52,8 @@
 
 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 @@
     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 @@
     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




More information about the Erp5-report mailing list