[gen] Allow for ajax-based master-slave relationships within the search screen for String fields.
This commit is contained in:
parent
6d6c842f12
commit
5bea4e728b
|
@ -327,10 +327,10 @@ class Ref(Field):
|
|||
width=None, height=5, maxChars=None, colspan=1, master=None,
|
||||
masterValue=None, focus=False, historized=False, mapping=None,
|
||||
label=None, queryable=False, queryFields=None, queryNbCols=1,
|
||||
navigable=False, searchSelect=None, changeOrder=True,
|
||||
sdefault='', scolspan=1, swidth=None, sheight=None,
|
||||
persist=True, render='list', menuIdMethod=None,
|
||||
menuInfoMethod=None, menuUrlMethod=None):
|
||||
navigable=False, changeOrder=True, sdefault='', scolspan=1,
|
||||
swidth=None, sheight=None, sselect=None, persist=True,
|
||||
render='list', menuIdMethod=None, menuInfoMethod=None,
|
||||
menuUrlMethod=None):
|
||||
self.klass = klass
|
||||
self.attribute = attribute
|
||||
# May the user add new objects through this ref ?
|
||||
|
@ -379,8 +379,23 @@ class Ref(Field):
|
|||
self.shownInfo = list(shownInfo)
|
||||
if not self.shownInfo: self.shownInfo.append('title')
|
||||
# If a method is defined in this field "select", it will be used to
|
||||
# filter the list of available tied objects.
|
||||
# return the list of possible tied objects. Be careful: this method can
|
||||
# receive, in its first argument ("self"), the tool instead of an
|
||||
# instance of the class where this field is defined. This little cheat
|
||||
# is:
|
||||
# - not really a problem: in this method you will mainly use methods
|
||||
# that are available on a tool as well as on any object (like
|
||||
# "search");
|
||||
# - necessary because in some cases we do not have an instance at our
|
||||
# disposal, ie, when we need to compute a list of objects on a
|
||||
# search screen.
|
||||
# NOTE that when a method is defined in field "masterValue" (see parent
|
||||
# class "Field"), it will be used instead of select (or sselect below).
|
||||
self.select = select
|
||||
# If you want to specify, for the search screen, a list of objects that
|
||||
# is different from the one produced by self.select, define an
|
||||
# alternative method in field "sselect" below.
|
||||
self.sselect = sselect or self.select
|
||||
# Maximum number of referenced objects shown at once.
|
||||
self.maxPerPage = maxPerPage
|
||||
# Specifies sync
|
||||
|
@ -396,11 +411,6 @@ class Ref(Field):
|
|||
self.queryNbCols = queryNbCols
|
||||
# Within the portlet, will referred elements appear ?
|
||||
self.navigable = navigable
|
||||
# The search select method is used if self.indexed is True. In this
|
||||
# case, we need to know among which values we can search on this field,
|
||||
# in the search screen. Those values are returned by self.searchSelect,
|
||||
# which must be a static method accepting the tool as single arg.
|
||||
self.searchSelect = searchSelect
|
||||
# If changeOrder is False, it even if the user has the right to modify
|
||||
# the field, it will not be possible to move objects or sort them.
|
||||
self.changeOrder = changeOrder
|
||||
|
@ -762,10 +772,9 @@ class Ref(Field):
|
|||
res = self.masterValue(obj, masterValues)
|
||||
return res
|
||||
else:
|
||||
# If this field is a ajax-updatable slave, no need to compute
|
||||
# selectable objects: it will be overridden by method
|
||||
# self.masterValue by a subsequent ajax request (=the "if"
|
||||
# statement above).
|
||||
# If this field is an ajax-updatable slave, no need to compute
|
||||
# possible values: it will be overridden by method self.masterValue
|
||||
# by a subsequent ajax request (=the "if" statement above).
|
||||
if self.masterValue and callable(self.masterValue): return []
|
||||
if not self.select:
|
||||
# No select method has been defined: we must retrieve all
|
||||
|
|
115
fields/string.py
115
fields/string.py
|
@ -53,7 +53,8 @@ class Selection:
|
|||
|
||||
def getText(self, obj, value, appyType):
|
||||
'''Gets the text that corresponds to p_value.'''
|
||||
for v, text in appyType.getPossibleValues(obj, withTranslations=True):
|
||||
for v, text in appyType.getPossibleValues(obj, ignoreMasterValues=True,\
|
||||
withTranslations=True):
|
||||
if v == value:
|
||||
return text
|
||||
return value
|
||||
|
@ -166,7 +167,8 @@ class String(Field):
|
|||
</x>
|
||||
<!-- The list of values -->
|
||||
<select var="preSelected=field.sdefault"
|
||||
name=":widgetName" size=":field.sheight" multiple="multiple">
|
||||
name=":widgetName" size=":field.sheight" multiple="multiple"
|
||||
onchange=":field.getOnChange(ztool, 'search', className)">
|
||||
<option for="v in field.getPossibleValues(ztool, withTranslations=True,\
|
||||
withBlankValue=False, className=className)"
|
||||
selected=":v[0] in preSelected" value=":v[0]"
|
||||
|
@ -476,7 +478,8 @@ class String(Field):
|
|||
return res
|
||||
|
||||
def getPossibleValues(self, obj, withTranslations=False,
|
||||
withBlankValue=False, className=None):
|
||||
withBlankValue=False, className=None,
|
||||
ignoreMasterValues=False):
|
||||
'''Returns the list of possible values for this field (only for fields
|
||||
with self.isSelect=True). If p_withTranslations is True, instead of
|
||||
returning a list of string values, the result is a list of tuples
|
||||
|
@ -485,52 +488,70 @@ class String(Field):
|
|||
p_className is given, p_obj is the tool and, if we need an instance
|
||||
of p_className, we will need to use obj.executeQuery to find one.'''
|
||||
if not self.isSelect: raise Exception('This field is not a selection.')
|
||||
if isinstance(self.validator, Selection):
|
||||
# We need to call self.methodName for getting the (dynamic) values.
|
||||
# If methodName begins with _appy_, it is a special Appy method:
|
||||
# we will call it on the Mixin (=p_obj) directly. Else, it is a
|
||||
# user method: we will call it on the wrapper (p_obj.appy()). Some
|
||||
# args can be hidden into p_methodName, separated with stars,
|
||||
# like in this example: method1*arg1*arg2. Only string params are
|
||||
# supported.
|
||||
methodName = self.validator.methodName
|
||||
# Unwrap parameters if any.
|
||||
if methodName.find('*') != -1:
|
||||
elems = methodName.split('*')
|
||||
methodName = elems[0]
|
||||
args = elems[1:]
|
||||
req = obj.REQUEST
|
||||
if ('masterValues' in req) and not ignoreMasterValues:
|
||||
# Get possible values from self.masterValue
|
||||
masterValues = req['masterValues']
|
||||
if '*' in masterValues: masterValues = masterValues.split('*')
|
||||
values = self.masterValue(obj.appy(), masterValues)
|
||||
if not withTranslations: res = values
|
||||
else:
|
||||
args = ()
|
||||
# On what object must we call the method that will produce the
|
||||
# values?
|
||||
if methodName.startswith('tool:'):
|
||||
obj = obj.getTool()
|
||||
methodName = methodName[5:]
|
||||
else:
|
||||
# We must call on p_obj. But if we have something in
|
||||
# p_className, p_obj is the tool and not an instance of
|
||||
# p_className as required. So find such an instance.
|
||||
if className:
|
||||
brains = obj.executeQuery(className, maxResults=1,
|
||||
brainsOnly=True)
|
||||
if brains:
|
||||
obj = brains[0].getObject()
|
||||
# Do we need to call the method on the object or on the wrapper?
|
||||
if methodName.startswith('_appy_'):
|
||||
exec 'res = obj.%s(*args)' % methodName
|
||||
else:
|
||||
exec 'res = obj.appy().%s(*args)' % methodName
|
||||
if not withTranslations: res = [v[0] for v in res]
|
||||
elif isinstance(res, list): res = res[:]
|
||||
res = []
|
||||
for v in values:
|
||||
res.append( (v, self.getFormattedValue(obj, v)) )
|
||||
else:
|
||||
# The list of (static) values is directly given in self.validator.
|
||||
res = []
|
||||
for value in self.validator:
|
||||
label = '%s_list_%s' % (self.labelId, value)
|
||||
if withTranslations:
|
||||
res.append( (value, obj.translate(label)) )
|
||||
# If this field is an ajax-updatable slave, no need to compute
|
||||
# possible values: it will be overridden by method self.masterValue
|
||||
# by a subsequent ajax request (=the "if" statement above).
|
||||
if self.masterValue and callable(self.masterValue) and \
|
||||
not ignoreMasterValues: return []
|
||||
if isinstance(self.validator, Selection):
|
||||
# We need to call self.methodName for getting the (dynamic)
|
||||
# values. If methodName begins with _appy_, it is a special Appy
|
||||
# method: we will call it on the Mixin (=p_obj) directly. Else,
|
||||
# it is a user method: we will call it on the wrapper
|
||||
# (p_obj.appy()). Some args can be hidden into p_methodName,
|
||||
# separated with stars, like in this example: method1*arg1*arg2.
|
||||
# Only string params are supported.
|
||||
methodName = self.validator.methodName
|
||||
# Unwrap parameters if any.
|
||||
if methodName.find('*') != -1:
|
||||
elems = methodName.split('*')
|
||||
methodName = elems[0]
|
||||
args = elems[1:]
|
||||
else:
|
||||
res.append(value)
|
||||
args = ()
|
||||
# On what object must we call the method that will produce the
|
||||
# values?
|
||||
if methodName.startswith('tool:'):
|
||||
obj = obj.getTool()
|
||||
methodName = methodName[5:]
|
||||
else:
|
||||
# We must call on p_obj. But if we have something in
|
||||
# p_className, p_obj is the tool and not an instance of
|
||||
# p_className as required. So find such an instance.
|
||||
if className:
|
||||
brains = obj.executeQuery(className, maxResults=1,
|
||||
brainsOnly=True)
|
||||
if brains:
|
||||
obj = brains[0].getObject()
|
||||
# Do we need to call the method on the object or on the wrapper?
|
||||
if methodName.startswith('_appy_'):
|
||||
exec 'res = obj.%s(*args)' % methodName
|
||||
else:
|
||||
exec 'res = obj.appy().%s(*args)' % methodName
|
||||
if not withTranslations: res = [v[0] for v in res]
|
||||
elif isinstance(res, list): res = res[:]
|
||||
else:
|
||||
# The list of (static) values is directly given in
|
||||
# self.validator.
|
||||
res = []
|
||||
for value in self.validator:
|
||||
label = '%s_list_%s' % (self.labelId, value)
|
||||
if withTranslations:
|
||||
res.append( (value, obj.translate(label)) )
|
||||
else:
|
||||
res.append(value)
|
||||
if withBlankValue and not self.isMultiValued():
|
||||
# Create the blank value to insert at the beginning of the list
|
||||
if withTranslations:
|
||||
|
@ -554,7 +575,7 @@ class String(Field):
|
|||
return obj.translate('bad_captcha')
|
||||
elif self.isSelect:
|
||||
# Check that the value is among possible values
|
||||
possibleValues = self.getPossibleValues(obj)
|
||||
possibleValues = self.getPossibleValues(obj,ignoreMasterValues=True)
|
||||
if isinstance(value, basestring):
|
||||
error = value not in possibleValues
|
||||
else:
|
||||
|
|
|
@ -213,7 +213,7 @@ class ToolMixin(BaseMixin):
|
|||
rootClasses = cfg.rootClasses
|
||||
if not rootClasses:
|
||||
# We consider every class as being a root class.
|
||||
rootClasses = cfg.appClassNames
|
||||
rootClasses = self.getProductConfig().appClassNames
|
||||
return [self.getAppyClass(k) for k in rootClasses]
|
||||
|
||||
def getSearchInfo(self, className, refInfo=None):
|
||||
|
|
|
@ -258,7 +258,7 @@ def getStringDict(d):
|
|||
res = []
|
||||
for k, v in d.iteritems():
|
||||
if type(v) not in sequenceTypes:
|
||||
value = "'%s':'%s'" % (k, v)
|
||||
value = "'%s':'%s'" % (k, v.replace("'", "\\'"))
|
||||
else:
|
||||
value = "'%s':%s" % (k, v)
|
||||
res.append(value)
|
||||
|
|
Loading…
Reference in a new issue