diff --git a/gen/__init__.py b/gen/__init__.py
index e1825c6..ca4d2bb 100755
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -355,7 +355,7 @@ class Ref(Type):
class Computed(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
- default=None, optional=False, editDefault=False, show=True,
+ default=None, optional=False, editDefault=False, show='view',
page='main', group=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py
index 8afe3ed..98c608a 100644
--- a/gen/plone25/mixins/__init__.py
+++ b/gen/plone25/mixins/__init__.py
@@ -33,9 +33,13 @@ class AbstractMixin:
# creates the final object from the temp object.
previousData = None
if not created: previousData = self.rememberPreviousData()
- # We do not process form data (=real update on the object) if the tool
- # itself is being created.
- if obj._appy_meta_type != 'tool': obj.processForm()
+ # Perform the change on the object, unless self is a tool being created.
+ if (obj._appy_meta_type == 'tool') and created:
+ # We do not process form data (=real update on the object) if the
+ # tool itself is being created.
+ pass
+ else:
+ obj.processForm()
if previousData:
# Keep in history potential changes on historized fields
self.historizeData(previousData)
@@ -389,7 +393,7 @@ class AbstractMixin:
fieldDescr = fieldDescr.__dict__
appyType = fieldDescr['appyType']
if isEdit and (appyType['type']=='Ref') and appyType['add']:return False
- if isEdit and (appyType['type'] in ('Action', 'Computed')): return False
+ if isEdit and (appyType['type'] == 'Action'): return False
if (fieldDescr['widgetType'] == 'backField') and \
not self.getBRefs(fieldDescr['fieldRel']): return False
# Do not show field if it is optional and not selected in flavour
diff --git a/gen/plone25/skin/edit.pt b/gen/plone25/skin/edit.pt
index 92df734..8fbd5d4 100644
--- a/gen/plone25/skin/edit.pt
+++ b/gen/plone25/skin/edit.pt
@@ -3,23 +3,19 @@
errors request/errors | python:{};
Iterator python:modules['Products.Archetypes'].IndexIterator;
schematas contextObj/Schemata;
- fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(here, visible_only=True))];
+ fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(contextObj, visible_only=True))];
default_fieldset python:(not schematas or schematas.has_key('default')) and 'default' or fieldsets[0];
fieldset request/fieldset|options/fieldset|default_fieldset;
- fields python:schematas[fieldset].editableFields(here);
- dummy python:here.at_isEditable(fields);
- portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
- type_name here/getPortalTypeName|here/archetype_name;
- lockable python:hasattr(here, 'wl_isLocked');
- isLocked python:lockable and here.wl_isLocked();
- tabindex tabindex|python:Iterator(pos=7000);
+ fields python:schematas[fieldset].editableFields(contextObj);
+ lockable python:hasattr(contextObj, 'wl_isLocked');
+ isLocked python:lockable and contextObj.wl_isLocked();
isEdit python:True;
tool contextObj/getTool;
flavour python: tool.getFlavour(contextObj);
appFolder tool/getAppFolder;
appName appFolder/id;
- css python:here.getUniqueWidgetAttr(fields, 'helper_css');
- js python:here.getUniqueWidgetAttr(fields, 'helper_js');
+ css python:contextObj.getUniqueWidgetAttr(fields, 'helper_css');
+ js python:contextObj.getUniqueWidgetAttr(fields, 'helper_js');
phaseInfo python: contextObj.getAppyPhases(fieldset=fieldset, forPlone=True);
phase request/phase|phaseInfo/name;
pageName python: contextObj.getAppyPage(isEdit, phaseInfo);">
@@ -91,24 +87,20 @@
+ tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
+ tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
+ tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
+ i18n:attributes="value label_cancel;" i18n:domain="plone"/>
diff --git a/gen/plone25/skin/macros.pt b/gen/plone25/skin/macros.pt
index c5429ef..98bbf5b 100644
--- a/gen/plone25/skin/macros.pt
+++ b/gen/plone25/skin/macros.pt
@@ -202,7 +202,7 @@
-
+
diff --git a/gen/plone25/wrappers/__init__.py b/gen/plone25/wrappers/__init__.py
index 164633f..9046391 100644
--- a/gen/plone25/wrappers/__init__.py
+++ b/gen/plone25/wrappers/__init__.py
@@ -2,7 +2,8 @@
developer the real classes used by the underlying web framework.'''
# ------------------------------------------------------------------------------
-import time, os.path, mimetypes, unicodedata, random
+import os, os.path, time, mimetypes, unicodedata, random
+import appy.pod
from appy.gen import Search
from appy.gen.utils import sequenceTypes
from appy.shared.utils import getOsTempFolder
@@ -236,15 +237,23 @@ class AbstractWrapper:
wfTool.doActionFor(self.o, transitionName, comment=comment)
del self.o._v_appy_do
- def log(self, message, logLevel='info'):
+ def log(self, message, type='info'):
'''Logs a message in the log file. p_logLevel may be "info", "warning"
or "error".'''
logger = self.o.getProductConfig().logger
- if logLevel == 'warning': logMethod = logger.warn
- elif logLevel == 'error': logMethod = logger.error
+ if type == 'warning': logMethod = logger.warn
+ elif type == 'error': logMethod = logger.error
else: logMethod = logger.info
logMethod(message)
+ def say(self, message, type='info'):
+ '''Prints a message in the user interface. p_logLevel may be "info",
+ "warning" or "error".'''
+ mType = type
+ if mType == 'warning': mType = 'warn'
+ elif mType == 'error': mType = 'stop'
+ self.o.plone_utils.addPortalMessage(message, type=mType)
+
def normalize(self, s):
'''Returns a version of string p_s whose special chars have been
replaced with normal chars.'''
@@ -338,11 +347,16 @@ class FileWrapper:
raise 'Impossible to set attribute %s. "Settable" attributes ' \
'are "name", "content" and "mimeType".' % name
- def dump(self, filePath=None):
+ def dump(self, filePath=None, format=None, tool=None):
'''Writes the file on disk. 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.'''
+ The absolute path name of the dumped file is returned.
+ If an error occurs, the method returns None. If p_format is
+ specified, OpenOffice will be called for converting the dumped file
+ to the desired format. In this case, p_tool, a Appy tool, must be
+ provided. Indeed, any Appy tool contains parameters for contacting
+ OpenOffice in server mode.'''
if not filePath:
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
self.name)
@@ -358,5 +372,20 @@ class FileWrapper:
# Only one chunk
f.write(self.content)
f.close()
+ if format:
+ if not tool: return
+ # Convert the dumped file using OpenOffice
+ convScript = '%s/converter.py' % os.path.dirname(appy.pod.__file__)
+ cmd = '%s %s "%s" %s -p%d' % (tool.unoEnabledPython, convScript,
+ filePath, format, tool.openOfficePort)
+ res = os.system(cmd)
+ os.remove(filePath)
+ if res != 0: return
+ # 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)
return filePath
# ------------------------------------------------------------------------------
diff --git a/pod/converter.py b/pod/converter.py
index cfcbc02..d093412 100755
--- a/pod/converter.py
+++ b/pod/converter.py
@@ -20,15 +20,26 @@
import sys, os, os.path, time, signal
from optparse import OptionParser
-ODT_FILE_TYPES = {'doc': 'MS Word 97', # Could be 'MS Word 2003 XML'
- 'pdf': 'writer_pdf_Export',
- 'rtf': 'Rich Text Format',
- 'txt': 'Text',
- 'html': 'HTML (StarWriter)',
- 'htm': 'HTML (StarWriter)',
- 'odt': 'ODT'}
-# Conversion to ODT does not make any conversion; it simply updates indexes and
-# linked documents.
+htmlFilters = {'odt': 'HTML (StarWriter)',
+ 'ods': 'HTML (StarCalc)',
+ 'odp': 'impress_html_Export'}
+
+FILE_TYPES = {'odt': 'writer8',
+ 'ods': 'calc8',
+ 'odp': 'impress8',
+ 'htm': htmlFilters, 'html': htmlFilters,
+ 'rtf': 'Rich Text Format',
+ 'txt': 'Text',
+ 'csv': 'Text - txt - csv (StarCalc)',
+ 'pdf': {'odt': 'writer_pdf_Export', 'ods': 'calc_pdf_Export',
+ 'odp': 'impress_pdf_Export', 'odg': 'draw_pdf_Export'},
+ 'swf': 'impress_flash_Export',
+ 'doc': 'MS Word 97',
+ 'xls': 'MS Excel 97',
+ 'ppt': 'MS PowerPoint 97',
+}
+# Conversion from odt to odt does not make any conversion, but updates indexes
+# and linked documents.
# ------------------------------------------------------------------------------
class ConverterError(Exception): pass
@@ -46,7 +57,7 @@ DEFAULT_PORT = 2002
# ------------------------------------------------------------------------------
class Converter:
- '''Converts an ODT document into pdf, doc, txt or rtf.'''
+ '''Converts an document readable by OpenOffice into pdf, doc, txt or rtf.'''
exeVariants = ('soffice.exe', 'soffice')
pathReplacements = {'program files': 'progra~1',
'openoffice.org 1': 'openof~1',
@@ -54,41 +65,64 @@ class Converter:
}
def __init__(self, docPath, resultType, port=DEFAULT_PORT):
self.port = port
- self.docUrl, self.docPath = self.getDocUrls(docPath)
- self.resultFilter = self.getResultFilter(resultType)
- self.resultUrl = self.getResultUrl(resultType)
+ self.docUrl, self.docPath = self.getInputUrls(docPath)
+ self.inputType = os.path.splitext(docPath)[1][1:].lower()
+ self.resultType = resultType
+ self.resultFilter = self.getResultFilter()
+ self.resultUrl = self.getResultUrl()
self.ooContext = None
- self.oo = None # OpenOffice application object
- self.doc = None # OpenOffice loaded document
- def getDocUrls(self, docPath):
+ self.oo = None # The OpenOffice application object
+ self.doc = None # The OpenOffice loaded document
+
+ def getInputUrls(self, docPath):
+ '''Returns the absolute path of the input file. In fact, it returns a
+ tuple with some URL version of the path for OO as the first element
+ and the absolute path as the second element.'''
import uno
if not os.path.exists(docPath) and not os.path.isfile(docPath):
raise ConverterError(DOC_NOT_FOUND % docPath)
docAbsPath = os.path.abspath(docPath)
# Return one path for OO, one path for me.
return uno.systemPathToFileUrl(docAbsPath), docAbsPath
- def getResultFilter(self, resultType):
- if ODT_FILE_TYPES.has_key(resultType):
- res = ODT_FILE_TYPES[resultType]
+
+ def getResultFilter(self):
+ '''Based on the result type, identifies which OO filter to use for the
+ document conversion.'''
+ if FILE_TYPES.has_key(self.resultType):
+ res = FILE_TYPES[self.resultType]
+ if isinstance(res, dict):
+ res = res[self.inputType]
else:
- raise ConverterError(BAD_RESULT_TYPE % (resultType,
- ODT_FILE_TYPES.keys()))
+ raise ConverterError(BAD_RESULT_TYPE % (self.resultType,
+ FILE_TYPES.keys()))
return res
- def getResultUrl(self, resultType):
+
+ def getResultUrl(self):
+ '''Returns the path of the result file in the format needed by OO. If
+ the result type and the input type are the same (ie the user wants to
+ refresh indexes or some other action and not perform a real
+ conversion), the result file is named
+ .res..
+
+ Else, the result file is named like the input file but with a
+ different extension:
+ .
+ '''
import uno
baseName = os.path.splitext(self.docPath)[0]
- if resultType != 'odt':
- res = '%s.%s' % (baseName, resultType)
+ if self.resultType != self.inputType:
+ res = '%s.%s' % (baseName, self.resultType)
else:
- res = '%s.res.%s' % (baseName, resultType)
+ res = '%s.res.%s' % (baseName, self.resultType)
try:
f = open(res, 'w')
f.write('Hello')
f.close()
os.remove(res)
return uno.systemPathToFileUrl(res)
- except OSError, oe:
- raise ConverterError(CANNOT_WRITE_RESULT % (res, oe))
+ except (OSError, IOError), ioe:
+ raise ConverterError(CANNOT_WRITE_RESULT % (res, ioe))
+
def connect(self):
'''Connects to OpenOffice'''
if os.name == 'nt':
@@ -115,73 +149,90 @@ class Converter:
'com.sun.star.frame.Desktop', self.ooContext)
except NoConnectException, nce:
raise ConverterError(CONNECT_ERROR % (self.port, nce))
- def disconnect(self):
- self.doc.close(True)
- # Do a nasty thing before exiting the python process. In case the
- # last call is a oneway call (e.g. see idl-spec of insertString),
- # it must be forced out of the remote-bridge caches before python
- # exits the process. Otherwise, the oneway call may or may not reach
- # the target object.
- # I do this here by calling a cheap synchronous call (getPropertyValue).
- self.ooContext.ServiceManager
- def loadDocument(self):
- from com.sun.star.lang import IllegalArgumentException, \
- IndexOutOfBoundsException
+
+ def updateOdtDocument(self):
+ '''If the input file is an ODT document, we will perform 2 tasks:
+ 1) Update all annexes;
+ 2) Update sections (if sections refer to external content, we try to
+ include the content within the result file)
+ '''
+ from com.sun.star.lang import IndexOutOfBoundsException
# I need to use IndexOutOfBoundsException because sometimes, when
# using sections.getCount, UNO returns a number that is bigger than
# the real number of sections (this is because it also counts the
# sections that are present within the sub-documents to integrate)
+ # Update all indexes
+ indexes = self.doc.getDocumentIndexes()
+ indexesCount = indexes.getCount()
+ if indexesCount != 0:
+ for i in range(indexesCount):
+ try:
+ indexes.getByIndex(i).update()
+ except IndexOutOfBoundsException:
+ pass
+ # Update sections
+ self.doc.updateLinks()
+ sections = self.doc.getTextSections()
+ sectionsCount = sections.getCount()
+ if sectionsCount != 0:
+ for i in range(sectionsCount-1, -1, -1):
+ # I must walk into the section from last one to the first
+ # one. Else, when "disposing" sections, I remove sections
+ # and the remaining sections other indexes.
+ try:
+ section = sections.getByIndex(i)
+ if section.FileLink and section.FileLink.FileURL:
+ section.dispose() # This method removes the
+ # tags without removing the content
+ # of the section. Else, it won't appear.
+ except IndexOutOfBoundsException:
+ pass
+
+ def loadDocument(self):
+ from com.sun.star.lang import IllegalArgumentException, \
+ IndexOutOfBoundsException
from com.sun.star.beans import PropertyValue
try:
- # Load the document to convert in a new hidden frame
+ # Loads the document to convert in a new hidden frame
prop = PropertyValue()
prop.Name = 'Hidden'
prop.Value = True
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
(prop,))
- # Update all indexes
- indexes = self.doc.getDocumentIndexes()
- indexesCount = indexes.getCount()
- if indexesCount != 0:
- for i in range(indexesCount):
- try:
- indexes.getByIndex(i).update()
- except IndexOutOfBoundsException:
- pass
- # Update sections
- self.doc.updateLinks()
- sections = self.doc.getTextSections()
- sectionsCount = sections.getCount()
- if sectionsCount != 0:
- for i in range(sectionsCount-1, -1, -1):
- # I must walk into the section from last one to the first
- # one. Else, when "disposing" sections, I remove sections
- # and the remaining sections other indexes.
- try:
- section = sections.getByIndex(i)
- if section.FileLink and section.FileLink.FileURL:
- section.dispose() # This method removes the
- # tags without removing the content
- # of the section. Else, it won't appear.
- except IndexOutOfBoundsException:
- pass
+ if self.inputType == 'odt':
+ # Perform additional tasks for odt documents
+ self.updateOdtDocument()
+ try:
+ self.doc.refresh()
+ except AttributeError:
+ pass
except IllegalArgumentException, iae:
raise ConverterError(URL_NOT_FOUND % (self.docPath, iae))
+
def convertDocument(self):
- if self.resultFilter != 'ODT':
- # I must really perform a conversion
- from com.sun.star.beans import PropertyValue
- prop = PropertyValue()
- prop.Name = 'FilterName'
- prop.Value = self.resultFilter
- self.doc.storeToURL(self.resultUrl, (prop,))
- else:
- self.doc.storeToURL(self.resultUrl, ())
+ '''Calls OO to perform a document conversion. Note that the conversion
+ is not really done if the source and target documents have the same
+ type.'''
+ properties = []
+ from com.sun.star.beans import PropertyValue
+ prop = PropertyValue()
+ prop.Name = 'FilterName'
+ prop.Value = self.resultFilter
+ properties.append(prop)
+ if self.resultType == 'csv':
+ # For CSV export, add options (separator, etc)
+ optionsProp = PropertyValue()
+ optionsProp.Name = 'FilterOptions'
+ optionsProp.Value = '59,34,76,1'
+ properties.append(optionsProp)
+ self.doc.storeToURL(self.resultUrl, tuple(properties))
+
def run(self):
+ '''Connects to OO, does the job and disconnects.'''
self.connect()
self.loadDocument()
self.convertDocument()
- self.disconnect()
+ self.doc.close(True)
# ConverterScript-related messages ---------------------------------------------
WRONG_NB_OF_ARGS = 'Wrong number of arguments.'
@@ -191,12 +242,13 @@ ERROR_CODE = 1
class ConverterScript:
usage = 'usage: python converter.py fileToConvert outputType [options]\n' \
' where fileToConvert is the absolute or relative pathname of\n' \
- ' the ODT file you want to convert;\n'\
+ ' the file you want to convert (or whose content like\n' \
+ ' indexes need to be refreshed);\n'\
' and outputType is the output format, that must be one of\n' \
' %s.\n' \
- ' "python" should be a UNO-enabled Python interpreter (ie the one\n' \
- ' which is included in the OpenOffice.org distribution).' % \
- str(ODT_FILE_TYPES.keys())
+ ' "python" should be a UNO-enabled Python interpreter (ie the ' \
+ ' one which is included in the OpenOffice.org distribution).' % \
+ str(FILE_TYPES.keys())
def run(self):
optParser = OptionParser(usage=ConverterScript.usage)
optParser.add_option("-p", "--port", dest="port",
diff --git a/pod/renderer.py b/pod/renderer.py
index f520277..6864c83 100755
--- a/pod/renderer.py
+++ b/pod/renderer.py
@@ -25,7 +25,7 @@ import appy.pod
from appy.pod import PodError
from appy.shared.xml_parser import XmlElement
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
-from appy.pod.converter import ODT_FILE_TYPES
+from appy.pod.converter import FILE_TYPES
from appy.pod.buffers import FileBuffer
from appy.pod.xhtml2odt import Xhtml2OdtConverter
from appy.pod.doc_importers import OdtImporter, ImageImporter, PdfImporter
@@ -423,9 +423,9 @@ class Renderer:
os.rename(resultOdtName, self.result)
else:
if resultType.startswith('.'): resultType = resultType[1:]
- if not resultType in ODT_FILE_TYPES.keys():
+ if not resultType in FILE_TYPES.keys():
raise PodError(BAD_RESULT_TYPE % (
- self.result, ODT_FILE_TYPES.keys()))
+ self.result, FILE_TYPES.keys()))
# Call OpenOffice to perform the conversion or document update
self.callOpenOffice(resultOdtName, resultType)
# I have the result. Move it to the correct name