Commit 9a9705cf authored by Yusei Tahara's avatar Yusei Tahara

Improve field performance. This changes depends on ERP5Type.Interactor.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@16501 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 58b44185
...@@ -35,6 +35,7 @@ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate ...@@ -35,6 +35,7 @@ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
from Products.CMFCore.utils import _checkPermission, getToolByName from Products.CMFCore.utils import _checkPermission, getToolByName
from Products.CMFCore.exceptions import AccessControl_Unauthorized from Products.CMFCore.exceptions import AccessControl_Unauthorized
from Products.ERP5Type import PropertySheet, Permissions from Products.ERP5Type import PropertySheet, Permissions
from Products.ERP5Type.Cache import CachingMethod
from urllib import quote from urllib import quote
from Globals import InitializeClass, PersistentMapping, DTMLFile, get_request from Globals import InitializeClass, PersistentMapping, DTMLFile, get_request
...@@ -46,28 +47,55 @@ from Products.ERP5Type.Utils import UpperCase ...@@ -46,28 +47,55 @@ from Products.ERP5Type.Utils import UpperCase
from Products.ERP5Type.PsycoWrapper import psyco from Products.ERP5Type.PsycoWrapper import psyco
import sys import sys
_field_value_cache = {}
def purgeFieldValueCache():
_field_value_cache.clear()
# Patch the fiels methods to provide improved namespace handling # Patch the fiels methods to provide improved namespace handling
from Products.Formulator.Field import Field from Products.Formulator.Field import Field
from Products.Formulator.TALESField import TALESMethod
from zLOG import LOG, PROBLEM from zLOG import LOG, PROBLEM
def get_value(self, id, **kw): class StaticValue:
"""Get value for id.""" """
# FIXME: backwards compat hack to make sure tales dict exists Encapsulated a static value in a class
if not hasattr(self, 'tales'): (quite heavy, would be faster to store the
self.tales = {} value as is)
"""
def __init__(self, value):
self.value = value
tales_expr = self.tales.get(id, "") def __call__(self, field, id, **kw):
if tales_expr: return self.returnValue(field, id, self.value)
def returnValue(self, field, id, value):
# if normal value is a callable itself, wrap it
if callable(value):
value = value.__of__(field)
#value=value() # Mising call ??? XXX Make sure compatible with listbox methods
if id == 'default':
# We make sure we convert values to empty strings
# for most fields (so that we do not get a 'value'
# message on screen)
# This can be overriden by using TALES in the field
if value is None: value = ''
return value
class TALESValue(StaticValue):
def __init__(self, tales_expr):
self.tales_expr = tales_expr
def __call__(self, field, id, **kw):
REQUEST = get_request() REQUEST = get_request()
if REQUEST is not None: if REQUEST is not None:
# Proxyfield stores the "real" field in the request. Look if the # Proxyfield stores the "real" field in the request. Look if the
# corresponding field exists in request, and use it as field in the # corresponding field exists in request, and use it as field in the
# TALES context # TALES context
field = REQUEST.get('field__proxyfield_%s_%s' % (self.id, id), self) field = REQUEST.get('field__proxyfield_%s_%s' % (field.id, id), field)
else:
field = self
kw['field'] = field kw['field'] = field
...@@ -101,7 +129,7 @@ def get_value(self, id, **kw): ...@@ -101,7 +129,7 @@ def get_value(self, id, **kw):
if getattr(REQUEST, 'cell', None) is not None: if getattr(REQUEST, 'cell', None) is not None:
kw['cell'] = getattr(REQUEST, 'cell') kw['cell'] = getattr(REQUEST, 'cell')
try: try:
value = tales_expr.__of__(self)(**kw) value = self.tales_expr.__of__(field)(**kw)
except (ConflictError, RuntimeError): except (ConflictError, RuntimeError):
raise raise
except: except:
...@@ -109,46 +137,42 @@ def get_value(self, id, **kw): ...@@ -109,46 +137,42 @@ def get_value(self, id, **kw):
# something reasonable rather than generate plenty of errors # something reasonable rather than generate plenty of errors
LOG('ERP5Form', PROBLEM, LOG('ERP5Form', PROBLEM,
'Field.get_value ( %s/%s [%s]), exception on tales_expr: ' % 'Field.get_value ( %s/%s [%s]), exception on tales_expr: ' %
( form.getId(), self.getId(), id), error=sys.exc_info()) ( form.getId(), field.getId(), id), error=sys.exc_info())
value = self.get_orig_value(id) value = field.get_orig_value(id)
else:
# FIXME: backwards compat hack to make sure overrides dict exists
if not hasattr(self, 'overrides'):
self.overrides = {}
override = self.overrides.get(id, "") return self.returnValue(field, id, value)
if override:
# call wrapped method to get answer
value = override.__of__(self)()
else:
# Get a normal value.
value = self.get_orig_value(id)
# For the 'default' value, we try to get a property value class OverrideValue(StaticValue):
# stored in the context, only if the field is prefixed with my_. def __init__(self, override):
REQUEST = get_request() self.override = override
if REQUEST is not None:
field_id = REQUEST.get('field__proxyfield_%s_%s' % (self.id, id),
self).id
else:
field_id = self.id
if id == 'default' and field_id.startswith('my_'): def __call__(self, field, id, **kw):
return self.returnValue(field, id, self.override.__of__(field)())
class DefaultValue(StaticValue):
def __init__(self, field_id, value):
self.key = field_id[3:]
self.value = value
def __call__(self, field, id, **kw):
try: try:
form = self.aq_parent form = field.aq_parent
ob = getattr(form, 'aq_parent', None) ob = getattr(form, 'aq_parent', None)
key = field_id[3:] value = self.value
if value not in (None, ''): if value not in (None, ''):
# If a default value is defined on the field, it has precedence # If a default value is defined on the field, it has precedence
value = ob.getProperty(key, d=value) value = ob.getProperty(self.key, d=value)
else: else:
# else we should give a chance to the accessor to provide # else we should give a chance to the accessor to provide
# a default value (including None) # a default value (including None)
value = ob.getProperty(key) value = ob.getProperty(self.key)
except (KeyError, AttributeError): except (KeyError, AttributeError):
value = None value = None
# For the 'editable' value, we try to get a default value return self.returnValue(field, id, value)
elif id == 'editable':
class EditableValue(StaticValue):
def __call__(self, field, id, **kw):
# By default, pages are editable and # By default, pages are editable and
# fields are editable if they are set to editable mode # fields are editable if they are set to editable mode
# However, if the REQUEST defines editable_mode to 0 # However, if the REQUEST defines editable_mode to 0
...@@ -158,20 +182,73 @@ def get_value(self, id, **kw): ...@@ -158,20 +182,73 @@ def get_value(self, id, **kw):
# which defines the current layout # which defines the current layout
if kw.has_key('REQUEST'): if kw.has_key('REQUEST'):
if not getattr(kw['REQUEST'], 'editable_mode', 1): if not getattr(kw['REQUEST'], 'editable_mode', 1):
value = 0 self.value = 0
return self.value
# if normal value is a callable itself, wrap it def getFieldValue(self, field, id, **kw):
"""
Return a callable expression
"""
# FIXME: backwards compat hack to make sure tales dict exists
if not hasattr(self, 'tales'):
self.tales = {}
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)
# FIXME: backwards compat hack to make sure overrides dict exists
if not hasattr(self, 'overrides'):
self.overrides = {}
override = self.overrides.get(id, "")
if override:
return OverrideValue(override)
# Get a normal value.
value = self.get_orig_value(id)
field_id = field.id
if id == 'default' and field_id.startswith('my_'):
return DefaultValue(field_id, value)
# For the 'editable' value, we try to get a default value
if id == 'editable':
return EditableValue(value)
# Return default value in non callable mode
if callable(value): if callable(value):
value = value.__of__(self) return StaticValue(value)
#value=value() # Mising call ??? XXX Make sure compatible with listbox methods
if id == 'default': # Return default value in non callable mode
# We make sure we convert values to empty strings return StaticValue(value)(field, id, **kw)
# for most fields (so that we do not get a 'value'
# message on screen) def get_value(self, id, **kw):
# This can be overriden by using TALES in the field REQUEST = get_request()
if value is None: value = '' if REQUEST is not None:
field = REQUEST.get('field__proxyfield_%s_%s' % (self.id, id), self)
else:
field = self
cache_id = ('Form.get_value',
self._p_oid or repr(self),
field._p_oid or repr(field),
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)
if callable(value):
return value(field, id, **kw)
return value return value
psyco.bind(get_value) psyco.bind(get_value)
...@@ -584,9 +661,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -584,9 +661,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
portal = portal_url.getPortalObject() portal = portal_url.getPortalObject()
portal_skins = getToolByName(self, 'portal_skins') portal_skins = getToolByName(self, 'portal_skins')
default_field_library_path = portal.getProperty( default_field_library_path = portal.getProperty('erp5_default_field_library_path', None)
'erp5_default_field_library_path',
'erp5_core.Base_viewFieldLibrary')
if (not default_field_library_path or if (not default_field_library_path or
len(default_field_library_path.split('.'))!=2): len(default_field_library_path.split('.'))!=2):
return return
...@@ -597,24 +672,21 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -597,24 +672,21 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
default_field_library = getattr(skinfolder, form_id, None) default_field_library = getattr(skinfolder, form_id, None)
if default_field_library is None: if default_field_library is None:
return return
if not default_field_library_path in form_order:
for i in default_field_library.objectValues(): for i in default_field_library.objectValues():
field_meta_type, proxy_flag = get_field_meta_type_and_proxy_flag(i) field_meta_type, proxy_flag = get_field_meta_type_and_proxy_flag(i)
if meta_type==field_meta_type: if meta_type==field_meta_type:
if proxy_flag: if proxy_flag:
field_meta_type = '%s(Proxy)' % field_meta_type field_meta_type = '%s(Proxy' % field_meta_type
matched_item = {'form_id':form_id, matched_item = {'form_id':form_id,
'field_type':field_meta_type, 'field_type':field_meta_type,
'field_object':i, 'field_object':i,
'proxy_flag':proxy_flag, 'proxy_flag':proxy_flag,
'matched_rate':0 'matched_rate':0
} }
if not default_field_library_path in form_order:
matched_append(default_field_library_path, matched_append(default_field_library_path,
matched_item) matched_item)
if not default_field_library_path in \
perfect_matched_form_order:
perfect_matched_append(default_field_library_path,
matched_item)
id_ = field.getId() id_ = field.getId()
meta_type = field.meta_type meta_type = field.meta_type
...@@ -654,7 +726,6 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -654,7 +726,6 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
if perfect_matched: if perfect_matched:
perfect_matched_form_order.sort() perfect_matched_form_order.sort()
add_default_field_library()
return perfect_matched_form_order, perfect_matched return perfect_matched_form_order, perfect_matched
form_order.sort() form_order.sort()
...@@ -782,6 +853,37 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -782,6 +853,37 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
psyco.bind(__call__) psyco.bind(__call__)
psyco.bind(_exec) psyco.bind(_exec)
# Overload of the Form method
# Use the include_disabled parameter since
# we should consider all fields to render the group tab
# moreoever, listbox rendering fails whenever enabled
# is based on the cell parameter.
security.declareProtected('View', 'get_largest_group_length')
def get_largest_group_length(self):
"""Get the largest group length available; necessary for
'order' screen user interface.
XXX - Copyright issue
"""
max = 0
for group in self.get_groups(include_empty=1):
fields = self.get_fields_in_group(group, include_disabled=1)
if len(fields) > max:
max = len(fields)
return max
security.declareProtected('View', 'get_groups')
def get_groups(self, include_empty=0):
"""Get a list of all groups, in display order.
If include_empty is false, suppress groups that do not have
enabled fields.
XXX - Copyright issue
"""
if include_empty:
return self.group_list
return [group for group in self.group_list
if self.get_fields_in_group(group, include_disabled=1)]
# utility function # utility function
def get_field_meta_type_and_proxy_flag(field): def get_field_meta_type_and_proxy_flag(field):
...@@ -789,8 +891,7 @@ def get_field_meta_type_and_proxy_flag(field): ...@@ -789,8 +891,7 @@ def get_field_meta_type_and_proxy_flag(field):
try: try:
return field.getRecursiveTemplateField().meta_type, True return field.getRecursiveTemplateField().meta_type, True
except AttributeError: except AttributeError:
raise AttributeError, 'The proxy target of %s field does not '\ raise AttributeError, 'The proxy target of %s field does not exists. Please check the field setting.' % field.getId()
'exists. Please check the field setting.' % field.getId()
else: else:
return field.meta_type, False return field.meta_type, False
...@@ -811,3 +912,7 @@ psyco.bind(Field.get_value) ...@@ -811,3 +912,7 @@ psyco.bind(Field.get_value)
#from Products.CMFCore.ActionsTool import ActionsTool #from Products.CMFCore.ActionsTool import ActionsTool
#psyco.bind(ActionsTool.listFilteredActionsFor) #psyco.bind(ActionsTool.listFilteredActionsFor)
# install interactor
from Products.ERP5Type.Interactor import fielf_value_interactor
fielf_value_interactor.install()
...@@ -31,7 +31,9 @@ from Products.Formulator import Widget, Validator ...@@ -31,7 +31,9 @@ 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.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.PageTemplates.PageTemplateFile import PageTemplateFile
...@@ -44,10 +46,17 @@ from Products.PythonScripts.standard import url_quote_plus ...@@ -44,10 +46,17 @@ from Products.PythonScripts.standard import url_quote_plus
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from MethodObject import Method from MethodObject import Method
from zLOG import LOG, WARNING, DEBUG from zLOG import LOG, WARNING, DEBUG, PROBLEM
from Acquisition import aq_base, aq_inner, aq_acquire, aq_chain from Acquisition import aq_base, aq_inner, aq_acquire, aq_chain
from Globals import DTMLFile from Globals import DTMLFile
from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Form.ListBox import ListBox
from Products.ERP5Form.Form import StaticValue, TALESValue, OverrideValue, DefaultValue, EditableValue
_field_value_cache = {}
def purgeFieldValueCache():
_field_value_cache.clear()
class WidgetDelegatedMethod(Method): class WidgetDelegatedMethod(Method):
"""Method delegated to the proxied field's widget. """Method delegated to the proxied field's widget.
...@@ -324,6 +333,11 @@ class ProxyField(ZMIField): ...@@ -324,6 +333,11 @@ class ProxyField(ZMIField):
""" """
Return template field of the proxy field. Return template field of the proxy field.
""" """
try:
return self._getTemplateFieldCache()
except KeyError:
pass
form = self.aq_parent form = self.aq_parent
object = form.aq_parent object = form.aq_parent
try: try:
...@@ -335,6 +349,7 @@ class ProxyField(ZMIField): ...@@ -335,6 +349,7 @@ class ProxyField(ZMIField):
'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))
proxy_field = None proxy_field = None
self._setTemplateFieldCache(proxy_field)
return proxy_field return proxy_field
def getRecursiveTemplateField(self): def getRecursiveTemplateField(self):
...@@ -342,10 +357,12 @@ class ProxyField(ZMIField): ...@@ -342,10 +357,12 @@ class ProxyField(ZMIField):
Return template field of the proxy field. Return template field of the proxy field.
This result must not be a ProxyField. This result must not be a ProxyField.
""" """
template_field = self.getTemplateField() field = self
if template_field.__class__ == ProxyField: while True:
return template_field.getRecursiveTemplateField() template_field = field.getTemplateField()
else: if template_field.__class__ != ProxyField:
break
field = template_field
return template_field return template_field
security.declareProtected('Access contents information', security.declareProtected('Access contents information',
...@@ -439,26 +456,6 @@ class ProxyField(ZMIField): ...@@ -439,26 +456,6 @@ class ProxyField(ZMIField):
# ("form_id and field_id don't define a valid template") # ("form_id and field_id don't define a valid template")
pass pass
security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw):
"""Get value for id.
Optionally pass keyword arguments that get passed to TALES
expression.
"""
result = None
if (id in self.widget.property_names) or \
(not self.is_delegated(id)):
result = ZMIField.get_value(self, id, **kw)
else:
proxy_field = self.getTemplateField()
if proxy_field is not None:
REQUEST = get_request()
REQUEST.set('field__proxyfield_%s_%s' % (proxy_field.id, id),
REQUEST.get('field__proxyfield_%s_%s' % (self.id, id), self))
result = proxy_field.get_value(id, **kw)
return result
security.declareProtected('Access contents information', 'has_value') security.declareProtected('Access contents information', 'has_value')
def has_value(self, id): def has_value(self, id):
""" """
...@@ -485,3 +482,115 @@ class ProxyField(ZMIField): ...@@ -485,3 +482,115 @@ class ProxyField(ZMIField):
else: else:
result = ZMIField._get_user_input_value(self, key, REQUEST) result = ZMIField._get_user_input_value(self, key, REQUEST)
return result return result
#
# 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
def getFieldValue(self, field, id, **kw):
"""
Return a callable expression
"""
tales_expr = self.get_tales_expression(id)
if tales_expr:
return TALESValue(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:
return OverrideValue(override)
# Get a normal value.
try:
template_field = self.getRecursiveTemplateField()
# Old ListBox instance might have default attribute. so we need to check it.
if id=='default' and isinstance(aq_base(template_field), ListBox):
return self._get_value(id, **kw)
value = self.get_recursive_orig_value(id)
except KeyError:
# For ListBox
return self._get_value(id, **kw)
field_id = field.id
if id == 'default' and field_id.startswith('my_'):
return DefaultValue(field_id, value)
# For the 'editable' value, we try to get a default value
if id == 'editable':
return EditableValue(value)
# Return default value in non callable mode
if callable(value):
return StaticValue(value)
# Return default value in non callable mode
return StaticValue(value)(field, id, **kw)
security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw):
REQUEST = get_request()
if ((id in self.widget.property_names) or
(not self.is_delegated(id))):
return ZMIField.get_value(self, id, **kw)
field = self
proxy_field = self.getTemplateField()
if proxy_field is not None and REQUEST is not None:
field = REQUEST.get('field__proxyfield_%s_%s' % (self.id, id), self)
REQUEST.set('field__proxyfield_%s_%s' % (proxy_field.id, id), field)
cache_id = ('ProxyField.get_value',
self._p_oid or repr(self),
field._p_oid or repr(field),
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] = self.getFieldValue(field, id, **kw)
if callable(value):
return value(field, id, **kw)
return value
def _get_value(self, id, **kw):
proxy_field = self.getTemplateField()
if proxy_field is not None:
return proxy_field.get_value(id, **kw)
def _getCacheId(self):
return '%s%s' % ('ProxyField', self._p_oid or repr(self))
def _setTemplateFieldCache(self, field):
getTransactionalVariable(self)[self._getCacheId()] = field
def _getTemplateFieldCache(self):
return getTransactionalVariable(self)[self._getCacheId()].__of__(self.aq_parent)
...@@ -53,7 +53,7 @@ from Products.Formulator.StandardFields import FloatField ...@@ -53,7 +53,7 @@ from Products.Formulator.StandardFields import FloatField
from Products.Formulator.StandardFields import StringField from Products.Formulator.StandardFields import StringField
from Products.ERP5Type.Core.Folder import Folder from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import ERP5Form from Products.ERP5Form.Form import ERP5Form, purgeFieldValueCache
class TestFloatField(unittest.TestCase): class TestFloatField(unittest.TestCase):
...@@ -75,6 +75,7 @@ class TestFloatField(unittest.TestCase): ...@@ -75,6 +75,7 @@ class TestFloatField(unittest.TestCase):
self.field.values['precision'] = 0 self.field.values['precision'] = 0
self.assertEquals('12', self.widget.format_value(self.field, 12.34)) self.assertEquals('12', self.widget.format_value(self.field, 12.34))
purgeFieldValueCache() # call this before changing internal field values.
self.field.values['precision'] = 2 self.field.values['precision'] = 2
self.assertEquals('0.01', self.widget.format_value(self.field, 0.011)) self.assertEquals('0.01', self.widget.format_value(self.field, 0.011))
# value is rounded # value is rounded
......
...@@ -50,6 +50,7 @@ ZopeTestCase.installProduct('ERP5Form') ...@@ -50,6 +50,7 @@ ZopeTestCase.installProduct('ERP5Form')
from Products.Formulator.TALESField import TALESMethod 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 ERP5Form from Products.ERP5Form.Form import ERP5Form
from Products.ERP5Form.ProxyField import purgeFieldValueCache
class TestProxify(unittest.TestCase): class TestProxify(unittest.TestCase):
...@@ -103,6 +104,7 @@ class TestProxify(unittest.TestCase): ...@@ -103,6 +104,7 @@ class TestProxify(unittest.TestCase):
self.assertEqual(field.is_delegated('description'), True) self.assertEqual(field.is_delegated('description'), True)
self.assertEqual(field.get_value('description'), '') self.assertEqual(field.get_value('description'), '')
purgeFieldValueCache() # must purge cache before changing internal field value.
template_field = self.base_view.my_string_field template_field = self.base_view.my_string_field
template_field.values['description'] = 'Description' template_field.values['description'] = 'Description'
self.assertEqual(field.get_value('description'), 'Description') self.assertEqual(field.get_value('description'), 'Description')
...@@ -131,6 +133,7 @@ class TestProxify(unittest.TestCase): ...@@ -131,6 +133,7 @@ class TestProxify(unittest.TestCase):
self.assertEqual(field.has_value('scrap_variable'), 0) self.assertEqual(field.has_value('scrap_variable'), 0)
purgeFieldValueCache() # must purge cache before changing internal field value.
template_field = self.address_view.my_region template_field = self.address_view.my_region
template_field.values['title'] = 'Region' template_field.values['title'] = 'Region'
self.assertEqual(field.get_value('title'), 'Region') self.assertEqual(field.get_value('title'), 'Region')
......
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