diff --git a/gen/__init__.py b/gen/__init__.py
index 96d9c72..cad3295 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
-import re, time, copy, sys, types, os, os.path, mimetypes
-from appy.shared.utils import Traceback
+import re, time, copy, sys, types, os, os.path, mimetypes, StringIO
from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
getClassName, SomeObjects
+import appy.pod
+from appy.pod.renderer import Renderer
from appy.shared.data import languages
+from appy.shared.utils import Traceback, getOsTempFolder
# Default Appy permissions -----------------------------------------------------
r, w, d = ('read', 'write', 'delete')
@@ -1421,6 +1423,28 @@ class File(Type):
width, height, colspan, master, masterValue, focus,
historized, True)
+ @staticmethod
+ def getFileObject(filePath, fileName=None, zope=False):
+ '''Returns a File instance as can be stored in the database or
+ manipulated in code, filled with content from a file on disk,
+ located at p_filePath. If you want to give it a name that is more
+ sexy than the actual basename of filePath, specify it in
+ p_fileName.
+
+ If p_zope is True, it will be the raw Zope object = an instance of
+ OFS.Image.File. Else, it will be a FileWrapper instance from Appy.'''
+ f = file(filePath, 'rb')
+ if not fileName:
+ fileName = os.path.basename(filePath)
+ fileId = 'file.%f' % time.time()
+ import OFS.Image
+ res = OFS.Image.File(fileId, fileName, f)
+ res.filename = fileName
+ res.content_type = mimetypes.guess_type(fileName)[0]
+ f.close()
+ if not zope: res = FileWrapper(res)
+ return res
+
def getValue(self, obj):
value = Type.getValue(self, obj)
if value: value = FileWrapper(value)
@@ -1505,14 +1529,7 @@ class File(Type):
elif isinstance(value, FileWrapper):
setattr(obj, self.name, value._atFile)
elif isinstance(value, basestring):
- f = file(value)
- fileName = os.path.basename(value)
- fileId = 'file.%f' % time.time()
- zopeFile = OFSImageFile(fileId, fileName, f)
- zopeFile.filename = fileName
- zopeFile.content_type = mimetypes.guess_type(fileName)[0]
- setattr(obj, self.name, zopeFile)
- f.close()
+ setattr(obj, self.name, File.getFileObject(value, zope=True))
elif type(value) in sequenceTypes:
# It should be a 2-tuple or 3-tuple
fileName = None
@@ -1913,6 +1930,9 @@ class Pod(Type):
'''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document
from data contained in Appy class and linked objects or anything you
want to put in it. It uses appy.pod.'''
+ POD_ERROR = 'An error occurred while generating the document. Please ' \
+ 'contact the system administrator.'
+ DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
def __init__(self, validator=None, index=None, default=None,
optional=False, editDefault=False, show='view',
page='main', group=None, layouts=None, move=0, indexed=False,
@@ -1920,7 +1940,7 @@ class Pod(Type):
specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False,
historized=False, template=None, context=None, action=None,
- askAction=False, stylesMapping=None):
+ askAction=False, stylesMapping={}, freezeFormat='pdf'):
# The following param stores the path to a POD template
self.template = template
# The context is a dict containing a specific pod context, or a method
@@ -1934,6 +1954,8 @@ class Pod(Type):
self.askAction = askAction
# A global styles mapping that would apply to the whole template
self.stylesMapping = stylesMapping
+ # Freeze format is by PDF by default
+ self.freezeFormat = freezeFormat
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
@@ -1941,6 +1963,135 @@ class Pod(Type):
masterValue, focus, historized, False)
self.validable = False
+ def isFrozen(self, obj):
+ '''Is there a frozen document for p_self on p_obj?'''
+ value = getattr(obj.o, self.name, None)
+ return isinstance(value, obj.o.getProductConfig().File)
+
+ def getToolInfo(self, obj):
+ '''Gets information related to this field (p_self) that is available in
+ the tool: the POD template and the available output formats. If this
+ field is frozen, available output formats are not available anymore:
+ only the format of the frozen doc is returned.'''
+ tool = obj.tool
+ appyClass = tool.o.getAppyClass(obj.o.meta_type)
+ # Get the output format(s)
+ if self.isFrozen(obj):
+ # The only available format is the one from the frozen document
+ fileName = getattr(obj.o, self.name).filename
+ formats = (os.path.splitext(fileName)[1][1:],)
+ else:
+ # Available formats are those which are selected in the tool.
+ name = tool.getAttributeName('formats', appyClass, self.name)
+ formats = getattr(tool, name)
+ # Get the POD template
+ name = tool.getAttributeName('podTemplate', appyClass, self.name)
+ template = getattr(tool, name)
+ return (template, formats)
+
+ def getValue(self, obj):
+ '''Gets, on_obj, the value conforming to self's type definition. For a
+ Pod field, if a file is stored in the field, it means that the
+ field has been frozen. Else, it means that the value must be
+ retrieved by calling pod to compute the result.'''
+ rq = getattr(obj, 'REQUEST', None)
+ res = getattr(obj, self.name, None)
+ if res and res.size:
+ print 'RETURNING FROZEN DOC'
+ return FileWrapper(res) # Return the frozen file.
+ # If we are here, it means that we must call pod to compute the file.
+ # A Pod field differs from other field types because there can be
+ # several ways to produce the field value (ie: output file format can be
+ # odt, pdf,...; self.action can be executed or not...). We get those
+ # precisions about the way to produce the file from the request object
+ # and from the tool. If we don't find the request object (or if it does
+ # not exist, ie, when Zope runs in test mode), we use default values.
+ obj = obj.appy()
+ tool = obj.tool
+ # Get POD template and available formats from the tool.
+ template, availFormats = self.getToolInfo(obj)
+ # Get the output format
+ defaultFormat = 'pdf'
+ if defaultFormat not in availFormats: defaultFormat = availFormats[0]
+ outputFormat = getattr(rq, 'podFormat', defaultFormat)
+ # Get or compute the specific POD context
+ specificContext = None
+ if callable(self.context):
+ specificContext = self.callMethod(obj, self.context)
+ else:
+ specificContext = self.context
+ # Temporary file where to generate the result
+ tempFileName = '%s/%s_%f.%s' % (
+ getOsTempFolder(), obj.uid, time.time(), outputFormat)
+ # Define parameters to give to the appy.pod renderer
+ podContext = {'tool': tool, 'user': obj.user, 'self': obj,
+ 'now': obj.o.getProductConfig().DateTime(),
+ 'projectFolder': tool.getDiskFolder()}
+ # If the POD document is related to a query, get it from the request,
+ # execute it and put the result in the context.
+ isQueryRelated = rq.get('queryData', None)
+ if isQueryRelated:
+ # Retrieve query params from the request
+ cmd = ', '.join(tool.o.queryParamNames)
+ cmd += " = rq['queryData'].split(';')"
+ exec cmd
+ # (re-)execute the query, but without any limit on the number of
+ # results; return Appy objects.
+ objs = tool.o.executeQuery(type_name, searchName=search,
+ sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
+ filterValue=filterValue, maxResults='NO_LIMIT')
+ podContext['objects'] = [o.appy() for o in objs['objects']]
+ if specificContext:
+ podContext.update(specificContext)
+ # Define a potential global styles mapping
+ if callable(self.stylesMapping):
+ stylesMapping = self.callMethod(obj, self.stylesMapping)
+ else:
+ stylesMapping = self.stylesMapping
+ rendererParams = {'template': StringIO.StringIO(template.content),
+ 'context': podContext, 'result': tempFileName,
+ 'stylesMapping': stylesMapping}
+ if tool.unoEnabledPython:
+ rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
+ if tool.openOfficePort:
+ rendererParams['ooPort'] = tool.openOfficePort
+ # Launch the renderer
+ try:
+ renderer = Renderer(**rendererParams)
+ renderer.run()
+ except appy.pod.PodError, pe:
+ if not os.path.exists(tempFileName):
+ # In some (most?) cases, when OO returns an error, the result is
+ # nevertheless generated.
+ obj.log(str(pe), type='error')
+ return Pod.POD_ERROR
+ # Give a friendly name for this file
+ fileName = obj.translate(self.labelId)
+ if not isQueryRelated:
+ # This is a POD for a single object: personalize the file name with
+ # the object title.
+ fileName = '%s-%s' % (obj.title, fileName)
+ fileName = tool.normalize(fileName) + '.' + outputFormat
+ # Get a FileWrapper instance from the temp file on the filesystem
+ res = File.getFileObject(tempFileName, fileName)
+ # Execute the related action if relevant
+ doAction = getattr(rq, 'askAction', False) in ('True', True)
+ if doAction and self.action: self.action(obj, podContext)
+ # Returns the doc and removes the temp file
+ try:
+ os.remove(tempFileName)
+ except OSError, oe:
+ obj.log(Pod.DELETE_TEMP_DOC_ERROR % str(oe), type='warning')
+ except IOError, ie:
+ obj.log(Pod.DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
+ return res
+
+ def store(self, obj, value):
+ '''Stores (=freezes) a document (in p_value) in the field.'''
+ if isinstance(value, FileWrapper):
+ value = value._atFile
+ setattr(obj, self.name, value)
+
# Workflow-specific types ------------------------------------------------------
class Role:
'''Represents a role.'''
diff --git a/gen/plone25/mixins/ToolMixin.py b/gen/plone25/mixins/ToolMixin.py
index cb91915..6aa02bf 100644
--- a/gen/plone25/mixins/ToolMixin.py
+++ b/gen/plone25/mixins/ToolMixin.py
@@ -1,9 +1,7 @@
# ------------------------------------------------------------------------------
-import re, os, os.path, time, Cookie, StringIO, types
+import re, os, os.path, time, Cookie, types
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder
-import appy.pod
-from appy.pod.renderer import Renderer
import appy.gen
from appy.gen import Type, Search, Selection
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
@@ -12,9 +10,6 @@ from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.descriptors import ClassDescriptor
# Errors -----------------------------------------------------------------------
-DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
-POD_ERROR = 'An error occurred while generating the document. Please ' \
- 'contact the system administrator.'
jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------
@@ -32,127 +27,34 @@ class ToolMixin(BaseMixin):
res = '%s%s' % (elems[1], elems[4])
return res
- def getPodInfo(self, ploneObj, fieldName):
- '''Returns POD-related information about Pod field p_fieldName defined
- on class whose p_ploneObj is an instance of.'''
- res = {}
- appyClass = self.getAppyClass(ploneObj.meta_type)
- appyTool = self.appy()
- n = appyTool.getAttributeName('formats', appyClass, fieldName)
- res['formats'] = getattr(appyTool, n)
- n = appyTool.getAttributeName('podTemplate', appyClass, fieldName)
- res['template'] = getattr(appyTool, n)
- appyType = ploneObj.getAppyType(fieldName)
- res['title'] = self.translate(appyType.labelId)
- res['context'] = appyType.context
- res['action'] = appyType.action
- res['stylesMapping'] = appyType.stylesMapping
- return res
-
def getSiteUrl(self):
'''Returns the absolute URL of this site.'''
return self.portal_url.getPortalObject().absolute_url()
+ def getPodInfo(self, obj, name):
+ '''Gets the available POD formats for Pod field named p_name on
+ p_obj.'''
+ podField = self.getAppyType(name, className=obj.meta_type)
+ return podField.getToolInfo(obj.appy())
+
def generateDocument(self):
'''Generates the document from field-related info. UID of object that
is the template target is given in the request.'''
rq = self.REQUEST
- appyTool = self.appy()
- # Get the object
- objectUid = rq.get('objectUid')
- obj = self.uid_catalog(UID=objectUid)[0].getObject()
- appyObj = obj.appy()
- # Get information about the document to render.
- specificPodContext = None
+ # Get the object on which a document must be generated.
+ obj = self.getObject(rq.get('objectUid'), appy=True)
fieldName = rq.get('fieldName')
- format = rq.get('podFormat')
- podInfo = self.getPodInfo(obj, fieldName)
- template = podInfo['template'].content
- podTitle = podInfo['title']
- if podInfo['context']:
- if callable(podInfo['context']):
- specificPodContext = podInfo['context'](appyObj)
- else:
- specificPodContext = podInfo['context']
- doAction = rq.get('askAction') == 'True'
- # Temporary file where to generate the result
- tempFileName = '%s/%s_%f.%s' % (
- getOsTempFolder(), obj.UID(), time.time(), format)
- # Define parameters to give to the appy.pod renderer
- currentUser = self.portal_membership.getAuthenticatedMember()
- podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj,
- 'now': self.getProductConfig().DateTime(),
- 'projectFolder': appyTool.getDiskFolder(),
- }
- # If the POD document is related to a query, get it from the request,
- # execute it and put the result in the context.
- if rq['queryData']:
- # Retrieve query params from the request
- cmd = ', '.join(self.queryParamNames)
- cmd += " = rq['queryData'].split(';')"
- exec cmd
- # (re-)execute the query, but without any limit on the number of
- # results; return Appy objects.
- objs = self.executeQuery(type_name, searchName=search,
- sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
- filterValue=filterValue, maxResults='NO_LIMIT')
- podContext['objects'] = [o.appy() for o in objs['objects']]
- if specificPodContext:
- podContext.update(specificPodContext)
- # Define a potential global styles mapping
- stylesMapping = None
- if podInfo['stylesMapping']:
- if callable(podInfo['stylesMapping']):
- stylesMapping = podInfo['stylesMapping'](appyObj)
- else:
- stylesMapping = podInfo['stylesMapping']
- rendererParams = {'template': StringIO.StringIO(template),
- 'context': podContext, 'result': tempFileName}
- if stylesMapping:
- rendererParams['stylesMapping'] = stylesMapping
- if appyTool.unoEnabledPython:
- rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
- if appyTool.openOfficePort:
- rendererParams['ooPort'] = appyTool.openOfficePort
- # Launch the renderer
- try:
- renderer = Renderer(**rendererParams)
- renderer.run()
- except appy.pod.PodError, pe:
- if not os.path.exists(tempFileName):
- # In some (most?) cases, when OO returns an error, the result is
- # nevertheless generated.
- appyTool.log(str(pe), type='error')
- appyTool.say(POD_ERROR)
- return self.goto(rq.get('HTTP_REFERER'))
- # Open the temp file on the filesystem
- f = file(tempFileName, 'rb')
- res = f.read()
- # Identify the filename to return
- if rq['queryData']:
- # This is a POD for a bunch of objects
- fileName = podTitle
- else:
- # This is a POD for a single object: personalize the file name with
- # the object title.
- fileName = '%s-%s' % (obj.Title(), podTitle)
- fileName = appyTool.normalize(fileName)
- response = obj.REQUEST.RESPONSE
- response.setHeader('Content-Type', mimeTypes[format])
- response.setHeader('Content-Disposition', 'inline;filename="%s.%s"' % \
- (fileName, format))
- f.close()
- # Execute the related action if relevant
- if doAction and podInfo['action']:
- podInfo['action'](appyObj, podContext)
- # Returns the doc and removes the temp file
- try:
- os.remove(tempFileName)
- except OSError, oe:
- appyTool.log(DELETE_TEMP_DOC_ERROR % str(oe), type='warning')
- except IOError, ie:
- appyTool.log(DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
- return res
+ res = getattr(obj, fieldName)
+ if isinstance(res, basestring):
+ # An error has occurred, and p_res contains the error message
+ obj.say(res)
+ return self.goto(rq.get('HTTP_REFERER'))
+ # res contains a FileWrapper instance.
+ response = rq.RESPONSE
+ response.setHeader('Content-Type', res.mimeType)
+ response.setHeader('Content-Disposition',
+ 'inline;filename="%s"' % res.name)
+ return res.content
def getAttr(self, name):
'''Gets attribute named p_attrName. Useful because we can't use getattr
diff --git a/gen/plone25/skin/widgets/pod.pt b/gen/plone25/skin/widgets/pod.pt
index a5b42f1..f09db67 100644
--- a/gen/plone25/skin/widgets/pod.pt
+++ b/gen/plone25/skin/widgets/pod.pt
@@ -7,7 +7,7 @@
-