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 ...@@ -48,7 +48,7 @@ from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Core.Folder import Folder from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import field_value_cache from Products.ERP5Form.Form import field_value_cache
from Products.ERP5Form.Form import getFieldValue from Products.ERP5Form.Form import getFieldValue
from Products.ERP5Form import ProxyField from Products.ERP5Form.ProxyField import BrokenProxyField, ProxyField
from DateTime import DateTime from DateTime import DateTime
import lxml.html import lxml.html
...@@ -67,7 +67,7 @@ class TestRenderViewAPI(ERP5TypeTestCase): ...@@ -67,7 +67,7 @@ class TestRenderViewAPI(ERP5TypeTestCase):
for field in FieldRegistry.get_field_classes().itervalues(): # pylint: disable=no-value-for-parameter for field in FieldRegistry.get_field_classes().itervalues(): # pylint: disable=no-value-for-parameter
self.assertEqual(('self', 'value', 'REQUEST', 'render_prefix'), self.assertEqual(('self', 'value', 'REQUEST', 'render_prefix'),
field.render_view.im_func.func_code.co_varnames) 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'), 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])) 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): ...@@ -712,6 +712,9 @@ class TestMultiListField(ERP5TypeTestCase):
class TestProxyField(ERP5TypeTestCase): class TestProxyField(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return 'erp5_core_proxy_field_legacy',
def getTitle(self): def getTitle(self):
return "Proxy Field" return "Proxy Field"
...@@ -731,9 +734,9 @@ class TestProxyField(ERP5TypeTestCase): ...@@ -731,9 +734,9 @@ class TestProxyField(ERP5TypeTestCase):
'my_title', 'Title', 'StringField') 'my_title', 'Title', 'StringField')
proxy_field = self.addField(self.container.Base_view, proxy_field = self.addField(self.container.Base_view,
'my_title', 'Not Title', 'ProxyField') 'my_title', 'Not Title', 'ProxyField')
self.assertEqual(None, proxy_field.getTemplateField()) self.assertIsNone(proxy_field.getTemplateField())
self.assertEqual(None, proxy_field.get_value('enable')) self.assertFalse(proxy_field.get_value('enabled'))
self.assertEqual(None, proxy_field.get_value('default')) self.assertRaises(BrokenProxyField, proxy_field.get_value, 'default')
proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary', proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary',
field_id='my_title',)) field_id='my_title',))
...@@ -871,7 +874,7 @@ class TestProxyField(ERP5TypeTestCase): ...@@ -871,7 +874,7 @@ class TestProxyField(ERP5TypeTestCase):
proxy_field = self.addField(self.container.Base_view, proxy_field = self.addField(self.container.Base_view,
'my_String', '', 'ProxyField') 'my_String', '', 'ProxyField')
proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary', 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')) proxy_field.manage_edit_surcharged_xmlrpc(dict(title='Title'))
self.assertFalse(proxy_field.is_delegated('title')) self.assertFalse(proxy_field.is_delegated('title'))
...@@ -1106,12 +1109,12 @@ class TestFieldValueCache(ERP5TypeTestCase): ...@@ -1106,12 +1109,12 @@ class TestFieldValueCache(ERP5TypeTestCase):
'default': 'python: repr(here)'}) 'default': 'python: repr(here)'})
form.my_on_memory_field._p_oid = None form.my_on_memory_field._p_oid = None
# proxy field # proxy field
addField(ProxyField.ProxyField('proxy_field')) addField(ProxyField('proxy_field'))
form.proxy_field._p_oid = makeDummyOid() form.proxy_field._p_oid = makeDummyOid()
form.proxy_field.values['form_id'] = 'form' form.proxy_field.values['form_id'] = 'form'
form.proxy_field.values['field_id'] = 'field' form.proxy_field.values['field_id'] = 'field'
# proxy field with tales # 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._p_oid = makeDummyOid()
form.proxy_field_tales.tales['form_id'] = TALESMethod('string:form') form.proxy_field_tales.tales['form_id'] = TALESMethod('string:form')
form.proxy_field_tales.tales['field_id'] = TALESMethod('string:field') form.proxy_field_tales.tales['field_id'] = TALESMethod('string:field')
...@@ -1141,7 +1144,7 @@ class TestFieldValueCache(ERP5TypeTestCase): ...@@ -1141,7 +1144,7 @@ class TestFieldValueCache(ERP5TypeTestCase):
# make sure that this will use cache. # make sure that this will use cache.
cache_size = self._getCacheSize('Form.get_value') cache_size = self._getCacheSize('Form.get_value')
self.root.form.field.get_value('title') 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 # check on-memory field
# make sure that this will not use cache. # make sure that this will not use cache.
...@@ -1150,19 +1153,7 @@ class TestFieldValueCache(ERP5TypeTestCase): ...@@ -1150,19 +1153,7 @@ class TestFieldValueCache(ERP5TypeTestCase):
self.root.form.my_on_memory_tales_field.get_value('default')) self.root.form.my_on_memory_tales_field.get_value('default'))
self.assertEqual('123', self.assertEqual('123',
self.root.form.my_on_memory_field.get_value('default')) self.root.form.my_on_memory_field.get_value('default'))
self.assertEqual(True, cache_size == self._getCacheSize('Form.get_value')) self.assertEqual(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'))
class TestCaptchaField(ERP5TypeTestCase): class TestCaptchaField(ERP5TypeTestCase):
......
...@@ -26,12 +26,16 @@ ...@@ -26,12 +26,16 @@
# #
############################################################################## ##############################################################################
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase import re
from Products.Formulator.XMLToForm import XMLToForm from functools import partial
from unittest import expectedFailure
from lxml import etree
from Products.Formulator.FormToXML import formToXML from Products.Formulator.FormToXML import formToXML
from Products.Formulator.TALESField import TALESMethod from Products.Formulator.TALESField import TALESMethod
from lxml import etree from Products.Formulator.XMLToForm import XMLToForm
from unittest import expectedFailure from Products.ERP5Form.ProxyField import BrokenProxyField
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class TestProxyField(ERP5TypeTestCase): class TestProxyField(ERP5TypeTestCase):
""" """
...@@ -396,15 +400,19 @@ return printed ...@@ -396,15 +400,19 @@ return printed
form = skin_folder._getOb('Base_viewGeek', None) form = skin_folder._getOb('Base_viewGeek', None)
form.manage_addField('my_title', 'Title', 'ProxyField') 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.assertIsNone(field.getTemplateField())
self.assertEqual('', field.render())
self.assertEqual('', field.get_tales('default')) self.assertEqual('', field.get_tales('default'))
self.assertIsNone(field.get_value('default'))
with self.assertRaisesRegexp( regexp = '^%s$' % re.escape("Can't find the template field of"
AttributeError, " <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title>"
'The proxy field <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title> cannot find a template field' % self.portal.getId())
% self.portal.getId()): for func in ( field.render
field.get_recursive_tales('default') , partial(field.get_value, 'default')
, partial(field.get_recursive_tales, 'default')
):
self.assertRaisesRegexp(BrokenProxyField, regexp, func)
...@@ -23,7 +23,6 @@ test.erp5.testERP5Core ...@@ -23,7 +23,6 @@ test.erp5.testERP5Core
test.erp5.testERP5Type test.erp5.testERP5Type
test.erp5.testERP5TypeInterfaces test.erp5.testERP5TypeInterfaces
test.erp5.testERP5Workflow test.erp5.testERP5Workflow
test.erp5.testFieldValueCache
test.erp5.testFields test.erp5.testFields
test.erp5.testFolder test.erp5.testFolder
test.erp5.testFolderMigration test.erp5.testFolderMigration
......
...@@ -55,7 +55,7 @@ for business_field in business_field_list: ...@@ -55,7 +55,7 @@ for business_field in business_field_list:
if field.meta_type=='ProxyField': if field.meta_type=='ProxyField':
if field.is_delegated('title') is True: if field.is_delegated('title') is True:
field_note_list.append('Delegated.') 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.') field_note_list.append('Tales is used.')
if len(term_list) == 1 and \ if len(term_list) == 1 and \
......
...@@ -14,8 +14,7 @@ for skin_name, skin_path_list in skins_tool.getSkinPaths(): ...@@ -14,8 +14,7 @@ for skin_name, skin_path_list in skins_tool.getSkinPaths():
): ):
for field in form.get_fields(): for field in form.get_fields():
if field.meta_type == 'ProxyField': if field.meta_type == 'ProxyField':
if field.getRecursiveTemplateField( if field.get_recursive_tales('external_validator'):
) is None or field.get_recursive_tales('external_validator'):
continue continue
try: try:
external_validator = field.get_recursive_orig_value( external_validator = field.get_recursive_orig_value(
......
...@@ -179,15 +179,6 @@ class TALESValue(StaticValue): ...@@ -179,15 +179,6 @@ class TALESValue(StaticValue):
def __call__(self, field, id, **kw): def __call__(self, field, id, **kw):
REQUEST = kw.get('REQUEST', get_request()) 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 form = field.aq_parent # XXX (JPS) form for default is wrong apparently in listbox - double check
obj = getattr(form, 'aq_parent', None) obj = getattr(form, 'aq_parent', None)
...@@ -196,6 +187,7 @@ class TALESValue(StaticValue): ...@@ -196,6 +187,7 @@ class TALESValue(StaticValue):
else: else:
container = None container = None
kw['field'] = field
kw['form'] = form kw['form'] = form
kw['request'] = REQUEST kw['request'] = REQUEST
kw['here'] = obj kw['here'] = obj
...@@ -339,15 +331,13 @@ def getFieldValue(self, field, id, **kw): ...@@ -339,15 +331,13 @@ def getFieldValue(self, field, id, **kw):
value = copyMethod(value) value = copyMethod(value)
cacheable = isCacheable(value) cacheable = isCacheable(value)
field_id = field.id if id == 'default':
field_id = field.id
if id == 'default' and (field_id.startswith('my_') or if field_id.startswith(('my_', 'listbox_')):
field_id.startswith('listbox_')): if (self.getRecursiveTemplateField() if self.meta_type == 'ProxyField'
if field.meta_type == 'ProxyField' and \ else self).meta_type == 'CheckBoxField':
field.getRecursiveTemplateField().meta_type == 'CheckBoxField' or \ return DefaultCheckBoxValue(field_id, value), cacheable
self.meta_type == 'CheckBoxField': return DefaultValue(field_id, value), cacheable
return DefaultCheckBoxValue(field_id, value), cacheable
return DefaultValue(field_id, value), cacheable
# For the 'editable' value, we try to get a default value # For the 'editable' value, we try to get a default value
if id == 'editable': if id == 'editable':
...@@ -358,24 +348,15 @@ def getFieldValue(self, field, id, **kw): ...@@ -358,24 +348,15 @@ def getFieldValue(self, field, id, **kw):
return StaticValue(value), cacheable return StaticValue(value), cacheable
# Return default value in non callable mode # 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) return return_value, isCacheable(return_value)
def get_value(self, id, REQUEST=None, **kw): def get_value(self, id, **kw):
if REQUEST is None: field = kw.pop('field', self)
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
cache_id = ('Form.get_value', cache_id = ('Form.get_value',
self._p_oid, self._p_oid,
field._p_oid, field._p_oid if id == 'default' else
id) id)
try: try:
value = field_value_cache[cache_id] value = field_value_cache[cache_id]
except KeyError: except KeyError:
...@@ -386,11 +367,11 @@ def get_value(self, id, REQUEST=None, **kw): ...@@ -386,11 +367,11 @@ def get_value(self, id, REQUEST=None, **kw):
# because such field must be used for editing field in ZMI # because such field must be used for editing field in ZMI
# and caching sometimes break these field settings at initialization. # and caching sometimes break these field settings at initialization.
# As the result, we would see broken field editing screen in ZMI. # 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 field_value_cache[cache_id] = value
if callable(value): if callable(value):
return value(field, id, REQUEST=REQUEST, **kw) return value(field, id, **kw)
return value return value
psyco.bind(get_value) psyco.bind(get_value)
......
...@@ -2888,23 +2888,17 @@ class ListBox(ZMIField): ...@@ -2888,23 +2888,17 @@ class ListBox(ZMIField):
security.declareProtected('Access contents information', 'get_value') security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw): def get_value(self, id, **kw):
if (id == 'default'): if id == 'default':
if (kw.get('render_format') in ('list', )): render_format = kw.get('render_format')
request = kw.get('REQUEST', None) if render_format == 'list':
request = kw.get('REQUEST')
if request is None: if request is None:
request = get_request() request = get_request()
# here the field can be a a proxyfield target, in this case just find field = kw.get('field', self) # for proxy field
# 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)
return self.widget.render(field, self.generate_field_key(), None, return self.widget.render(field, self.generate_field_key(), None,
request, request,
render_format=kw.get('render_format'), render_format=render_format,
render_prefix=kw.get('render_prefix')) render_prefix=kw.get('render_prefix'))
else:
return None
else: else:
return ZMIField.get_value(self, id, **kw) return ZMIField.get_value(self, id, **kw)
...@@ -3213,7 +3207,3 @@ allow_class(ListBoxLine) ...@@ -3213,7 +3207,3 @@ allow_class(ListBoxLine)
from Products.ERP5Type.PsycoWrapper import psyco from Products.ERP5Type.PsycoWrapper import psyco
#psyco.bind(ListBoxWidget.render) #psyco.bind(ListBoxWidget.render)
psyco.bind(ListBoxValidator.validate) psyco.bind(ListBoxValidator.validate)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(ListBox, 'default')
...@@ -629,10 +629,17 @@ class MatrixBox(ZMIField): ...@@ -629,10 +629,17 @@ class MatrixBox(ZMIField):
security.declareProtected('Access contents information', 'get_value') security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw): def get_value(self, id, **kw):
if id=='default' and kw.get('render_format') in ('list', ): if id == 'default':
return self.widget.render(self, self.generate_field_key(), None, render_format = kw.get('render_format')
kw.get('REQUEST'), if render_format == 'list':
render_format=kw.get('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: else:
return ZMIField.get_value(self, id, **kw) return ZMIField.get_value(self, id, **kw)
...@@ -640,7 +647,3 @@ class MatrixBox(ZMIField): ...@@ -640,7 +647,3 @@ class MatrixBox(ZMIField):
from Products.ERP5Type.PsycoWrapper import psyco from Products.ERP5Type.PsycoWrapper import psyco
psyco.bind(MatrixBoxWidget.render) psyco.bind(MatrixBoxWidget.render)
psyco.bind(MatrixBoxValidator.validate) psyco.bind(MatrixBoxValidator.validate)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(MatrixBox, 'default')
...@@ -769,7 +769,3 @@ class MultiRelationStringField(ZMIField): ...@@ -769,7 +769,3 @@ class MultiRelationStringField(ZMIField):
else: else:
result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw) result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
return result return result
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(MultiRelationStringField, 'items')
...@@ -225,7 +225,11 @@ class ParallelListField(ZMIField): ...@@ -225,7 +225,11 @@ class ParallelListField(ZMIField):
Optionally pass keyword arguments that get passed to TALES Optionally pass keyword arguments that get passed to TALES
expression. 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): def generateSubForm(self, value, REQUEST):
item_list = [x for x in self.get_value('items', REQUEST=REQUEST) item_list = [x for x in self.get_value('items', REQUEST=REQUEST)
...@@ -259,21 +263,3 @@ def generateSubForm(self, value, REQUEST): ...@@ -259,21 +263,3 @@ def generateSubForm(self, value, REQUEST):
empty_sub_field_property_dict['item_list'] = item_list empty_sub_field_property_dict['item_list'] = item_list
empty_sub_field_property_dict['value'] = value_list empty_sub_field_property_dict['value'] = value_list
return [empty_sub_field_property_dict] 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): ...@@ -3046,9 +3046,14 @@ class PlanningBox(ZMIField):
XXX What is the purpose ? XXX What is the purpose ?
""" """
if id == 'default' and render_format == 'list': if id == 'default' and render_format == 'list':
return self.widget.render(self, self.generate_field_key(), None, request = kw.get('REQUEST')
kw.get('REQUEST'), if request is None:
render_format=render_format) 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: else:
return ZMIField.get_value(self, id, **kw) return ZMIField.get_value(self, id, **kw)
...@@ -3075,7 +3080,3 @@ for klass in (PlanningBoxWidget, BasicStructure, BasicGroup, ...@@ -3075,7 +3080,3 @@ for klass in (PlanningBoxWidget, BasicStructure, BasicGroup,
Info): Info):
InitializeClass(klass) InitializeClass(klass)
allow_class(klass) allow_class(klass)
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(PlanningBox, 'default')
...@@ -28,38 +28,28 @@ ...@@ -28,38 +28,28 @@
# #
############################################################################## ##############################################################################
from thread import get_ident
from AccessControl import allow_class, ClassSecurityInfo
from Acquisition import aq_base
from MethodObject import Method
from zLOG import LOG, WARNING
from Products.CMFCore.Skinnable import SKINDATA
from Products.Formulator import Widget, Validator from Products.Formulator import Widget, Validator
from Products.Formulator.Field import ZMIField from Products.Formulator.Field import ZMIField
from Products.Formulator.DummyField import fields from Products.Formulator.DummyField import fields
from Products.Formulator.Errors import ValidationError from Products.Formulator.Errors import ValidationError
from Products.Formulator import MethodField from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.ObjectMessage import ObjectMessage from Products.ERP5Type.ObjectMessage import ObjectMessage
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.ERP5Type.Globals import get_request
from Products.PythonScripts.Utility import allow_class
from Products.PythonScripts.standard import url_quote_plus
from AccessControl import ClassSecurityInfo
from MethodObject import Method
from zLOG import LOG, WARNING, DEBUG, PROBLEM
from Acquisition import aq_base, aq_inner, aq_acquire, aq_chain
from Products.ERP5Type.Globals import DTMLFile from Products.ERP5Type.Globals import DTMLFile
from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Form.Form import StaticValue, TALESValue, OverrideValue, \
DefaultValue, EditableValue, DefaultCheckBoxValue
from Products.ERP5Form.Form import copyMethod, isCacheable
from Products.CMFCore.Skinnable import SKINDATA
from thread import get_ident
_USE_ORIGINAL_GET_VALUE_MARKER = [] class BrokenProxyField(Exception):
pass
allow_class(BrokenProxyField)
class WidgetDelegatedMethod(Method): class WidgetDelegatedMethod(Method):
"""Method delegated to the proxied field's widget. """Method delegated to the proxied field's widget.
...@@ -411,7 +401,7 @@ class ProxyField(ZMIField): ...@@ -411,7 +401,7 @@ class ProxyField(ZMIField):
""" """
Return template field of the proxy field. Return template field of the proxy field.
""" """
if cache is True: if cache:
tales = self.tales tales = self.tales
if self._p_oid is None or tales['field_id'] or tales['form_id']: if self._p_oid is None or tales['field_id'] or tales['form_id']:
cache = False cache = False
...@@ -478,30 +468,35 @@ class ProxyField(ZMIField): ...@@ -478,30 +468,35 @@ class ProxyField(ZMIField):
LOG('ProxyField', WARNING, LOG('ProxyField', WARNING,
'Could not get a field from a proxy field %s in %s' % \ 'Could not get a field from a proxy field %s in %s' % \
(self.id, object.id)) (self.id, object.id))
if cache is True: if cache:
self._setTemplateFieldCache(proxy_field) self._setTemplateFieldCache(proxy_field)
return proxy_field return proxy_field
security.declareProtected('Access contents information', 'getRecursiveTemplateField') security.declareProtected('Access contents information', 'getRecursiveTemplateField')
def getRecursiveTemplateField(self): def getRecursiveTemplateField(self, delegated_id=None):
""" """
Return template field of the proxy field. Return template field of the proxy field.
This result must not be a ProxyField. If delegated_id is None, the result is not a ProxyField,
""" else it is the field that defines the value (possibly an
field = self intermediate proxy field).
chain = [] """
while True: seen = [aq_base(self)]
template_field = field.getTemplateField() while delegated_id is None or self.is_delegated(delegated_id):
if template_field.__class__ != ProxyField: field = self.getTemplateField()
if not isinstance(field, ProxyField):
if field is None:
error = "Can't find the template field of %r"
break
return field
self = field
field = aq_base(field)
if field in seen:
error = "Infinite loop when searching template field of %r"
break break
template_field_base = aq_base(template_field) seen.append(field)
if template_field_base in chain: else:
LOG('ProxyField', WARNING, 'Infinite loop detected in %s.' % return self
'/'.join(self.getPhysicalPath())) raise BrokenProxyField(error % self)
return
chain.append(template_field_base)
field = template_field
return template_field
def _get_sub_form(self, field=None): def _get_sub_form(self, field=None):
if field is None: if field is None:
...@@ -545,22 +540,15 @@ class ProxyField(ZMIField): ...@@ -545,22 +540,15 @@ class ProxyField(ZMIField):
return proxied_field.get_orig_value(id) return proxied_field.get_orig_value(id)
security.declareProtected('View management screens', 'get_recursive_tales') security.declareProtected('View management screens', 'get_recursive_tales')
def get_recursive_tales(self, id, include=1): def get_recursive_tales(self, id):
""" """
Get tales expression method for id. Get tales expression method for id.
""" """
if include and \ if id not in self.widget.property_names:
((id in self.widget.property_names) or \ self = self.getRecursiveTemplateField(id)
not self.is_delegated(id)): tales = self.get_tales(id)
return self.get_tales(id) if tales:
else: return TALESMethod(tales._text)
proxied_field = self.getTemplateField()
if proxied_field is None:
raise AttributeError('The proxy field %r cannot find a template field' % self)
if proxied_field.__class__ == ProxyField:
return proxied_field.get_recursive_tales(id)
else:
return proxied_field.get_tales(id)
# XXX Not implemented # XXX Not implemented
security.declareProtected('View management screens', 'get_recursive_override') security.declareProtected('View management screens', 'get_recursive_override')
...@@ -631,154 +619,35 @@ class ProxyField(ZMIField): ...@@ -631,154 +619,35 @@ class ProxyField(ZMIField):
return self.getTemplateField().get_orig_value(id) return self.getTemplateField().get_orig_value(id)
return ZMIField.get_orig_value(self, id) return ZMIField.get_orig_value(self, id)
#
# Performance improvement
#
def get_tales_expression(self, id):
field = self
while True:
if (id in field.widget.property_names or
not field.is_delegated(id)):
tales = field.get_tales(id)
if tales:
return TALESMethod(tales._text)
else:
return None
proxied_field = field.getTemplateField()
if proxied_field.__class__ == ProxyField:
field = proxied_field
elif proxied_field is None:
raise ValueError("Can't find the template field of %s" % self.id)
else:
tales = proxied_field.get_tales(id)
if tales:
return TALESMethod(tales._text)
else:
return None
security.declareProtected('Access contents information', 'getFieldValue')
def getFieldValue(self, field, id, **kw):
"""
Return a callable expression and cacheable boolean flag
"""
# Some field types have their own get_value implementation,
# then we must use it always. This check must be done at first.
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, True
try:
tales_expr = self.get_tales_expression(id)
except ValueError:
return None, False
if 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'):
self.overrides = {}
override = self.overrides.get(id, "")
if override:
override = copyMethod(override)
return OverrideValue(override), isCacheable(override)
# Get a normal value.
try:
value = self.get_recursive_orig_value(id)
except KeyError:
# For ListBox and other exceptional fields.
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_') or
field_id.startswith('listbox_')):
# XXX far from object-oriented programming
if template_field.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':
return EditableValue(value), cacheable
# Return default value in callable mode
if callable(value):
return StaticValue(value), cacheable
# Return default value in non callable mode
return_value = StaticValue(value)(field, id, **kw)
return return_value, isCacheable(return_value)
security.declareProtected('Access contents information', 'get_value') security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw): def get_value(self, id, **kw):
if id in self.widget.property_names: if id in self.widget.property_names:
return ZMIField.get_value(self, id, **kw) return ZMIField.get_value(self, id, **kw)
if not self.is_delegated(id):
original_template_field = self.getRecursiveTemplateField()
function = getOriginalGetValueFunction(original_template_field, id)
if function is not None:
return function(self, id, **kw)
else:
return ZMIField.get_value(self, id, **kw)
field = self
proxy_field = self.getTemplateField()
REQUEST = kw.get('REQUEST', get_request())
if proxy_field is not None and REQUEST is not None:
field = REQUEST.get(
'field__proxyfield_%s_%s_%s' % (self.id, self._p_oid, id),
self)
REQUEST.set(
'field__proxyfield_%s_%s_%s' % (proxy_field.id, proxy_field._p_oid, id),
field)
# Don't use cache if field is not stored in zodb, or if target field is
# defined by a TALES
if self._p_oid is None or self.tales['field_id'] or self.tales['form_id']:
return self._get_value(id, **kw)
# XXX: Are these disabled?
proxy_field = self.getTemplateField(cache=False)
if proxy_field is not None:
return proxy_field.get_value(id, **kw)
else:
return None
cache_id = ('ProxyField.get_value',
self._p_oid,
field._p_oid,
id)
from Products.ERP5Form.Form import field_value_cache
try: try:
value = field_value_cache[cache_id] field = self.getRecursiveTemplateField(id)
except KeyError: if isinstance(field, ProxyField):
# either returns non callable value (ex. "Title") cls = field.getRecursiveTemplateField().__class__
# or a FieldValue instance of appropriate class try:
value, cacheable = self.getFieldValue(field, id, **kw) _ProxyField = cls.__ProxyField
if cacheable: except AttributeError:
field_value_cache[cache_id] = value class _ProxyField(cls):
def __init__(self):
if value is _USE_ORIGINAL_GET_VALUE_MARKER: pass
return proxy_field.get_value(id, **kw) cls.__ProxyField = _ProxyField
tmp_field = _ProxyField()
if callable(value): for attr in 'values', 'tales', 'overrides':
return value(field, id, **kw) setattr(tmp_field, attr, getattr(field, attr).copy())
return value field = tmp_field
except BrokenProxyField:
def _get_value(self, id, **kw): # do not break Form.get_field
proxy_field = self.getTemplateField(cache=False) if id != 'enabled':
if proxy_field is not None: raise
return proxy_field.get_value(id, **kw) else:
return field.get_value(id, field=self, **kw)
def _getCacheId(self): def _getCacheId(self):
return '%s%s' % ('ProxyField', self._p_oid or repr(self)) assert self._p_oid
return 'ProxyField', self._p_oid
def _setTemplateFieldCache(self, field): def _setTemplateFieldCache(self, field):
getTransactionalVariable()[self._getCacheId()] = field getTransactionalVariable()[self._getCacheId()] = field
...@@ -815,46 +684,3 @@ class ProxyField(ZMIField): ...@@ -815,46 +684,3 @@ class ProxyField(ZMIField):
message="Internal proxy field data structures are inconsistent. " message="Internal proxy field data structures are inconsistent. "
"Differences: {}".format(difference))] "Differences: {}".format(difference))]
return [] return []
#
# get_value exception dict
#
_get_value_exception_dict = {}
def registerOriginalGetValueClassAndArgument(class_, argument_name_list=(), get_value_function=None):
"""
if field class has its own get_value implementation and
must use it rather than ProxyField's one, then register it.
if argument_name_list is '*' , original get_value is
applied for all arguments.
"""
if not isinstance(argument_name_list, (list, tuple)):
argument_name_list = (argument_name_list,)
if get_value_function is None:
get_value_function = ZMIField.get_value
_get_value_exception_dict[class_] = {'argument_name_list':argument_name_list,
'get_value_function':get_value_function}
def checkOriginalGetValue(instance, argument_name):
"""
if exception data is registered, then return True
"""
class_ = aq_base(instance).__class__
dict_ = _get_value_exception_dict.get(class_, {})
argument_name_list = dict_.get('argument_name_list')
if argument_name_list is None:
return False
if len(argument_name_list)==1 and argument_name_list[0]=='*':
return True
if argument_name in argument_name_list:
return True
return False
def getOriginalGetValueFunction(instance, argument_name):
class_ = aq_base(instance).__class__
dict_ = _get_value_exception_dict.get(class_, {})
return dict_.get('get_value_function')
...@@ -174,7 +174,3 @@ class RelationStringField(ZMIField): ...@@ -174,7 +174,3 @@ class RelationStringField(ZMIField):
else: else:
result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw) result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
return result return result
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(RelationStringField, 'items')
...@@ -1366,6 +1366,7 @@ class TestKeyAuthentication(RoleManagementTestCase): ...@@ -1366,6 +1366,7 @@ class TestKeyAuthentication(RoleManagementTestCase):
"""This test also uses web and dms """This test also uses web and dms
""" """
return super(TestKeyAuthentication, self).getBusinessTemplateList() + ( return super(TestKeyAuthentication, self).getBusinessTemplateList() + (
'erp5_core_proxy_field_legacy', # for erp5_web
'erp5_base', 'erp5_web', 'erp5_ingestion', 'erp5_dms', 'erp5_administration') 'erp5_base', 'erp5_web', 'erp5_ingestion', 'erp5_dms', 'erp5_administration')
......
...@@ -403,7 +403,7 @@ class TestZodbPropertySheet(ERP5TypeTestCase): ...@@ -403,7 +403,7 @@ class TestZodbPropertySheet(ERP5TypeTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return 'erp5_base', return 'erp5_core_proxy_field_legacy', 'erp5_base'
def _newStandardProperty(self, operation_type): 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