Commit 25ad9ece authored by Julien Muchembled's avatar Julien Muchembled

ERP55Form: drop a broken proxy field cache

When rendering a proxy field, 3 different fields can come in play:
1. the field to be rendered
3. the template field (i.e. not a proxy field) that knows how to render
2. possibly an intermediate proxy field that contains the value to render

What's difficult when rendering a proxy field is to take the above 3 fields
into account and this commit does it by creating a temporary field:
1. 'field' variable in TALES
2. the value
3. the code

Before this commit, 1 could be wrong.
parent cc60f03a
Pipeline #20394 failed with stage
in 0 seconds
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class TestFieldValueCache(ERP5TypeTestCase):
"""
A Sample Test Class
"""
def getTitle(self):
return "TestDiscussionThread"
def getBusinessTemplateList(self):
"""
Tuple of Business Templates we need to install
"""
return (
'erp5_base',
)
def setUpOnce(self):
"""
Do nothing
"""
def afterSetUp(self):
"""
This is ran before each and every test, used to set up the environment
"""
self.person_module = self.portal.getDefaultModule(portal_type='Person')
def testEditZMIFieldPurgesValueCache(self):
"""
This test makes sure that if manage_edit is called on a ZMI
field then its ValueCache is purged.
"""
person = self.person_module.newContent()
form = person.Person_view
# Render
form()
# Get form value
field = form.my_first_name
id_ = 'title'
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))
# Make sure cache and field are equal
self.assertEqual(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))
# Render
form()
# Make sure cache has field
self.assertTrue(field_value_cache.has_key(cache_id))
# Make sure cache and field are equal
self.assertEqual(field.get_value(id_), field_value_cache[cache_id])
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testFieldValueCache</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5Form.tests.testFieldValueCache</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFieldValueCache</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -48,7 +48,7 @@ from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import field_value_cache
from Products.ERP5Form.Form import getFieldValue
from Products.ERP5Form import ProxyField
from Products.ERP5Form.ProxyField import BrokenProxyField, ProxyField
from DateTime import DateTime
import lxml.html
......@@ -67,7 +67,7 @@ class TestRenderViewAPI(ERP5TypeTestCase):
for field in FieldRegistry.get_field_classes().itervalues(): # pylint: disable=no-value-for-parameter
self.assertEqual(('self', 'value', 'REQUEST', 'render_prefix'),
field.render_view.im_func.func_code.co_varnames)
if field is not ProxyField.ProxyField:
if field is not ProxyField:
self.assertEqual(('self', 'field', 'value', 'REQUEST'),
field.widget.render_view.im_func.func_code.co_varnames[:4], '%s %s' % (field.widget, field.widget.render_view.im_func.func_code.co_varnames[:4]))
......@@ -712,6 +712,9 @@ class TestMultiListField(ERP5TypeTestCase):
class TestProxyField(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return 'erp5_core_proxy_field_legacy',
def getTitle(self):
return "Proxy Field"
......@@ -731,9 +734,9 @@ class TestProxyField(ERP5TypeTestCase):
'my_title', 'Title', 'StringField')
proxy_field = self.addField(self.container.Base_view,
'my_title', 'Not Title', 'ProxyField')
self.assertEqual(None, proxy_field.getTemplateField())
self.assertEqual(None, proxy_field.get_value('enable'))
self.assertEqual(None, proxy_field.get_value('default'))
self.assertIsNone(proxy_field.getTemplateField())
self.assertFalse(proxy_field.get_value('enabled'))
self.assertRaises(BrokenProxyField, proxy_field.get_value, 'default')
proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary',
field_id='my_title',))
......@@ -871,7 +874,7 @@ class TestProxyField(ERP5TypeTestCase):
proxy_field = self.addField(self.container.Base_view,
'my_String', '', 'ProxyField')
proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary',
field_id='my_date',))
field_id='my_string',))
proxy_field.manage_edit_surcharged_xmlrpc(dict(title='Title'))
self.assertFalse(proxy_field.is_delegated('title'))
......@@ -1106,12 +1109,12 @@ class TestFieldValueCache(ERP5TypeTestCase):
'default': 'python: repr(here)'})
form.my_on_memory_field._p_oid = None
# proxy field
addField(ProxyField.ProxyField('proxy_field'))
addField(ProxyField('proxy_field'))
form.proxy_field._p_oid = makeDummyOid()
form.proxy_field.values['form_id'] = 'form'
form.proxy_field.values['field_id'] = 'field'
# proxy field with tales
addField(ProxyField.ProxyField('proxy_field_tales'))
addField(ProxyField('proxy_field_tales'))
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')
......@@ -1141,7 +1144,7 @@ class TestFieldValueCache(ERP5TypeTestCase):
# make sure that this will use cache.
cache_size = self._getCacheSize('Form.get_value')
self.root.form.field.get_value('title')
self.assertEqual(True, cache_size < self._getCacheSize('Form.get_value'))
self.assertLess(cache_size, self._getCacheSize('Form.get_value'))
# check on-memory field
# make sure that this will not use cache.
......@@ -1150,19 +1153,7 @@ class TestFieldValueCache(ERP5TypeTestCase):
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 == self._getCacheSize('Form.get_value'))
# check proxy field
# make sure that this will use cache.
cache_size = self._getCacheSize('ProxyField.get_value')
self.root.form.proxy_field.get_value('title')
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 = self._getCacheSize('ProxyField.get_value')
self.root.form.proxy_field_tales.get_value('title')
self.assertEqual(True, cache_size == self._getCacheSize('ProxyField.get_value'))
self.assertEqual(cache_size, self._getCacheSize('Form.get_value'))
class TestCaptchaField(ERP5TypeTestCase):
......
......@@ -26,12 +26,16 @@
#
##############################################################################
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.Formulator.XMLToForm import XMLToForm
import re
from functools import partial
from unittest import expectedFailure
from lxml import etree
from Products.Formulator.FormToXML import formToXML
from Products.Formulator.TALESField import TALESMethod
from lxml import etree
from unittest import expectedFailure
from Products.Formulator.XMLToForm import XMLToForm
from Products.ERP5Form.ProxyField import BrokenProxyField
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class TestProxyField(ERP5TypeTestCase):
"""
......@@ -396,15 +400,19 @@ return printed
form = skin_folder._getOb('Base_viewGeek', None)
form.manage_addField('my_title', 'Title', 'ProxyField')
field = getattr(form, 'my_title')
field = form.my_title
self.assertFalse(form.get_fields())
self.assertEqual([field], form.get_fields(include_disabled=True))
self.assertIsNone(field.getTemplateField())
self.assertEqual('', field.render())
self.assertEqual('', field.get_tales('default'))
self.assertIsNone(field.get_value('default'))
with self.assertRaisesRegexp(
AttributeError,
'The proxy field <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title> cannot find a template field'
% self.portal.getId()):
field.get_recursive_tales('default')
regexp = '^%s$' % re.escape("Can't find the template field of"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title>"
% self.portal.getId())
for func in ( field.render
, partial(field.get_value, 'default')
, partial(field.get_recursive_tales, 'default')
):
self.assertRaisesRegexp(BrokenProxyField, regexp, func)
......@@ -23,7 +23,6 @@ test.erp5.testERP5Core
test.erp5.testERP5Type
test.erp5.testERP5TypeInterfaces
test.erp5.testERP5Workflow
test.erp5.testFieldValueCache
test.erp5.testFields
test.erp5.testFolder
test.erp5.testFolderMigration
......
......@@ -55,7 +55,7 @@ for business_field in business_field_list:
if field.meta_type=='ProxyField':
if field.is_delegated('title') is True:
field_note_list.append('Delegated.')
elif field.get_tales_expression('title') is not None:
elif field.get_recursive_tales('title') is not None:
field_note_list.append('Tales is used.')
if len(term_list) == 1 and \
......
......@@ -14,8 +14,7 @@ for skin_name, skin_path_list in skins_tool.getSkinPaths():
):
for field in form.get_fields():
if field.meta_type == 'ProxyField':
if field.getRecursiveTemplateField(
) is None or field.get_recursive_tales('external_validator'):
if field.get_recursive_tales('external_validator'):
continue
try:
external_validator = field.get_recursive_orig_value(
......
......@@ -179,15 +179,6 @@ class TALESValue(StaticValue):
def __call__(self, field, id, **kw):
REQUEST = kw.get('REQUEST', get_request())
if REQUEST is not None:
# Proxyfield stores the "real" field in the request. Look if the
# corresponding field exists in request, and use it as field in the
# TALES context
field = REQUEST.get(
'field__proxyfield_%s_%s_%s' % (field.id, field._p_oid, id),
field)
kw['field'] = field
form = field.aq_parent # XXX (JPS) form for default is wrong apparently in listbox - double check
obj = getattr(form, 'aq_parent', None)
......@@ -196,6 +187,7 @@ class TALESValue(StaticValue):
else:
container = None
kw['field'] = field
kw['form'] = form
kw['request'] = REQUEST
kw['here'] = obj
......@@ -339,15 +331,13 @@ def getFieldValue(self, field, id, **kw):
value = copyMethod(value)
cacheable = isCacheable(value)
field_id = field.id
if id == 'default' and (field_id.startswith('my_') or
field_id.startswith('listbox_')):
if field.meta_type == 'ProxyField' and \
field.getRecursiveTemplateField().meta_type == 'CheckBoxField' or \
self.meta_type == 'CheckBoxField':
return DefaultCheckBoxValue(field_id, value), cacheable
return DefaultValue(field_id, value), cacheable
if id == 'default':
field_id = field.id
if field_id.startswith(('my_', 'listbox_')):
if (self.getRecursiveTemplateField() if self.meta_type == 'ProxyField'
else self).meta_type == 'CheckBoxField':
return DefaultCheckBoxValue(field_id, value), cacheable
return DefaultValue(field_id, value), cacheable
# For the 'editable' value, we try to get a default value
if id == 'editable':
......@@ -358,24 +348,15 @@ def getFieldValue(self, field, id, **kw):
return StaticValue(value), cacheable
# Return default value in non callable mode
return_value = StaticValue(value)(field, id, **kw)
return_value = StaticValue(value)(None, id, **kw)
return return_value, isCacheable(return_value)
def get_value(self, id, REQUEST=None, **kw):
if REQUEST is None:
REQUEST = get_request()
if REQUEST is not None:
field = REQUEST.get(
'field__proxyfield_%s_%s_%s' % (self.id, self._p_oid, id),
self)
else:
field = self
def get_value(self, id, **kw):
field = kw.pop('field', self)
cache_id = ('Form.get_value',
self._p_oid,
field._p_oid,
field._p_oid if id == 'default' else
id)
try:
value = field_value_cache[cache_id]
except KeyError:
......@@ -386,11 +367,11 @@ def get_value(self, id, REQUEST=None, **kw):
# because such field must be used for editing field in ZMI
# 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:
if cacheable and None not in cache_id:
field_value_cache[cache_id] = value
if callable(value):
return value(field, id, REQUEST=REQUEST, **kw)
return value(field, id, **kw)
return value
psyco.bind(get_value)
......
......@@ -2888,23 +2888,17 @@ class ListBox(ZMIField):
security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw):
if (id == 'default'):
if (kw.get('render_format') in ('list', )):
request = kw.get('REQUEST', None)
if id == 'default':
render_format = kw.get('render_format')
if render_format == 'list':
request = kw.get('REQUEST')
if request is None:
request = get_request()
# here the field can be a a proxyfield target, in this case just find
# back the original proxy field so that renderer's calls to .get_value
# are called on the proxyfield.
field = request.get(
'field__proxyfield_%s_%s_%s' % (self.id, self._p_oid, id),
self)
field = kw.get('field', self) # for proxy field
return self.widget.render(field, self.generate_field_key(), None,
request,
render_format=kw.get('render_format'),
render_format=render_format,
render_prefix=kw.get('render_prefix'))
else:
return None
else:
return ZMIField.get_value(self, id, **kw)
......@@ -3213,7 +3207,3 @@ allow_class(ListBoxLine)
from Products.ERP5Type.PsycoWrapper import psyco
#psyco.bind(ListBoxWidget.render)
psyco.bind(ListBoxValidator.validate)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(ListBox, 'default')
......@@ -629,10 +629,17 @@ class MatrixBox(ZMIField):
security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw):
if id=='default' and kw.get('render_format') in ('list', ):
return self.widget.render(self, self.generate_field_key(), None,
kw.get('REQUEST'),
render_format=kw.get('render_format'))
if id == 'default':
render_format = kw.get('render_format')
if render_format == 'list':
request = kw.get('REQUEST')
if request is None:
request = get_request()
field = kw.get('field', self) # for proxy field
return self.widget.render(field, self.generate_field_key(), None,
request,
render_format=render_format,
render_prefix=kw.get('render_prefix'))
else:
return ZMIField.get_value(self, id, **kw)
......@@ -640,7 +647,3 @@ class MatrixBox(ZMIField):
from Products.ERP5Type.PsycoWrapper import psyco
psyco.bind(MatrixBoxWidget.render)
psyco.bind(MatrixBoxValidator.validate)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(MatrixBox, 'default')
......@@ -769,7 +769,3 @@ class MultiRelationStringField(ZMIField):
else:
result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
return result
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(MultiRelationStringField, 'items')
......@@ -225,7 +225,11 @@ class ParallelListField(ZMIField):
Optionally pass keyword arguments that get passed to TALES
expression.
"""
return paralellListFieldGetValue(self, id, REQUEST=REQUEST, **kw)
if REQUEST is not None:
result = REQUEST.get(KEYWORD % id, MARKER)
if result is not MARKER:
return result
return ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
def generateSubForm(self, value, REQUEST):
item_list = [x for x in self.get_value('items', REQUEST=REQUEST)
......@@ -259,21 +263,3 @@ def generateSubForm(self, value, REQUEST):
empty_sub_field_property_dict['item_list'] = item_list
empty_sub_field_property_dict['value'] = value_list
return [empty_sub_field_property_dict]
def paralellListFieldGetValue(field, id, REQUEST=None, **kw):
result = MARKER
key = KEYWORD % id
if REQUEST is not None and REQUEST.has_key(key):
result = REQUEST.get(key)
if result is MARKER:
result = ZMIField.get_value(field, id, REQUEST=REQUEST, **kw)
return result
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(
ParallelListField,
('title', 'required', 'size', 'default', 'first_item', 'items'),
paralellListFieldGetValue)
......@@ -3046,9 +3046,14 @@ class PlanningBox(ZMIField):
XXX What is the purpose ?
"""
if id == 'default' and render_format == 'list':
return self.widget.render(self, self.generate_field_key(), None,
kw.get('REQUEST'),
render_format=render_format)
request = kw.get('REQUEST')
if request is None:
request = get_request()
field = kw.get('field', self) # for proxy field
return self.widget.render(field, self.generate_field_key(), None,
request,
render_format=render_format,
render_prefix=kw.get('render_prefix'))
else:
return ZMIField.get_value(self, id, **kw)
......@@ -3075,7 +3080,3 @@ for klass in (PlanningBoxWidget, BasicStructure, BasicGroup,
Info):
InitializeClass(klass)
allow_class(klass)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(PlanningBox, 'default')
This diff is collapsed.
......@@ -174,7 +174,3 @@ class RelationStringField(ZMIField):
else:
result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
return result
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(RelationStringField, 'items')
......@@ -1366,6 +1366,7 @@ class TestKeyAuthentication(RoleManagementTestCase):
"""This test also uses web and dms
"""
return super(TestKeyAuthentication, self).getBusinessTemplateList() + (
'erp5_core_proxy_field_legacy', # for erp5_web
'erp5_base', 'erp5_web', 'erp5_ingestion', 'erp5_dms', 'erp5_administration')
......
......@@ -403,7 +403,7 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return 'erp5_base',
return 'erp5_core_proxy_field_legacy', 'erp5_base'
def _newStandardProperty(self, operation_type):
"""
......
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