[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
15 changed files with 253 additions and 74 deletions
|
@ -49,7 +49,8 @@ class Field:
|
|||
# * showChanges If True, a variant of the field showing successive changes
|
||||
# made to it is shown.
|
||||
pxRender = Px('''
|
||||
<x var="showChanges=showChanges|False;
|
||||
<x var="showChanges=showChanges|req.get('showChanges',False);
|
||||
layoutType=layoutType|req.get('layoutType');
|
||||
layout=field.layouts[layoutType];
|
||||
name=fieldName|field.name;
|
||||
sync=field.sync[layoutType];
|
||||
|
@ -65,8 +66,7 @@ class Field:
|
|||
isMultiple=(field.multiplicity[1] == None) or \
|
||||
(field.multiplicity[1] > 1);
|
||||
masterCss=field.slaves and ('master_%s' % name) or '';
|
||||
slaveCss=field.master and ('slave_%s_%s' % \
|
||||
(field.masterName, '_'.join(field.masterValue))) or '';
|
||||
slaveCss=field.getSlaveCss();
|
||||
tagCss=tagCss|'';
|
||||
tagCss=('%s %s' % (slaveCss, tagCss)).strip();
|
||||
tagId='%s_%s' % (zobj.UID(), name);
|
||||
|
@ -170,7 +170,12 @@ class Field:
|
|||
# The behaviour of this field may depend on another, "master" field
|
||||
self.master = master
|
||||
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)
|
||||
# If a field must retain attention in a particular way, set focus=True.
|
||||
# It will be rendered in a special way.
|
||||
|
@ -323,6 +328,7 @@ class Field:
|
|||
if not masterData: return True
|
||||
else:
|
||||
master, masterValue = masterData
|
||||
if masterValue and callable(masterValue): return True
|
||||
reqValue = master.getRequestValue(obj.REQUEST)
|
||||
# reqValue can be a list or not
|
||||
if type(reqValue) not in sutils.sequenceTypes:
|
||||
|
@ -567,6 +573,38 @@ class Field:
|
|||
if self.master: return (self.master, self.masterValue)
|
||||
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):
|
||||
'''Returns True if the p_value must be considered as an empty value.'''
|
||||
return value in self.nullValues
|
||||
|
|
|
@ -33,8 +33,9 @@ class Boolean(Field):
|
|||
<x var="isChecked=field.isChecked(zobj, rawValue)">
|
||||
<input type="checkbox" name=":name + '_visible'" id=":name"
|
||||
class=":masterCss" checked=":isChecked"
|
||||
onclick=":'toggleCheckbox(%s, %s); updateSlaves(this)' % \
|
||||
(q(name), q('%s_hidden' % name))"/>
|
||||
onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \
|
||||
q('%s_hidden' % name), field.getOnChange(name, zobj, \
|
||||
layoutType))"/>
|
||||
<input type="hidden" name=":name" id=":'%s_hidden' % name"
|
||||
value=":isChecked and 'True' or 'False'"/>
|
||||
</x>''')
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# 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 appy import Object
|
||||
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 ' \
|
||||
'2-tuple (fileName, fileContent) or a 3-tuple (fileName, fileContent, ' \
|
||||
'mimeType).'
|
||||
CONVERSION_ERROR = 'An error occurred. %s'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class FileInfo:
|
||||
|
@ -44,6 +45,12 @@ class FileInfo:
|
|||
self.mimeType = None # Its MIME type
|
||||
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):
|
||||
'''Removes the file from the filesystem.'''
|
||||
try:
|
||||
|
@ -176,6 +183,39 @@ class FileInfo:
|
|||
response.write(chunk)
|
||||
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):
|
||||
|
||||
|
|
|
@ -282,13 +282,11 @@ class Ref(Field):
|
|||
|
||||
pxEdit = Px('''
|
||||
<select if="field.link"
|
||||
var2="requestValue=req.get(name, []);
|
||||
inRequest=req.has_key(name);
|
||||
zobjects=field.getSelectableObjects(obj);
|
||||
var2="zobjects=field.getSelectableObjects(obj);
|
||||
uids=[o.UID() for o in \
|
||||
field.getLinkedObjects(zobj).objects];
|
||||
isBeingCreated=zobj.isTemporary()"
|
||||
name=":name" size=":isMultiple and field.height or ''"
|
||||
field.getLinkedObjects(zobj).objects]"
|
||||
name=":name" id=":name" size=":isMultiple and field.height or ''"
|
||||
onchange=":field.getOnChange(name, zobj, layoutType)"
|
||||
multiple=":isMultiple">
|
||||
<option value="" if="not isMultiple">:_('choose_a_value')</option>
|
||||
<option for="ztied in zobjects" var2="uid=ztied.o.UID()"
|
||||
|
@ -743,13 +741,31 @@ class Ref(Field):
|
|||
|
||||
def getSelectableObjects(self, obj):
|
||||
'''This method returns the list of all objects that can be selected to
|
||||
be linked as references to p_obj via p_self.'''
|
||||
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)
|
||||
be linked as references to p_obj via p_self. If master values are
|
||||
present in the request, we use field.masterValues method instead of
|
||||
self.select.'''
|
||||
req = obj.request
|
||||
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:
|
||||
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)
|
||||
def getReferenceLabel(self, refObject):
|
||||
|
|
|
@ -104,21 +104,19 @@ class String(Field):
|
|||
|
||||
pxEdit = Px('''
|
||||
<x var="fmt=field.format;
|
||||
isSelect=field.isSelect;
|
||||
isMaster=field.slaves;
|
||||
isOneLine=fmt in (0,3,4)">
|
||||
<select if="isSelect"
|
||||
<select if="field.isSelect"
|
||||
var2="possibleValues=field.getPossibleValues(zobj, \
|
||||
withTranslations=True, withBlankValue=True)"
|
||||
name=":name" id=":name" class=":masterCss"
|
||||
multiple=":isMultiple and 'multiple' or ''"
|
||||
onchange=":isMaster and 'updateSlaves(this)' or ''"
|
||||
onchange=":field.getOnChange(name, zobj, layoutType)"
|
||||
size=":isMultiple and field.height or 1">
|
||||
<option for="val in possibleValues" value=":val[0]"
|
||||
selected=":field.isSelected(zobj, name, val[0], rawValue)"
|
||||
title=":val[1]">:ztool.truncateValue(val[1],field.width)</option>
|
||||
</select>
|
||||
<x if="isOneLine and not isSelect">
|
||||
<x if="isOneLine and not field.isSelect">
|
||||
<input id=":name" name=":name" size=":field.width"
|
||||
maxlength=":field.maxChars"
|
||||
value=":inRequest and requestValue or value"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue