2013-07-08 16:39:16 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# This file is part of Appy, a framework for building applications in the Python
|
|
|
|
# language. Copyright (C) 2007 Gaetan Delannay
|
|
|
|
|
|
|
|
# Appy is free software; you can redistribute it and/or modify it under the
|
|
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
|
|
# version.
|
|
|
|
|
|
|
|
# Appy is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
|
|
# Appy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2013-09-21 10:46:42 -05:00
|
|
|
import time, os, os.path
|
2014-03-19 17:13:31 -05:00
|
|
|
from file import FileInfo
|
|
|
|
from appy import Object
|
2013-07-08 16:39:16 -05:00
|
|
|
from appy.fields import Field
|
|
|
|
from appy.px import Px
|
|
|
|
from appy.gen.layout import Table
|
2014-03-19 17:13:31 -05:00
|
|
|
from appy.gen import utils as gutils
|
2013-07-08 16:39:16 -05:00
|
|
|
from appy.pod import PodError
|
|
|
|
from appy.pod.renderer import Renderer
|
|
|
|
from appy.shared import utils as sutils
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class Pod(Field):
|
|
|
|
'''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
|
2013-07-10 02:56:35 -05:00
|
|
|
want to put in it. It is the way gen uses pod.'''
|
2013-07-08 16:39:16 -05:00
|
|
|
# Layout for rendering a POD field for exporting query results.
|
|
|
|
rLayouts = {'view': Table('fl', width=None)}
|
|
|
|
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.'
|
2014-03-19 17:13:31 -05:00
|
|
|
NO_TEMPLATE = 'Please specify a pod template in field "template".'
|
|
|
|
UNAVAILABLE_TEMPLATE = 'You are not allow to perform this action.'
|
|
|
|
TEMPLATE_NOT_FOUND = 'Template not found at %s.'
|
2013-07-08 16:39:16 -05:00
|
|
|
|
2014-02-27 05:54:05 -06:00
|
|
|
pxView = pxCell = Px('''
|
2014-03-19 17:13:31 -05:00
|
|
|
<table cellpadding="0" cellspacing="0">
|
|
|
|
<tr>
|
|
|
|
<td for="template in field.getVisibleTemplates(obj)">
|
|
|
|
<table cellpadding="0" cellspacing="0" class="podTable">
|
|
|
|
<tr>
|
|
|
|
<td for="fmt in field.getOutputFormats(obj)">
|
|
|
|
<img src=":url(fmt)" title=":fmt.upper()" class="clickable"
|
|
|
|
onclick=":'generatePodDocument(%s,%s,%s,%s,%s)' % \
|
|
|
|
(q(obj.uid), q(name), q(template), q(fmt), \
|
|
|
|
q(ztool.getQueryInfo()))"/>
|
|
|
|
</td>
|
|
|
|
<td class="podName">:field.getTemplateName(obj, template)</td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>''')
|
2013-07-08 16:39:16 -05:00
|
|
|
|
|
|
|
pxEdit = pxSearch = ''
|
|
|
|
|
|
|
|
def __init__(self, validator=None, default=None, show=('view', 'result'),
|
|
|
|
page='main', group=None, layouts=None, move=0, indexed=False,
|
|
|
|
searchable=False, specificReadPermission=False,
|
|
|
|
specificWritePermission=False, width=None, height=None,
|
|
|
|
maxChars=None, colspan=1, master=None, masterValue=None,
|
|
|
|
focus=False, historized=False, mapping=None, label=None,
|
2014-03-19 17:13:31 -05:00
|
|
|
template=None, templateName=None, showTemplate=None,
|
|
|
|
context=None, stylesMapping={}, formats=None,
|
|
|
|
freezeFormat='pdf'):
|
|
|
|
# Param "template" stores the path to the pod template(s).
|
|
|
|
if not template: raise Exception(Pod.NO_TEMPLATE)
|
|
|
|
if isinstance(template, basestring):
|
|
|
|
self.template = [template]
|
|
|
|
else:
|
|
|
|
self.template = template
|
|
|
|
# Param "templateName", if specified, is a method that will be called
|
|
|
|
# with the current template (from self.template) as single arg and must
|
|
|
|
# return the name of this template. If self.template stores a single
|
|
|
|
# template, you have no need to use param "templateName". Simply use the
|
|
|
|
# field label to name the template. But if you have a multi-pod field
|
|
|
|
# (with several templates specified as a list or tuple in param
|
|
|
|
# "template"), you will probably choose to hide the field label and use
|
|
|
|
# param "templateName" to give a specific name to every template. If
|
|
|
|
# "template" contains several templates and "templateName" is None, Appy
|
|
|
|
# will produce names from template filenames.
|
|
|
|
self.templateName = templateName
|
|
|
|
# "showTemplate", if specified, must be a method that will be called
|
|
|
|
# with the current template as single arg and that must return True if
|
|
|
|
# the template can be seen by the current user. "showTemplate" comes in
|
|
|
|
# addition to self.show. self.show dictates the visibility of the whole
|
|
|
|
# field (ie, all templates from self.template) while "showTemplate"
|
|
|
|
# dictates the visiblity of a specific template within self.template.
|
|
|
|
self.showTemplate = showTemplate
|
2013-07-08 16:39:16 -05:00
|
|
|
# The context is a dict containing a specific pod context, or a method
|
|
|
|
# that returns such a dict.
|
|
|
|
self.context = context
|
|
|
|
# A global styles mapping that would apply to the whole template
|
|
|
|
self.stylesMapping = stylesMapping
|
2013-09-21 10:46:42 -05:00
|
|
|
# What are the output formats when generating documents from this pod ?
|
2014-03-11 09:29:59 -05:00
|
|
|
self.formats = formats
|
2013-09-21 10:46:42 -05:00
|
|
|
if not formats:
|
|
|
|
# Compute default ones
|
2014-03-19 17:13:31 -05:00
|
|
|
if self.template[0].endswith('.ods'):
|
2013-09-21 10:46:42 -05:00
|
|
|
self.formats = ('xls', 'ods')
|
|
|
|
else:
|
|
|
|
self.formats = ('pdf', 'doc', 'odt')
|
|
|
|
# Freeze format is PDF by default.
|
2013-07-08 16:39:16 -05:00
|
|
|
self.freezeFormat = freezeFormat
|
|
|
|
Field.__init__(self, None, (0,1), default, show, page, group, layouts,
|
|
|
|
move, indexed, searchable, specificReadPermission,
|
|
|
|
specificWritePermission, width, height, None, colspan,
|
2014-03-05 09:19:11 -06:00
|
|
|
master, masterValue, focus, historized, mapping, label,
|
|
|
|
None, None, None, None, True)
|
2014-03-04 08:03:37 -06:00
|
|
|
# Param "persist" is set to True but actually, persistence for a pod
|
|
|
|
# field is determined by freezing.
|
2013-07-08 16:39:16 -05:00
|
|
|
self.validable = False
|
|
|
|
|
|
|
|
def isFrozen(self, obj):
|
|
|
|
'''Is there a frozen document for p_self on p_obj?'''
|
|
|
|
value = getattr(obj.o.aq_base, self.name, None)
|
|
|
|
return isinstance(value, obj.o.getProductConfig().File)
|
|
|
|
|
2013-09-21 10:46:42 -05:00
|
|
|
def getOutputFormats(self, obj):
|
|
|
|
'''Returns self.formats, excepted if there is a frozen document: in
|
|
|
|
this case, only the format of the frozen doc is returned.'''
|
|
|
|
if not self.isFrozen(obj): return self.formats
|
|
|
|
# The only available format is the one from the frozen document
|
|
|
|
fileName = getattr(obj.o.aq_base, self.name).filename
|
|
|
|
return (os.path.splitext(fileName)[1][1:],)
|
2013-07-08 16:39:16 -05:00
|
|
|
|
2014-03-19 17:13:31 -05:00
|
|
|
def getTemplateName(self, obj, fileName):
|
|
|
|
'''Gets the name of a template given its p_fileName.'''
|
|
|
|
res = None
|
|
|
|
if self.templateName:
|
|
|
|
# Use the method specified in self.templateName.
|
|
|
|
res = self.templateName(obj, fileName)
|
|
|
|
# Else, deduce a nice name from p_fileName.
|
|
|
|
if not res:
|
|
|
|
name = os.path.splitext(os.path.basename(fileName))[0]
|
|
|
|
res = gutils.produceNiceMessage(name)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def getVisibleTemplates(self, obj):
|
|
|
|
'''Returns, among self.template, the template(s) that can be shown.'''
|
|
|
|
if not self.showTemplate: return self.template # Show them all.
|
|
|
|
res = []
|
|
|
|
for template in self.template:
|
|
|
|
if self.showTemplate(obj, template):
|
|
|
|
res.append(template)
|
|
|
|
return res
|
|
|
|
|
2013-07-08 16:39:16 -05:00
|
|
|
def getValue(self, obj):
|
2014-03-19 17:13:31 -05:00
|
|
|
'''For a pod field, getting its value means computing a pod document or
|
|
|
|
returning a frozen one. A pod field differs from other field types
|
|
|
|
because there can be several ways to produce the field value (ie:
|
|
|
|
self.template can hold various templates; output file format can be
|
|
|
|
odt, pdf,.... We get those precisions about the way to produce the
|
|
|
|
file from the request object. 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.'''
|
|
|
|
rq = getattr(obj, 'REQUEST') or Object()
|
2013-07-08 16:39:16 -05:00
|
|
|
obj = obj.appy()
|
2014-03-19 17:13:31 -05:00
|
|
|
template = rq.get('template') or self.template[0]
|
|
|
|
# Security check.
|
|
|
|
if not self.showTemplate(obj, template):
|
|
|
|
raise Exception(self.UNAVAILABLE_TEMPLATE)
|
|
|
|
# Return the frozen document if frozen.
|
|
|
|
# if ...
|
|
|
|
# We must call pod to compute a pod document from "template".
|
2013-07-08 16:39:16 -05:00
|
|
|
tool = obj.tool
|
2013-09-21 10:46:42 -05:00
|
|
|
diskFolder = tool.getDiskFolder()
|
|
|
|
# Get the path to the pod template.
|
2014-03-19 17:13:31 -05:00
|
|
|
templatePath = os.path.join(diskFolder, template)
|
2013-09-21 10:46:42 -05:00
|
|
|
if not os.path.isfile(templatePath):
|
2014-03-19 17:13:31 -05:00
|
|
|
raise Exception(self.TEMPLATE_NOT_FOUND % templatePath)
|
2013-07-08 16:39:16 -05:00
|
|
|
# Get the output format
|
2014-03-19 17:13:31 -05:00
|
|
|
outputFormat = rq.get('podFormat', 'odt')
|
2013-07-08 16:39:16 -05:00
|
|
|
# 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' % (
|
|
|
|
sutils.getOsTempFolder(), obj.uid, time.time(), outputFormat)
|
|
|
|
# Define parameters to give to the appy.pod renderer
|
|
|
|
podContext = {'tool': tool, 'user': obj.user, 'self': obj, 'field':self,
|
|
|
|
'now': obj.o.getProductConfig().DateTime(),
|
2013-09-21 10:46:42 -05:00
|
|
|
'_': obj.translate, 'projectFolder': diskFolder}
|
2013-07-08 16:39:16 -05:00
|
|
|
# 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(obj.o.portal_type, searchName=search,
|
|
|
|
sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
|
|
|
|
filterValue=filterValue, maxResults='NO_LIMIT')
|
2013-08-21 15:25:27 -05:00
|
|
|
podContext['objects'] = [o.appy() for o in objs.objects]
|
2013-07-08 16:39:16 -05:00
|
|
|
# Add the field-specific context if present.
|
|
|
|
if specificContext:
|
|
|
|
podContext.update(specificContext)
|
|
|
|
# If a custom param comes from the request, add it to the context. A
|
|
|
|
# custom param must have format "name:value". Custom params override any
|
|
|
|
# other value in the request, including values from the field-specific
|
|
|
|
# context.
|
|
|
|
customParams = rq.get('customParams', None)
|
|
|
|
if customParams:
|
|
|
|
paramsDict = eval(customParams)
|
|
|
|
podContext.update(paramsDict)
|
|
|
|
# Define a potential global styles mapping
|
|
|
|
if callable(self.stylesMapping):
|
|
|
|
stylesMapping = self.callMethod(obj, self.stylesMapping)
|
|
|
|
else:
|
|
|
|
stylesMapping = self.stylesMapping
|
2013-09-21 10:46:42 -05:00
|
|
|
rendererParams = {'template': templatePath, 'context': podContext,
|
|
|
|
'result': tempFileName, 'stylesMapping':stylesMapping,
|
2013-07-08 16:39:16 -05:00
|
|
|
'imageResolver': tool.o.getApp()}
|
|
|
|
if tool.unoEnabledPython:
|
|
|
|
rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
|
|
|
|
if tool.openOfficePort:
|
|
|
|
rendererParams['ooPort'] = tool.openOfficePort
|
|
|
|
# Launch the renderer
|
|
|
|
try:
|
|
|
|
renderer = Renderer(**rendererParams)
|
|
|
|
renderer.run()
|
|
|
|
except PodError, pe:
|
|
|
|
if not os.path.exists(tempFileName):
|
|
|
|
# In some (most?) cases, when OO returns an error, the result is
|
|
|
|
# nevertheless generated.
|
2013-09-21 10:46:42 -05:00
|
|
|
obj.log(str(pe).strip(), type='error')
|
2013-07-08 16:39:16 -05:00
|
|
|
return Pod.POD_ERROR
|
|
|
|
# Give a friendly name for this file
|
2014-03-19 17:13:31 -05:00
|
|
|
fileName = self.getTemplateName(obj, template)
|
2013-07-08 16:39:16 -05:00
|
|
|
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
|
2014-03-19 17:13:31 -05:00
|
|
|
# Get a FileInfo instance to manipulate the temp file on the filesystem.
|
|
|
|
return FileInfo(tempFileName, inDb=False, uploadName=fileName)
|
|
|
|
|
2013-07-08 16:39:16 -05:00
|
|
|
# Returns the doc and removes the temp file
|
|
|
|
try:
|
|
|
|
os.remove(tempFileName)
|
2014-03-19 17:13:31 -05:00
|
|
|
except Exception, e:
|
|
|
|
obj.log(Pod.DELETE_TEMP_DOC_ERROR % str(e).strip(), type='warning')
|
2013-07-08 16:39:16 -05:00
|
|
|
return res
|
|
|
|
|
|
|
|
def store(self, obj, value):
|
|
|
|
'''Stores (=freezes) a document (in p_value) in the field.'''
|
|
|
|
if isinstance(value, sutils.FileWrapper):
|
|
|
|
value = value._zopeFile
|
|
|
|
setattr(obj, self.name, value)
|
|
|
|
# ------------------------------------------------------------------------------
|