[gen] Master-slave fields: slave values can now ajax-change when the user modifies master values.
This commit is contained in:
parent
f7172be6ee
commit
b8ceb66a49
|
@ -49,7 +49,8 @@ class Field:
|
||||||
# * showChanges If True, a variant of the field showing successive changes
|
# * showChanges If True, a variant of the field showing successive changes
|
||||||
# made to it is shown.
|
# made to it is shown.
|
||||||
pxRender = Px('''
|
pxRender = Px('''
|
||||||
<x var="showChanges=showChanges|False;
|
<x var="showChanges=showChanges|req.get('showChanges',False);
|
||||||
|
layoutType=layoutType|req.get('layoutType');
|
||||||
layout=field.layouts[layoutType];
|
layout=field.layouts[layoutType];
|
||||||
name=fieldName|field.name;
|
name=fieldName|field.name;
|
||||||
sync=field.sync[layoutType];
|
sync=field.sync[layoutType];
|
||||||
|
@ -65,8 +66,7 @@ class Field:
|
||||||
isMultiple=(field.multiplicity[1] == None) or \
|
isMultiple=(field.multiplicity[1] == None) or \
|
||||||
(field.multiplicity[1] > 1);
|
(field.multiplicity[1] > 1);
|
||||||
masterCss=field.slaves and ('master_%s' % name) or '';
|
masterCss=field.slaves and ('master_%s' % name) or '';
|
||||||
slaveCss=field.master and ('slave_%s_%s' % \
|
slaveCss=field.getSlaveCss();
|
||||||
(field.masterName, '_'.join(field.masterValue))) or '';
|
|
||||||
tagCss=tagCss|'';
|
tagCss=tagCss|'';
|
||||||
tagCss=('%s %s' % (slaveCss, tagCss)).strip();
|
tagCss=('%s %s' % (slaveCss, tagCss)).strip();
|
||||||
tagId='%s_%s' % (zobj.UID(), name);
|
tagId='%s_%s' % (zobj.UID(), name);
|
||||||
|
@ -170,7 +170,12 @@ class Field:
|
||||||
# The behaviour of this field may depend on another, "master" field
|
# The behaviour of this field may depend on another, "master" field
|
||||||
self.master = master
|
self.master = master
|
||||||
if master: self.master.slaves.append(self)
|
if master: self.master.slaves.append(self)
|
||||||
# When master has some value(s), there is impact on this field.
|
# The semantics of attribute "masterValue" below is as follows:
|
||||||
|
# - if "masterValue" is anything but a method, the field will be shown
|
||||||
|
# only when the master has this value, or one of it if multivalued;
|
||||||
|
# - if "masterValue" is a method, the value(s) of the slave field will
|
||||||
|
# be returned by this method, depending on the master value(s) that
|
||||||
|
# are given to it, as its unique parameter.
|
||||||
self.masterValue = gutils.initMasterValue(masterValue)
|
self.masterValue = gutils.initMasterValue(masterValue)
|
||||||
# If a field must retain attention in a particular way, set focus=True.
|
# If a field must retain attention in a particular way, set focus=True.
|
||||||
# It will be rendered in a special way.
|
# It will be rendered in a special way.
|
||||||
|
@ -323,6 +328,7 @@ class Field:
|
||||||
if not masterData: return True
|
if not masterData: return True
|
||||||
else:
|
else:
|
||||||
master, masterValue = masterData
|
master, masterValue = masterData
|
||||||
|
if masterValue and callable(masterValue): return True
|
||||||
reqValue = master.getRequestValue(obj.REQUEST)
|
reqValue = master.getRequestValue(obj.REQUEST)
|
||||||
# reqValue can be a list or not
|
# reqValue can be a list or not
|
||||||
if type(reqValue) not in sutils.sequenceTypes:
|
if type(reqValue) not in sutils.sequenceTypes:
|
||||||
|
@ -567,6 +573,38 @@ class Field:
|
||||||
if self.master: return (self.master, self.masterValue)
|
if self.master: return (self.master, self.masterValue)
|
||||||
if self.group: return self.group.getMasterData()
|
if self.group: return self.group.getMasterData()
|
||||||
|
|
||||||
|
def getSlaveCss(self):
|
||||||
|
'''Gets the CSS class that must apply to this field in the web UI when
|
||||||
|
this field is the slave of another field.'''
|
||||||
|
if not self.master: return ''
|
||||||
|
res = 'slave*%s*' % self.masterName
|
||||||
|
if not callable(self.masterValue):
|
||||||
|
res += '*'.join(self.masterValue)
|
||||||
|
else:
|
||||||
|
res += '+'
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getOnChange(self, name, zobj, layoutType):
|
||||||
|
'''When this field is a master, this method computes the call to the
|
||||||
|
Javascript function that will be called when its value changes (in
|
||||||
|
order to update slaves).'''
|
||||||
|
if not self.slaves: return ''
|
||||||
|
q = zobj.getTool().quote
|
||||||
|
# Create the dict of request values for slave fields.
|
||||||
|
rvs = {}
|
||||||
|
req = zobj.REQUEST
|
||||||
|
for slave in self.slaves:
|
||||||
|
name = slave.name
|
||||||
|
if not req.has_key(name): continue
|
||||||
|
if not req[name]: continue
|
||||||
|
rvs[name] = req[name]
|
||||||
|
if rvs:
|
||||||
|
rvs = ',%s' % sutils.getStringDict(rvs)
|
||||||
|
else:
|
||||||
|
rvs = ''
|
||||||
|
return 'updateSlaves(this,null,%s,%s%s)' % \
|
||||||
|
(q(zobj.absolute_url()), q(layoutType), rvs)
|
||||||
|
|
||||||
def isEmptyValue(self, value, obj=None):
|
def isEmptyValue(self, value, obj=None):
|
||||||
'''Returns True if the p_value must be considered as an empty value.'''
|
'''Returns True if the p_value must be considered as an empty value.'''
|
||||||
return value in self.nullValues
|
return value in self.nullValues
|
||||||
|
|
|
@ -33,8 +33,9 @@ class Boolean(Field):
|
||||||
<x var="isChecked=field.isChecked(zobj, rawValue)">
|
<x var="isChecked=field.isChecked(zobj, rawValue)">
|
||||||
<input type="checkbox" name=":name + '_visible'" id=":name"
|
<input type="checkbox" name=":name + '_visible'" id=":name"
|
||||||
class=":masterCss" checked=":isChecked"
|
class=":masterCss" checked=":isChecked"
|
||||||
onclick=":'toggleCheckbox(%s, %s); updateSlaves(this)' % \
|
onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \
|
||||||
(q(name), q('%s_hidden' % name))"/>
|
q('%s_hidden' % name), field.getOnChange(name, zobj, \
|
||||||
|
layoutType))"/>
|
||||||
<input type="hidden" name=":name" id=":'%s_hidden' % name"
|
<input type="hidden" name=":name" id=":'%s_hidden' % name"
|
||||||
value=":isChecked and 'True' or 'False'"/>
|
value=":isChecked and 'True' or 'False'"/>
|
||||||
</x>''')
|
</x>''')
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# Appy. If not, see <http://www.gnu.org/licenses/>.
|
# Appy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import time, os.path, mimetypes
|
import time, os.path, mimetypes, shutil
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
from appy import Object
|
from appy import Object
|
||||||
from appy.fields import Field
|
from appy.fields import Field
|
||||||
|
@ -27,6 +27,7 @@ from appy.shared import UnmarshalledFile, mimeTypesExts
|
||||||
WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
||||||
'2-tuple (fileName, fileContent) or a 3-tuple (fileName, fileContent, ' \
|
'2-tuple (fileName, fileContent) or a 3-tuple (fileName, fileContent, ' \
|
||||||
'mimeType).'
|
'mimeType).'
|
||||||
|
CONVERSION_ERROR = 'An error occurred. %s'
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class FileInfo:
|
class FileInfo:
|
||||||
|
@ -44,6 +45,12 @@ class FileInfo:
|
||||||
self.mimeType = None # Its MIME type
|
self.mimeType = None # Its MIME type
|
||||||
self.modified = None # The last modification date for this file.
|
self.modified = None # The last modification date for this file.
|
||||||
|
|
||||||
|
def getFilePath(self, obj):
|
||||||
|
'''Returns the absolute file name of the file on disk that corresponds
|
||||||
|
to this FileInfo instance.'''
|
||||||
|
dbFolder, folder = obj.o.getFsFolder()
|
||||||
|
return os.path.join(dbFolder, folder, self.fsName)
|
||||||
|
|
||||||
def removeFile(self, dbFolder, removeEmptyFolders=False):
|
def removeFile(self, dbFolder, removeEmptyFolders=False):
|
||||||
'''Removes the file from the filesystem.'''
|
'''Removes the file from the filesystem.'''
|
||||||
try:
|
try:
|
||||||
|
@ -176,6 +183,39 @@ class FileInfo:
|
||||||
response.write(chunk)
|
response.write(chunk)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
def dump(self, obj, filePath=None, format=None):
|
||||||
|
'''Exports this file to disk (outside the db-controller filesystem).
|
||||||
|
The tied Appy p_obj(ect) is required. If p_filePath is specified, it
|
||||||
|
is the path name where the file will be dumped; folders mentioned in
|
||||||
|
it must exist. If not, the file will be dumped in the OS temp folder.
|
||||||
|
The absolute path name of the dumped file is returned. If an error
|
||||||
|
occurs, the method returns None. If p_format is specified,
|
||||||
|
LibreOffice will be called for converting the dumped file to the
|
||||||
|
desired format.'''
|
||||||
|
if not filePath:
|
||||||
|
filePath = '%s/file%f.%s' % (sutils.getOsTempFolder(), time.time(),
|
||||||
|
self.fsName)
|
||||||
|
# Copies the file to disk.
|
||||||
|
shutil.copyfile(self.getFilePath(obj), filePath)
|
||||||
|
if format:
|
||||||
|
# Convert the dumped file using LibreOffice
|
||||||
|
errorMessage = obj.tool.convert(filePath, format)
|
||||||
|
# Even if we have an "error" message, it could be a simple warning.
|
||||||
|
# So we will continue here and, as a subsequent check for knowing if
|
||||||
|
# an error occurred or not, we will test the existence of the
|
||||||
|
# converted file (see below).
|
||||||
|
os.remove(filePath)
|
||||||
|
# Return the name of the converted file.
|
||||||
|
baseName, ext = os.path.splitext(filePath)
|
||||||
|
if (ext == '.%s' % format):
|
||||||
|
filePath = '%s.res.%s' % (baseName, format)
|
||||||
|
else:
|
||||||
|
filePath = '%s.%s' % (baseName, format)
|
||||||
|
if not os.path.exists(filePath):
|
||||||
|
obj.log(CONVERSION_ERROR % errorMessage, type='error')
|
||||||
|
return
|
||||||
|
return filePath
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class File(Field):
|
class File(Field):
|
||||||
|
|
||||||
|
|
|
@ -282,13 +282,11 @@ class Ref(Field):
|
||||||
|
|
||||||
pxEdit = Px('''
|
pxEdit = Px('''
|
||||||
<select if="field.link"
|
<select if="field.link"
|
||||||
var2="requestValue=req.get(name, []);
|
var2="zobjects=field.getSelectableObjects(obj);
|
||||||
inRequest=req.has_key(name);
|
|
||||||
zobjects=field.getSelectableObjects(obj);
|
|
||||||
uids=[o.UID() for o in \
|
uids=[o.UID() for o in \
|
||||||
field.getLinkedObjects(zobj).objects];
|
field.getLinkedObjects(zobj).objects]"
|
||||||
isBeingCreated=zobj.isTemporary()"
|
name=":name" id=":name" size=":isMultiple and field.height or ''"
|
||||||
name=":name" size=":isMultiple and field.height or ''"
|
onchange=":field.getOnChange(name, zobj, layoutType)"
|
||||||
multiple=":isMultiple">
|
multiple=":isMultiple">
|
||||||
<option value="" if="not isMultiple">:_('choose_a_value')</option>
|
<option value="" if="not isMultiple">:_('choose_a_value')</option>
|
||||||
<option for="ztied in zobjects" var2="uid=ztied.o.UID()"
|
<option for="ztied in zobjects" var2="uid=ztied.o.UID()"
|
||||||
|
@ -743,13 +741,31 @@ class Ref(Field):
|
||||||
|
|
||||||
def getSelectableObjects(self, obj):
|
def getSelectableObjects(self, obj):
|
||||||
'''This method returns the list of all objects that can be selected to
|
'''This method returns the list of all objects that can be selected to
|
||||||
be linked as references to p_obj via p_self.'''
|
be linked as references to p_obj via p_self. If master values are
|
||||||
if not self.select:
|
present in the request, we use field.masterValues method instead of
|
||||||
# No select method has been defined: we must retrieve all objects
|
self.select.'''
|
||||||
# of the referred type that the user is allowed to access.
|
req = obj.request
|
||||||
return obj.search(self.klass)
|
if 'masterValues' in req:
|
||||||
|
# Convert masterValue(s) from UID(s) to real object(s).
|
||||||
|
masterValues = req['masterValues'].strip()
|
||||||
|
if not masterValues: masterValues = None
|
||||||
|
else:
|
||||||
|
masterValues = masterValues.split('*')
|
||||||
|
tool = obj.tool
|
||||||
|
if len(masterValues) == 1:
|
||||||
|
masterValues = tool.getObject(masterValues[0])
|
||||||
|
else:
|
||||||
|
masterValues = [tool.getObject(v) for v in masterValues]
|
||||||
|
res = self.masterValue(obj, masterValues)
|
||||||
|
return res
|
||||||
else:
|
else:
|
||||||
return self.select(obj)
|
if not self.select:
|
||||||
|
# No select method has been defined: we must retrieve all
|
||||||
|
# objects of the referred type that the user is allowed to
|
||||||
|
# access.
|
||||||
|
return obj.search(self.klass)
|
||||||
|
else:
|
||||||
|
return self.select(obj)
|
||||||
|
|
||||||
xhtmlToText = re.compile('<.*?>', re.S)
|
xhtmlToText = re.compile('<.*?>', re.S)
|
||||||
def getReferenceLabel(self, refObject):
|
def getReferenceLabel(self, refObject):
|
||||||
|
|
|
@ -104,21 +104,19 @@ class String(Field):
|
||||||
|
|
||||||
pxEdit = Px('''
|
pxEdit = Px('''
|
||||||
<x var="fmt=field.format;
|
<x var="fmt=field.format;
|
||||||
isSelect=field.isSelect;
|
|
||||||
isMaster=field.slaves;
|
|
||||||
isOneLine=fmt in (0,3,4)">
|
isOneLine=fmt in (0,3,4)">
|
||||||
<select if="isSelect"
|
<select if="field.isSelect"
|
||||||
var2="possibleValues=field.getPossibleValues(zobj, \
|
var2="possibleValues=field.getPossibleValues(zobj, \
|
||||||
withTranslations=True, withBlankValue=True)"
|
withTranslations=True, withBlankValue=True)"
|
||||||
name=":name" id=":name" class=":masterCss"
|
name=":name" id=":name" class=":masterCss"
|
||||||
multiple=":isMultiple and 'multiple' or ''"
|
multiple=":isMultiple and 'multiple' or ''"
|
||||||
onchange=":isMaster and 'updateSlaves(this)' or ''"
|
onchange=":field.getOnChange(name, zobj, layoutType)"
|
||||||
size=":isMultiple and field.height or 1">
|
size=":isMultiple and field.height or 1">
|
||||||
<option for="val in possibleValues" value=":val[0]"
|
<option for="val in possibleValues" value=":val[0]"
|
||||||
selected=":field.isSelected(zobj, name, val[0], rawValue)"
|
selected=":field.isSelected(zobj, name, val[0], rawValue)"
|
||||||
title=":val[1]">:ztool.truncateValue(val[1],field.width)</option>
|
title=":val[1]">:ztool.truncateValue(val[1],field.width)</option>
|
||||||
</select>
|
</select>
|
||||||
<x if="isOneLine and not isSelect">
|
<x if="isOneLine and not field.isSelect">
|
||||||
<input id=":name" name=":name" size=":field.width"
|
<input id=":name" name=":name" size=":field.width"
|
||||||
maxlength=":field.maxChars"
|
maxlength=":field.maxChars"
|
||||||
value=":inRequest and requestValue or value"
|
value=":inRequest and requestValue or value"
|
||||||
|
|
|
@ -171,6 +171,9 @@ class Config:
|
||||||
# People having one of these roles will be able to create instances
|
# People having one of these roles will be able to create instances
|
||||||
# of classes defined in your application.
|
# of classes defined in your application.
|
||||||
defaultCreators = ['Manager']
|
defaultCreators = ['Manager']
|
||||||
|
# The "root" classes are those that will get their menu in the user
|
||||||
|
# interface. Put their names in the list below.
|
||||||
|
rootClasses = []
|
||||||
# Number of translations for every page on a Translation object
|
# Number of translations for every page on a Translation object
|
||||||
translationsPerPage = 30
|
translationsPerPage = 30
|
||||||
# Language that will be used as a basis for translating to other
|
# Language that will be used as a basis for translating to other
|
||||||
|
|
|
@ -553,9 +553,6 @@ class ZopeGenerator(Generator):
|
||||||
if theImport not in imports:
|
if theImport not in imports:
|
||||||
imports.append(theImport)
|
imports.append(theImport)
|
||||||
repls['imports'] = '\n'.join(imports)
|
repls['imports'] = '\n'.join(imports)
|
||||||
# Compute root classes
|
|
||||||
repls['rootClasses'] = ','.join(["'%s'" % c.name \
|
|
||||||
for c in classesButTool if c.isRoot()])
|
|
||||||
# Compute list of class definitions
|
# Compute list of class definitions
|
||||||
repls['appClasses'] = ','.join(['%s.%s' % (c.klass.__module__, \
|
repls['appClasses'] = ','.join(['%s.%s' % (c.klass.__module__, \
|
||||||
c.klass.__name__) for c in classes])
|
c.klass.__name__) for c in classes])
|
||||||
|
@ -564,6 +561,9 @@ class ZopeGenerator(Generator):
|
||||||
for c in classes])
|
for c in classes])
|
||||||
repls['allClassNames'] = ','.join(['"%s"' % c.name \
|
repls['allClassNames'] = ','.join(['"%s"' % c.name \
|
||||||
for c in classesButTool])
|
for c in classesButTool])
|
||||||
|
allShortClassNames = ['"%s":"%s"' % (c.name.split('_')[-1], c.name) \
|
||||||
|
for c in classesAll]
|
||||||
|
repls['allShortClassNames'] = ','.join(allShortClassNames)
|
||||||
# Compute the list of ordered attributes (forward and backward,
|
# Compute the list of ordered attributes (forward and backward,
|
||||||
# inherited included) for every Appy class.
|
# inherited included) for every Appy class.
|
||||||
attributes = []
|
attributes = []
|
||||||
|
|
|
@ -209,8 +209,12 @@ class ToolMixin(BaseMixin):
|
||||||
|
|
||||||
def getRootClasses(self):
|
def getRootClasses(self):
|
||||||
'''Returns the list of root classes for this application.'''
|
'''Returns the list of root classes for this application.'''
|
||||||
cfg = self.getProductConfig()
|
cfg = self.getProductConfig().appConfig
|
||||||
return [self.getAppyClass(k) for k in cfg.rootClasses]
|
rootClasses = cfg.rootClasses
|
||||||
|
if not rootClasses:
|
||||||
|
# We consider every class as being a root class.
|
||||||
|
rootClasses = cfg.appClassNames
|
||||||
|
return [self.getAppyClass(k) for k in rootClasses]
|
||||||
|
|
||||||
def getSearchInfo(self, className, refInfo=None):
|
def getSearchInfo(self, className, refInfo=None):
|
||||||
'''Returns, as an object:
|
'''Returns, as an object:
|
||||||
|
@ -466,6 +470,10 @@ class ToolMixin(BaseMixin):
|
||||||
'''Gets the Appy class corresponding to the Zope class named p_name.
|
'''Gets the Appy class corresponding to the Zope class named p_name.
|
||||||
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
|
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
|
||||||
the user-defined class.'''
|
the user-defined class.'''
|
||||||
|
# p_zopeName may be the name of the Zope class *or* the name of the Appy
|
||||||
|
# class (shorter, not prefixed with the underscored package path).
|
||||||
|
classes = self.getProductConfig().allShortClassNames
|
||||||
|
if zopeName in classes: zopeName = classes[zopeName]
|
||||||
zopeClass = self.getZopeClass(zopeName)
|
zopeClass = self.getZopeClass(zopeName)
|
||||||
if wrapper: return zopeClass.wrapperClass
|
if wrapper: return zopeClass.wrapperClass
|
||||||
else: return zopeClass.wrapperClass.__bases__[-1]
|
else: return zopeClass.wrapperClass.__bases__[-1]
|
||||||
|
|
|
@ -814,6 +814,20 @@ class BaseMixin:
|
||||||
res.append(field)
|
res.append(field)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getSlaveFieldsRequestValues(self, pageName):
|
||||||
|
'''Returns the list of slave fields having a masterValue being a
|
||||||
|
method.'''
|
||||||
|
res = {}
|
||||||
|
req = self.REQUEST
|
||||||
|
for field in self.getAllAppyTypes():
|
||||||
|
if field.page.name != pageName: continue
|
||||||
|
if field.masterValue and callable(field.masterValue):
|
||||||
|
# We have such a field
|
||||||
|
name = field.name
|
||||||
|
if req.has_key(name) and req[name]:
|
||||||
|
res[name] = req[name]
|
||||||
|
return sutils.getStringDict(res)
|
||||||
|
|
||||||
def getCssJs(self, fields, layoutType, res):
|
def getCssJs(self, fields, layoutType, res):
|
||||||
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of
|
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of
|
||||||
Javascript and CSS files required by Appy types p_fields when shown
|
Javascript and CSS files required by Appy types p_fields when shown
|
||||||
|
|
|
@ -20,10 +20,10 @@ PROJECTNAME = '<!applicationName!>'
|
||||||
diskFolder = os.path.dirname(<!applicationName!>.__file__)
|
diskFolder = os.path.dirname(<!applicationName!>.__file__)
|
||||||
|
|
||||||
# Applications classes, in various formats
|
# Applications classes, in various formats
|
||||||
rootClasses = [<!rootClasses!>]
|
|
||||||
appClasses = [<!appClasses!>]
|
appClasses = [<!appClasses!>]
|
||||||
appClassNames = [<!appClassNames!>]
|
appClassNames = [<!appClassNames!>]
|
||||||
allClassNames = [<!allClassNames!>]
|
allClassNames = [<!allClassNames!>]
|
||||||
|
allShortClassNames = {<!allShortClassNames!>}
|
||||||
|
|
||||||
# In the following dict, we store, for every Appy class, the ordered list of
|
# In the following dict, we store, for every Appy class, the ordered list of
|
||||||
# fields.
|
# fields.
|
||||||
|
|
|
@ -228,10 +228,13 @@ function askComputedField(hookId, objectUrl, fieldName) {
|
||||||
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxViewContent');
|
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxViewContent');
|
||||||
}
|
}
|
||||||
|
|
||||||
function askField(hookId, objectUrl, layoutType, showChanges){
|
function askField(hookId, objectUrl, layoutType, showChanges, masterValues,
|
||||||
|
requestValue){
|
||||||
// Sends an Ajax request for getting the content of any field.
|
// Sends an Ajax request for getting the content of any field.
|
||||||
var fieldName = hookId.split('_')[1];
|
var fieldName = hookId.split('_')[1];
|
||||||
var params = {'layoutType': layoutType, 'showChanges': showChanges};
|
var params = {'layoutType': layoutType, 'showChanges': showChanges};
|
||||||
|
if (masterValues) params['masterValues'] = masterValues.join('*');
|
||||||
|
if (requestValue) params[fieldName] = requestValue;
|
||||||
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxRender', params, null,
|
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxRender', params, null,
|
||||||
evalInnerScripts);
|
evalInnerScripts);
|
||||||
}
|
}
|
||||||
|
@ -286,14 +289,15 @@ function toggleSubTitles(tag) {
|
||||||
// Functions used for master/slave relationships between widgets
|
// Functions used for master/slave relationships between widgets
|
||||||
function getSlaveInfo(slave, infoType) {
|
function getSlaveInfo(slave, infoType) {
|
||||||
// Returns the appropriate info about slavery, depending on p_infoType.
|
// Returns the appropriate info about slavery, depending on p_infoType.
|
||||||
cssClasses = slave.className.split(' ');
|
var cssClasses = slave.className.split(' ');
|
||||||
|
var masterInfo = null;
|
||||||
// Find the CSS class containing master-related info.
|
// Find the CSS class containing master-related info.
|
||||||
for (var j=0; j < cssClasses.length; j++) {
|
for (var j=0; j < cssClasses.length; j++) {
|
||||||
if (cssClasses[j].indexOf('slave_') == 0) {
|
if (cssClasses[j].indexOf('slave*') == 0) {
|
||||||
// Extract, from this CSS class, master name or master values.
|
// Extract, from this CSS class, master name or master values.
|
||||||
masterInfo = cssClasses[j].split('_');
|
masterInfo = cssClasses[j].split('*');
|
||||||
if (infoType == 'masterName') return masterInfo[1];
|
if (infoType == 'masterName') return masterInfo[1];
|
||||||
else return masterInfo.slice(2); // Master values
|
else return masterInfo.slice(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,7 +341,7 @@ function getSlaves(master) {
|
||||||
if (master.type == 'checkbox') {
|
if (master.type == 'checkbox') {
|
||||||
masterName = masterName.substr(0, masterName.length-8);
|
masterName = masterName.substr(0, masterName.length-8);
|
||||||
}
|
}
|
||||||
slavePrefix = 'slave_' + masterName + '_';
|
slavePrefix = 'slave*' + masterName + '*';
|
||||||
for (var i=0; i < allSlaves.length; i++){
|
for (var i=0; i < allSlaves.length; i++){
|
||||||
cssClasses = allSlaves[i].className.split(' ');
|
cssClasses = allSlaves[i].className.split(' ');
|
||||||
for (var j=0; j < cssClasses.length; j++) {
|
for (var j=0; j < cssClasses.length; j++) {
|
||||||
|
@ -350,37 +354,52 @@ function getSlaves(master) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSlaves(master, slave) {
|
function updateSlaves(master, slave, objectUrl, layoutType, requestValues){
|
||||||
// Given the value(s) in a master field, we must update slave's visibility.
|
/* Given the value(s) in a master field, we must update slave's visibility or
|
||||||
// If p_slave is given, it updates only this slave. Else, it updates all
|
value(s). If p_slave is given, it updates only this slave. Else, it updates
|
||||||
// slaves of p_master.
|
all slaves of p_master. */
|
||||||
var slaves = null;
|
var slaves = null;
|
||||||
if (slave) { slaves = [slave]; }
|
if (slave) { slaves = [slave]; }
|
||||||
else { slaves = getSlaves(master); }
|
else { slaves = getSlaves(master); }
|
||||||
masterValues = getMasterValues(master);
|
masterValues = getMasterValues(master);
|
||||||
for (var i=0; i < slaves.length; i++) {
|
for (var i=0; i < slaves.length; i++) {
|
||||||
showSlave = false;
|
|
||||||
slaveryValues = getSlaveInfo(slaves[i], 'masterValues');
|
slaveryValues = getSlaveInfo(slaves[i], 'masterValues');
|
||||||
for (var j=0; j < slaveryValues.length; j++) {
|
if (slaveryValues[0] != '+') {
|
||||||
for (var k=0; k< masterValues.length; k++) {
|
// Update slaves visibility depending on master values.
|
||||||
if (slaveryValues[j] == masterValues[k]) showSlave = true;
|
var showSlave = false;
|
||||||
}
|
for (var j=0; j < slaveryValues.length; j++) {
|
||||||
}
|
for (var k=0; k< masterValues.length; k++) {
|
||||||
if (showSlave) slaves[i].style.display = "";
|
if (slaveryValues[j] == masterValues[k]) showSlave = true;
|
||||||
else slaves[i].style.display = "none";
|
}
|
||||||
|
}
|
||||||
|
if (showSlave) slaves[i].style.display = '';
|
||||||
|
else slaves[i].style.display = 'none';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Update slaves' values depending on master values.
|
||||||
|
var slaveId = slaves[i].id;
|
||||||
|
var slaveName = slaveId.split('_')[1];
|
||||||
|
var reqValue = null;
|
||||||
|
if (requestValues && (slaveName in requestValues))
|
||||||
|
reqValue = requestValues[slaveName];
|
||||||
|
askField(slaveId, objectUrl, layoutType, false, masterValues, reqValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSlaves() {
|
function initSlaves(objectUrl, layoutType, requestValues) {
|
||||||
// When the current page is loaded, we must set the correct state for all
|
/* When the current page is loaded, we must set the correct state for all
|
||||||
// slave fields.
|
slave fields. p_requestValues are those from the slave fields that must
|
||||||
|
be ajax-updated. */
|
||||||
slaves = getElementsHavingName('table', 'slave');
|
slaves = getElementsHavingName('table', 'slave');
|
||||||
i = slaves.length -1;
|
i = slaves.length -1;
|
||||||
while (i >= 0) {
|
while (i >= 0) {
|
||||||
masterName = getSlaveInfo(slaves[i], 'masterName');
|
masterName = getSlaveInfo(slaves[i], 'masterName');
|
||||||
master = document.getElementById(masterName);
|
master = document.getElementById(masterName);
|
||||||
// If master is not here, we can't hide its slaves when appropriate.
|
// If master is not here, we can't hide its slaves when appropriate.
|
||||||
if (master) updateSlaves(master, slaves[i]);
|
if (master) {
|
||||||
|
updateSlaves(master, slaves[i], objectUrl, layoutType, requestValues);
|
||||||
|
}
|
||||||
i -= 1;
|
i -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,8 @@ def writeCookie(login, password, request):
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def initMasterValue(v):
|
def initMasterValue(v):
|
||||||
'''Standardizes p_v as a list of strings.'''
|
'''Standardizes p_v as a list of strings, excepted if p_v is a method.'''
|
||||||
|
if callable(v): return v
|
||||||
if not isinstance(v, bool) and not v: res = []
|
if not isinstance(v, bool) and not v: res = []
|
||||||
elif type(v) not in sutils.sequenceTypes: res = [v]
|
elif type(v) not in sutils.sequenceTypes: res = [v]
|
||||||
else: res = v
|
else: res = v
|
||||||
|
|
|
@ -141,7 +141,9 @@ class ToolWrapper(AbstractWrapper):
|
||||||
</x>''')
|
</x>''')
|
||||||
|
|
||||||
pxPageBottom = Px('''
|
pxPageBottom = Px('''
|
||||||
<script type="text/javascript">initSlaves();</script>''')
|
<script type="text/javascript">:'initSlaves(%s,%s,%s)' % \
|
||||||
|
(q(zobj.absolute_url()), q(layoutType), \
|
||||||
|
zobj.getSlaveFieldsRequestValues(page))</script>''')
|
||||||
|
|
||||||
pxPortlet = Px('''
|
pxPortlet = Px('''
|
||||||
<x var="toolUrl=tool.url;
|
<x var="toolUrl=tool.url;
|
||||||
|
@ -260,11 +262,11 @@ class ToolWrapper(AbstractWrapper):
|
||||||
|
|
||||||
# Hook for defining a PX that proposes additional links, after the links
|
# Hook for defining a PX that proposes additional links, after the links
|
||||||
# corresponding to top-level pages.
|
# corresponding to top-level pages.
|
||||||
pxLinks = ''
|
pxLinks = Px('')
|
||||||
|
|
||||||
# Hook for defining a PX that proposes additional icons after standard
|
# Hook for defining a PX that proposes additional icons after standard
|
||||||
# icons in the user strip.
|
# icons in the user strip.
|
||||||
pxIcons = ''
|
pxIcons = Px('')
|
||||||
|
|
||||||
# Displays the content of a layouted object (a page or a field). If the
|
# Displays the content of a layouted object (a page or a field). If the
|
||||||
# layouted object is a page, the "layout target" (where to look for PXs)
|
# layouted object is a page, the "layout target" (where to look for PXs)
|
||||||
|
@ -310,7 +312,7 @@ class ToolWrapper(AbstractWrapper):
|
||||||
</table>''', template=AbstractWrapper.pxTemplate, hook='content')
|
</table>''', template=AbstractWrapper.pxTemplate, hook='content')
|
||||||
|
|
||||||
# Show on query list or grid, the field content for a given object.
|
# Show on query list or grid, the field content for a given object.
|
||||||
pxQueryField = Px('''<x>
|
pxQueryField = Px('''
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<x if="field.name == 'title'"
|
<x if="field.name == 'title'"
|
||||||
var2="navInfo='search.%s.%s.%d.%d' % \
|
var2="navInfo='search.%s.%s.%d.%d' % \
|
||||||
|
@ -350,8 +352,7 @@ class ToolWrapper(AbstractWrapper):
|
||||||
<x if="field.name != 'title'">
|
<x if="field.name != 'title'">
|
||||||
<x var="layoutType='cell'; innerRef=True"
|
<x var="layoutType='cell'; innerRef=True"
|
||||||
if="zobj.showField(field.name, 'result')">:field.pxRender</x>
|
if="zobj.showField(field.name, 'result')">:field.pxRender</x>
|
||||||
</x>
|
</x>''')
|
||||||
</x>''')
|
|
||||||
|
|
||||||
# Show query results as a list.
|
# Show query results as a list.
|
||||||
pxQueryResultList = Px('''
|
pxQueryResultList = Px('''
|
||||||
|
|
|
@ -253,6 +253,17 @@ def keepDigits(s):
|
||||||
if c.isdigit(): res += c
|
if c.isdigit(): res += c
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getStringDict(d):
|
||||||
|
'''Gets the string literal corresponding to dict p_d.'''
|
||||||
|
res = []
|
||||||
|
for k, v in d.iteritems():
|
||||||
|
if type(v) not in sequenceTypes:
|
||||||
|
value = "'%s':'%s'" % (k, v)
|
||||||
|
else:
|
||||||
|
value = "'%s':%s" % (k, v)
|
||||||
|
res.append(value)
|
||||||
|
return '{%s}' % ','.join(res)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def formatNumber(n, sep=',', precision=2, tsep=' '):
|
def formatNumber(n, sep=',', precision=2, tsep=' '):
|
||||||
'''Returns a string representation of number p_n, which can be a float
|
'''Returns a string representation of number p_n, which can be a float
|
||||||
|
|
|
@ -596,31 +596,48 @@ class XmlMarshaller:
|
||||||
def dumpFile(self, res, v):
|
def dumpFile(self, res, v):
|
||||||
'''Dumps a file into the result.'''
|
'''Dumps a file into the result.'''
|
||||||
if not v: return
|
if not v: return
|
||||||
|
w = res.write
|
||||||
# p_value contains the (possibly binary) content of a file. We will
|
# p_value contains the (possibly binary) content of a file. We will
|
||||||
# encode it in Base64, in one or several parts.
|
# encode it in Base64, in one or several parts.
|
||||||
partTag = self.getTagName('part')
|
partTag = self.getTagName('part')
|
||||||
res.write('<%s type="base64" number="1">' % partTag)
|
res.write('<%s type="base64" number="1">' % partTag)
|
||||||
if hasattr(v, 'data'):
|
if hasattr(v, 'data'):
|
||||||
# The file is an Archetypes file.
|
# The file is an Archetypes file.
|
||||||
valueType = v.data.__class__.__name__
|
if v.data.__class__.__name__ == 'Pdata':
|
||||||
if valueType == 'Pdata':
|
|
||||||
# There will be several parts.
|
# There will be several parts.
|
||||||
res.write(v.data.data.encode('base64'))
|
w(v.data.data.encode('base64'))
|
||||||
# Write subsequent parts
|
# Write subsequent parts
|
||||||
nextPart = v.data.next
|
nextPart = v.data.next
|
||||||
nextPartNumber = 2
|
nextPartNb = 2
|
||||||
while nextPart:
|
while nextPart:
|
||||||
res.write('</%s>' % partTag) # Close the previous part
|
w('</%s>' % partTag) # Close the previous part
|
||||||
res.write('<%s type="base64" number="%d">' % \
|
w('<%s type="base64" number="%d">' % (partTag, nextPartNb))
|
||||||
(partTag, nextPartNumber))
|
w(nextPart.data.encode('base64'))
|
||||||
res.write(nextPart.data.encode('base64'))
|
|
||||||
nextPart = nextPart.next
|
nextPart = nextPart.next
|
||||||
nextPartNumber += 1
|
nextPartNb += 1
|
||||||
else:
|
else:
|
||||||
res.write(v.data.encode('base64'))
|
w(v.data.encode('base64'))
|
||||||
|
w('</%s>' % partTag)
|
||||||
|
elif hasattr(v, 'uploadName'):
|
||||||
|
# The file is a Appy FileInfo instance. Read the file from disk.
|
||||||
|
filePath = v.getFilePath(self.instance)
|
||||||
|
f = file(filePath, 'rb')
|
||||||
|
partNb = 1
|
||||||
|
while True:
|
||||||
|
chunk = f.read(v.BYTES)
|
||||||
|
if not chunk: break
|
||||||
|
# We have one more chunk. Dump the start tag (excepted if it is
|
||||||
|
# the first chunk: the start tag has already been dumped, see
|
||||||
|
# above).
|
||||||
|
if partNb > 1:
|
||||||
|
w('<%s type="base64" number="%d">' % (partTag, partNb))
|
||||||
|
w(chunk.encode('base64'))
|
||||||
|
w('</%s>' % partTag) # Close the tag
|
||||||
|
partNb += 1
|
||||||
|
f.close()
|
||||||
else:
|
else:
|
||||||
res.write(v.encode('base64'))
|
w(v.encode('base64'))
|
||||||
res.write('</%s>' % partTag)
|
w('</%s>' % partTag)
|
||||||
|
|
||||||
def dumpDict(self, res, v):
|
def dumpDict(self, res, v):
|
||||||
'''Dumps the XML version of dict p_v.'''
|
'''Dumps the XML version of dict p_v.'''
|
||||||
|
@ -704,11 +721,22 @@ class XmlMarshaller:
|
||||||
if fieldValue: length = len(fieldValue)
|
if fieldValue: length = len(fieldValue)
|
||||||
res.write(' count="%d"' % length)
|
res.write(' count="%d"' % length)
|
||||||
if fType == 'file':
|
if fType == 'file':
|
||||||
|
# Get the MIME type
|
||||||
|
mimeType = None
|
||||||
if hasattr(fieldValue, 'content_type'):
|
if hasattr(fieldValue, 'content_type'):
|
||||||
res.write(' mimeType="%s"' % fieldValue.content_type)
|
mimeType = fieldValue.content_type
|
||||||
|
elif hasattr(fieldValue, 'mimeType'):
|
||||||
|
mimeType = fieldValue.mimeType
|
||||||
|
if mimeType: res.write(' mimeType="%s"' % mimeType)
|
||||||
|
# Get the file name
|
||||||
|
fileName = None
|
||||||
if hasattr(fieldValue, 'filename'):
|
if hasattr(fieldValue, 'filename'):
|
||||||
|
fileName = fieldValue.filename
|
||||||
|
elif hasattr(fieldValue, 'uploadName'):
|
||||||
|
fileName = fieldValue.uploadName
|
||||||
|
if fileName:
|
||||||
res.write(' name="')
|
res.write(' name="')
|
||||||
self.dumpString(res, fieldValue.filename)
|
self.dumpString(res, fileName)
|
||||||
res.write('"')
|
res.write('"')
|
||||||
res.write('>')
|
res.write('>')
|
||||||
# Dump the field value
|
# Dump the field value
|
||||||
|
@ -724,6 +752,8 @@ class XmlMarshaller:
|
||||||
an instance at all, but another Python data structure or basic type,
|
an instance at all, but another Python data structure or basic type,
|
||||||
p_objectType is ignored.'''
|
p_objectType is ignored.'''
|
||||||
self.objectType = objectType
|
self.objectType = objectType
|
||||||
|
# The Appy object is needed to marshall its File fields.
|
||||||
|
if objectType == 'appy': self.instance = instance
|
||||||
# Call the XmlMarshaller constructor if it hasn't been called yet.
|
# Call the XmlMarshaller constructor if it hasn't been called yet.
|
||||||
if not hasattr(self, 'cdata'):
|
if not hasattr(self, 'cdata'):
|
||||||
XmlMarshaller.__init__(self)
|
XmlMarshaller.__init__(self)
|
||||||
|
@ -789,7 +819,6 @@ class XmlMarshaller:
|
||||||
if field.type == 'File':
|
if field.type == 'File':
|
||||||
fieldType = 'file'
|
fieldType = 'file'
|
||||||
v = field.getValue(instance)
|
v = field.getValue(instance)
|
||||||
if v: v = v._zopeFile
|
|
||||||
elif field.type == 'Ref':
|
elif field.type == 'Ref':
|
||||||
fieldType = 'ref'
|
fieldType = 'ref'
|
||||||
v = field.getValue(instance, type='zobjects')
|
v = field.getValue(instance, type='zobjects')
|
||||||
|
|
Loading…
Reference in a new issue