Commit 085a46be authored by Arnaud Fontaine's avatar Arnaud Fontaine

ERP5Form: Reset Form Field Value Cache on all nodes upon modification.

Before, the field value cache was only invalidated on the node where a field
has been modified, so similarly to reset of ZODB Components, implement
synchronization on all nodes through ZODB Cache Cookie.
......@@ -42,10 +42,6 @@ from hashlib import md5
import time
from zope.interface import Interface
from zope.interface import implements
_field_value_cache = {}
def purgeFieldValueCache():
_field_value_cache.clear()
class ICaptchaProvider(Interface):
"""The CaptchaProvider interface provides a captcha generator."""
......
......@@ -48,9 +48,38 @@ from Products.PageTemplates.Expressions import SecureModuleImporter
from Products.ERP5Type.PsycoWrapper import psyco
import sys
_field_value_cache = {}
def purgeFieldValueCache():
_field_value_cache.clear()
class FieldValueCacheDict(dict):
_last_sync = -1
def clear(self):
super(FieldValueCacheDict, self).clear()
from Products.ERP5.ERP5Site import getSite
try:
portal = getSite()
except IndexError:
pass
else:
portal.newCacheCookie('form_field_value_cache')
self._last_sync = portal.getCacheCookie('form_field_value_cache')
def __getitem__(self, cache_id):
from Products.ERP5.ERP5Site import getSite
try:
portal = getSite()
except IndexError:
pass
else:
cookie = portal.getCacheCookie('form_field_value_cache')
if cookie != self._last_sync:
LOG("ERP5Form.Form", 0, "Resetting form field value cache")
self._last_sync = cookie
self.clear()
raise KeyError('Field cache is outdated and has been reset')
return super(FieldValueCacheDict, self).__getitem__(cache_id)
field_value_cache = FieldValueCacheDict()
# Patch the fiels methods to provide improved namespace handling
......@@ -343,7 +372,7 @@ def get_value(self, id, REQUEST=None, **kw):
id)
try:
value = _field_value_cache[cache_id]
value = field_value_cache[cache_id]
except KeyError:
# either returns non callable value (ex. "Title")
# or a FieldValue instance of appropriate class
......@@ -353,7 +382,7 @@ def get_value(self, id, REQUEST=None, **kw):
# and caching sometimes break these field settings at initialization.
# As the result, we would see broken field editing screen in ZMI.
if cacheable and self._p_oid:
_field_value_cache[cache_id] = value
field_value_cache[cache_id] = value
if callable(value):
return value(field, id, **kw)
......
......@@ -52,6 +52,5 @@ class FieldValueCacheInteractor(Interactor):
Interaction method (defined at the Interactor level).
Make sure all field value caches are purged
"""
from Products.ERP5Form import Form, ProxyField
Form.purgeFieldValueCache()
ProxyField.purgeFieldValueCache()
from Products.ERP5Form.Form import field_value_cache
field_value_cache.clear()
......@@ -60,10 +60,6 @@ from thread import get_ident
_USE_ORIGINAL_GET_VALUE_MARKER = []
_field_value_cache = {}
def purgeFieldValueCache():
_field_value_cache.clear()
class WidgetDelegatedMethod(Method):
"""Method delegated to the proxied field's widget.
"""
......@@ -740,14 +736,15 @@ class ProxyField(ZMIField):
field._p_oid,
id)
from Products.ERP5Form.Form import field_value_cache
try:
value = _field_value_cache[cache_id]
value = field_value_cache[cache_id]
except KeyError:
# either returns non callable value (ex. "Title")
# or a FieldValue instance of appropriate class
value, cacheable = self.getFieldValue(field, id, **kw)
if cacheable:
_field_value_cache[cache_id] = value
field_value_cache[cache_id] = value
if value is _USE_ORIGINAL_GET_VALUE_MARKER:
return proxy_field.get_value(id, **kw)
......
......@@ -70,23 +70,23 @@ class TestFieldValueCache(ERP5TypeTestCase):
# Get form value
field = form.my_first_name
id = 'title'
from Products.ERP5Form.ProxyField import _field_value_cache
from Products.ERP5Form.Form import field_value_cache
cache_id = ('ProxyField.get_value',
field._p_oid,
field._p_oid,
id)
# Make sure cache has field
self.assertTrue(_field_value_cache.has_key(cache_id))
self.assertTrue(field_value_cache.has_key(cache_id))
# Make sure cache and field are equal
self.assertEquals(field.get_value(id), _field_value_cache[cache_id])
self.assertEquals(field.get_value(id), field_value_cache[cache_id])
# Call manage_renameObject
form.manage_renameObject('my_first_name', 'my_first_name2')
form.manage_renameObject('my_first_name2', 'my_first_name')
# Make sure cache has no field
self.assertFalse(_field_value_cache.has_key(cache_id))
self.assertFalse(field_value_cache.has_key(cache_id))
# Render
form()
# Make sure cache has field
self.assertTrue(_field_value_cache.has_key(cache_id))
self.assertTrue(field_value_cache.has_key(cache_id))
# Make sure cache and field are equal
self.assertEquals(field.get_value(id), _field_value_cache[cache_id])
self.assertEquals(field.get_value(id), field_value_cache[cache_id])
......@@ -46,7 +46,7 @@ from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import ERP5Form
from Products.ERP5Form.Form import purgeFieldValueCache
from Products.ERP5Form.Form import field_value_cache
from Products.ERP5Form.Form import getFieldValue
from Products.ERP5Form import Form
from Products.ERP5Form import ProxyField
......@@ -130,7 +130,7 @@ class TestFloatField(ERP5TypeTestCase):
# value is rounded
self.assertEquals('13', self.widget.format_value(self.field, 12.9))
purgeFieldValueCache() # call this before changing internal field values.
field_value_cache.clear() # call this before changing internal field values.
self.field.values['precision'] = 2
self.assertEquals('0.01', self.widget.format_value(self.field, 0.011))
# value is rounded
......@@ -934,42 +934,50 @@ class TestFieldValueCache(ERP5TypeTestCase):
self.assertEqual(False, value.value is field.values['external_validator'])
self.assertEqual(True, type(value.value) is Method)
def _getCacheSize(self, cache_id):
count = 0
for cache_key in field_value_cache.viewkeys():
if cache_key[0] == cache_id:
count += 1
return count
def test_using_cache_or_not(self):
# check standard field in zodb
# make sure that this will use cache.
cache_size = len(Form._field_value_cache)
cache_size = self._getCacheSize('Form.get_value')
self.root.form.field.get_value('title')
self.assertEqual(True, cache_size < len(Form._field_value_cache))
self.assertEqual(True, cache_size < self._getCacheSize('Form.get_value'))
# check on-memory field
# make sure that this will not use cache.
cache_size = len(Form._field_value_cache)
cache_size = self._getCacheSize('Form.get_value')
self.assertEqual(repr(self.root),
self.root.form.my_on_memory_tales_field.get_value('default'))
self.assertEqual('123',
self.root.form.my_on_memory_field.get_value('default'))
self.assertEqual(True, cache_size == len(Form._field_value_cache))
self.assertEqual(True, cache_size == self._getCacheSize('Form.get_value'))
# check proxy field
# make sure that this will use cache.
cache_size = len(ProxyField._field_value_cache)
cache_size = self._getCacheSize('ProxyField.get_value')
self.root.form.proxy_field.get_value('title')
self.assertEqual(True, cache_size < len(ProxyField._field_value_cache))
self.assertEqual(True, cache_size < self._getCacheSize('ProxyField.get_value'))
# check proxy field with tales
# make sure that this will not use cache.
cache_size = len(ProxyField._field_value_cache)
cache_size = self._getCacheSize('ProxyField.get_value')
self.root.form.proxy_field_tales.get_value('title')
self.assertEqual(True, cache_size == len(ProxyField._field_value_cache))
self.assertEqual(True, cache_size == self._getCacheSize('ProxyField.get_value'))
def test_datetime_field(self):
purgeFieldValueCache()
field_value_cache.clear()
# 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)
cache_size = len(field_value_cache)
year_field.get_value('items')
# See Formulator/StandardFields.py(line:174)
......@@ -981,22 +989,22 @@ class TestFieldValueCache(ERP5TypeTestCase):
self.root.form.datetime_field._p_oid,
self.root.form.datetime_field._p_oid,
'start_datetime'
) in Form._field_value_cache)
) in 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)
) in 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))
) in field_value_cache)
self.assertEqual(cache_size, len(field_value_cache))
year_field.get_value('size')
year_field.get_value('default')
self.assertEqual(cache_size+2, len(Form._field_value_cache))
self.assertEqual(cache_size+2, len(field_value_cache))
def makeDummyOid():
import time, random
......
......@@ -30,8 +30,7 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import ERP5Form
from Products.ERP5Form.ProxyField import purgeFieldValueCache
from Products.ERP5Form.Form import field_value_cache
class TestProxify(ERP5TypeTestCase):
......@@ -103,12 +102,12 @@ class TestProxify(ERP5TypeTestCase):
self.assertEqual(field.is_delegated('description'), True)
self.assertEqual(field.get_value('description'), '')
purgeFieldValueCache() # must purge cache before changing internal field value.
field_value_cache.clear() # must purge cache before changing internal field value.
template_field = self.base_view.my_string_field
template_field.values['description'] = 'Description'
self.assertEqual(field.get_value('description'), 'Description')
purgeFieldValueCache()
field_value_cache.clear()
# ListField
self.person_view.manage_addField('my_gender', 'Gender', 'ListField')
......@@ -119,7 +118,7 @@ class TestProxify(ERP5TypeTestCase):
self.assertEqual(field.is_delegated('items'), True)
self.assertEqual(field.get_value('items'), [('Male', 'Male'), ('Female', 'Female')])
purgeFieldValueCache()
field_value_cache.clear()
def test_multi_level_proxify(self):
......@@ -146,7 +145,7 @@ class TestProxify(ERP5TypeTestCase):
self.assertEqual(field.has_value('scrap_variable'), 0)
purgeFieldValueCache() # must purge cache before changing internal field value.
field_value_cache.clear() # must purge cache before changing internal field value.
template_field = self.address_view.my_region
template_field.values['title'] = 'Region'
self.assertEqual(field.get_value('title'), 'Region')
......@@ -192,7 +191,7 @@ class TestProxify(ERP5TypeTestCase):
#Proxify First
self.address_view.proxifyField({'my_region':'Base_view.my_list_field'})
self.person_view.proxifyField({'my_default_region':'Address_view.my_region'})
purgeFieldValueCache()
field_value_cache.clear()
#UnProxify
self.person_view.unProxifyField({'my_default_region':'on'})
field = self.person_view.my_default_region
......@@ -204,7 +203,7 @@ class TestProxify(ERP5TypeTestCase):
#Test unproxify with old instance.
#Proxify First
self.person_view.proxifyField({'my_career_subordination_title':'Base_view.my_relation_string_field'})
purgeFieldValueCache()
field_value_cache.clear()
#UnProxify
self.person_view.unProxifyField({'my_career_subordination_title':'on'})
field = self.person_view.my_career_subordination_title
......
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