diff --git a/fields/__init__.py b/fields/__init__.py index 969930d..1013c35 100644 --- a/fields/__init__.py +++ b/fields/__init__.py @@ -49,7 +49,8 @@ class Field: # * showChanges If True, a variant of the field showing successive changes # made to it is shown. pxRender = Px(''' - + onclick=":'toggleCheckbox(%s, %s); %s' % (q(name), \ + q('%s_hidden' % name), field.getOnChange(name, zobj, \ + layoutType))"/> ''') diff --git a/fields/file.py b/fields/file.py index 9163b86..877ccff 100644 --- a/fields/file.py +++ b/fields/file.py @@ -15,7 +15,7 @@ # Appy. If not, see . # ------------------------------------------------------------------------------ -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): diff --git a/fields/ref.py b/fields/ref.py index bfeec9f..f6b1bfe 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -282,13 +282,11 @@ class Ref(Field): pxEdit = Px(''' - + .__file__) # Applications classes, in various formats -rootClasses = [] appClasses = [] appClassNames = [] allClassNames = [] +allShortClassNames = {} # In the following dict, we store, for every Appy class, the ordered list of # fields. diff --git a/gen/ui/appy.js b/gen/ui/appy.js index 32192bb..f9298ce 100644 --- a/gen/ui/appy.js +++ b/gen/ui/appy.js @@ -228,10 +228,13 @@ function askComputedField(hookId, objectUrl, fieldName) { 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. var fieldName = hookId.split('_')[1]; 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, evalInnerScripts); } @@ -286,14 +289,15 @@ function toggleSubTitles(tag) { // Functions used for master/slave relationships between widgets function getSlaveInfo(slave, 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. 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. - masterInfo = cssClasses[j].split('_'); + masterInfo = cssClasses[j].split('*'); 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') { masterName = masterName.substr(0, masterName.length-8); } - slavePrefix = 'slave_' + masterName + '_'; + slavePrefix = 'slave*' + masterName + '*'; for (var i=0; i < allSlaves.length; i++){ cssClasses = allSlaves[i].className.split(' '); for (var j=0; j < cssClasses.length; j++) { @@ -350,37 +354,52 @@ function getSlaves(master) { return res; } -function updateSlaves(master, slave) { - // Given the value(s) in a master field, we must update slave's visibility. - // If p_slave is given, it updates only this slave. Else, it updates all - // slaves of p_master. +function updateSlaves(master, slave, objectUrl, layoutType, requestValues){ + /* Given the value(s) in a master field, we must update slave's visibility or + value(s). If p_slave is given, it updates only this slave. Else, it updates + all slaves of p_master. */ var slaves = null; if (slave) { slaves = [slave]; } else { slaves = getSlaves(master); } masterValues = getMasterValues(master); for (var i=0; i < slaves.length; i++) { - showSlave = false; slaveryValues = getSlaveInfo(slaves[i], 'masterValues'); - for (var j=0; j < slaveryValues.length; j++) { - for (var k=0; k< masterValues.length; k++) { - if (slaveryValues[j] == masterValues[k]) showSlave = true; - } - } - if (showSlave) slaves[i].style.display = ""; - else slaves[i].style.display = "none"; + if (slaveryValues[0] != '+') { + // Update slaves visibility depending on master values. + var showSlave = false; + for (var j=0; j < slaveryValues.length; j++) { + for (var k=0; k< masterValues.length; k++) { + if (slaveryValues[j] == masterValues[k]) showSlave = true; + } + } + 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() { - // When the current page is loaded, we must set the correct state for all - // slave fields. +function initSlaves(objectUrl, layoutType, requestValues) { + /* When the current page is loaded, we must set the correct state for all + slave fields. p_requestValues are those from the slave fields that must + be ajax-updated. */ slaves = getElementsHavingName('table', 'slave'); i = slaves.length -1; while (i >= 0) { masterName = getSlaveInfo(slaves[i], 'masterName'); master = document.getElementById(masterName); // 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; } } diff --git a/gen/utils.py b/gen/utils.py index 4789ecf..0454714 100644 --- a/gen/utils.py +++ b/gen/utils.py @@ -211,7 +211,8 @@ def writeCookie(login, password, request): # ------------------------------------------------------------------------------ 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 = [] elif type(v) not in sutils.sequenceTypes: res = [v] else: res = v diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index 7dc4c6f..40e40f7 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -141,7 +141,9 @@ class ToolWrapper(AbstractWrapper): ''') pxPageBottom = Px(''' - ''') + ''') pxPortlet = Px(''' ''', template=AbstractWrapper.pxTemplate, hook='content') # Show on query list or grid, the field content for a given object. - pxQueryField = Px(''' + pxQueryField = Px(''' :field.pxRender - - ''') + ''') # Show query results as a list. pxQueryResultList = Px(''' diff --git a/shared/utils.py b/shared/utils.py index 38c42f0..b33fb32 100644 --- a/shared/utils.py +++ b/shared/utils.py @@ -253,6 +253,17 @@ def keepDigits(s): if c.isdigit(): res += c 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=' '): '''Returns a string representation of number p_n, which can be a float diff --git a/shared/xml_parser.py b/shared/xml_parser.py index b378d06..7e02734 100644 --- a/shared/xml_parser.py +++ b/shared/xml_parser.py @@ -596,31 +596,48 @@ class XmlMarshaller: def dumpFile(self, res, v): '''Dumps a file into the result.''' if not v: return + w = res.write # p_value contains the (possibly binary) content of a file. We will # encode it in Base64, in one or several parts. partTag = self.getTagName('part') res.write('<%s type="base64" number="1">' % partTag) if hasattr(v, 'data'): # The file is an Archetypes file. - valueType = v.data.__class__.__name__ - if valueType == 'Pdata': + if v.data.__class__.__name__ == 'Pdata': # There will be several parts. - res.write(v.data.data.encode('base64')) + w(v.data.data.encode('base64')) # Write subsequent parts nextPart = v.data.next - nextPartNumber = 2 + nextPartNb = 2 while nextPart: - res.write('' % partTag) # Close the previous part - res.write('<%s type="base64" number="%d">' % \ - (partTag, nextPartNumber)) - res.write(nextPart.data.encode('base64')) + w('' % partTag) # Close the previous part + w('<%s type="base64" number="%d">' % (partTag, nextPartNb)) + w(nextPart.data.encode('base64')) nextPart = nextPart.next - nextPartNumber += 1 + nextPartNb += 1 else: - res.write(v.data.encode('base64')) + w(v.data.encode('base64')) + w('' % 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('' % partTag) # Close the tag + partNb += 1 + f.close() else: - res.write(v.encode('base64')) - res.write('' % partTag) + w(v.encode('base64')) + w('' % partTag) def dumpDict(self, res, v): '''Dumps the XML version of dict p_v.''' @@ -704,11 +721,22 @@ class XmlMarshaller: if fieldValue: length = len(fieldValue) res.write(' count="%d"' % length) if fType == 'file': + # Get the MIME type + mimeType = None 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'): + fileName = fieldValue.filename + elif hasattr(fieldValue, 'uploadName'): + fileName = fieldValue.uploadName + if fileName: res.write(' name="') - self.dumpString(res, fieldValue.filename) + self.dumpString(res, fileName) res.write('"') res.write('>') # Dump the field value @@ -724,6 +752,8 @@ class XmlMarshaller: an instance at all, but another Python data structure or basic type, p_objectType is ignored.''' 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. if not hasattr(self, 'cdata'): XmlMarshaller.__init__(self) @@ -789,7 +819,6 @@ class XmlMarshaller: if field.type == 'File': fieldType = 'file' v = field.getValue(instance) - if v: v = v._zopeFile elif field.type == 'Ref': fieldType = 'ref' v = field.getValue(instance, type='zobjects')