[gen] Pod field can now freeze and unfreeze any of its multiple templates.

This commit is contained in:
Gaetan Delannay 2014-03-21 16:50:48 +01:00
parent ecc3a8c39b
commit 0834356487
3 changed files with 111 additions and 86 deletions

View file

@ -33,12 +33,15 @@ class Pod(Field):
want to put in it. It is the way gen uses pod.''' want to put in it. It is the way gen uses pod.'''
# Layout for rendering a POD field for exporting query results. # Layout for rendering a POD field for exporting query results.
rLayouts = {'view': Table('fl', width=None)} rLayouts = {'view': Table('fl', width=None)}
allFormats = ('pdf', 'doc', 'odt')
POD_ERROR = 'An error occurred while generating the document. Please ' \ POD_ERROR = 'An error occurred while generating the document. Please ' \
'contact the system administrator.' 'contact the system administrator.'
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
NO_TEMPLATE = 'Please specify a pod template in field "template".' NO_TEMPLATE = 'Please specify a pod template in field "template".'
UNAVAILABLE_TEMPLATE = 'You are not allow to perform this action.' UNAVAILABLE_TEMPLATE = 'You are not allow to perform this action.'
TEMPLATE_NOT_FOUND = 'Template not found at %s.' TEMPLATE_NOT_FOUND = 'Template not found at %s.'
FREEZE_ERROR = 'Error while trying to freeze a "%s" file in pod field ' \
'"%s" (%s).'
FREEZE_FATAL_ERROR = 'Server error. Please contact the administrator.'
pxView = pxCell = Px(''' pxView = pxCell = Px('''
<table cellpadding="0" cellspacing="0"> <table cellpadding="0" cellspacing="0">
@ -68,8 +71,7 @@ class Pod(Field):
maxChars=None, colspan=1, master=None, masterValue=None, maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
template=None, templateName=None, showTemplate=None, template=None, templateName=None, showTemplate=None,
context=None, stylesMapping={}, formats=None, context=None, stylesMapping={}, formats=None):
freezeFormat='pdf'):
# Param "template" stores the path to the pod template(s). # Param "template" stores the path to the pod template(s).
if not template: raise Exception(Pod.NO_TEMPLATE) if not template: raise Exception(Pod.NO_TEMPLATE)
if isinstance(template, basestring): if isinstance(template, basestring):
@ -107,8 +109,6 @@ class Pod(Field):
self.formats = ('xls', 'ods') self.formats = ('xls', 'ods')
else: else:
self.formats = ('pdf', 'doc', 'odt') self.formats = ('pdf', 'doc', 'odt')
# Freeze format is PDF by default.
self.freezeFormat = freezeFormat
Field.__init__(self, None, (0,1), default, show, page, group, layouts, Field.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, searchable, specificReadPermission, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
@ -118,18 +118,12 @@ class Pod(Field):
# field is determined by freezing. # field is determined by freezing.
self.validable = False 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)
def getOutputFormats(self, obj): def getOutputFormats(self, obj):
'''Returns self.formats, excepted if there is a frozen document: in '''Returns self.formats, excepted if there is a frozen document: in
this case, only the format of the frozen doc is returned.''' this case, only the format of the frozen doc is returned.'''
if not self.isFrozen(obj): return self.formats if not obj.user.has_role('Manager'): return self.formats
# The only available format is the one from the frozen document # A manager can have all formats
fileName = getattr(obj.o.aq_base, self.name).filename return self.allFormats
return (os.path.splitext(fileName)[1][1:],)
def getTemplateName(self, obj, fileName): def getTemplateName(self, obj, fileName):
'''Gets the name of a template given its p_fileName.''' '''Gets the name of a template given its p_fileName.'''
@ -143,6 +137,16 @@ class Pod(Field):
res = gutils.produceNiceMessage(name) res = gutils.produceNiceMessage(name)
return res return res
def getDownloadName(self, obj, template, format, queryRelated):
'''Gets the name of the pod result as will be seen by the user that will
download it.'''
fileName = self.getTemplateName(obj, template)
if not queryRelated:
# This is a POD for a single object: personalize the file name with
# the object title.
fileName = '%s-%s' % (obj.title, fileName)
return obj.tool.normalize(fileName) + '.' + format
def getVisibleTemplates(self, obj): def getVisibleTemplates(self, obj):
'''Returns, among self.template, the template(s) that can be shown.''' '''Returns, among self.template, the template(s) that can be shown.'''
if not self.showTemplate: return self.template # Show them all. if not self.showTemplate: return self.template # Show them all.
@ -152,23 +156,30 @@ class Pod(Field):
res.append(template) res.append(template)
return res return res
def getValue(self, obj): def getValue(self, obj, template=None, format=None, result=None):
'''For a pod field, getting its value means computing a pod document or '''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 returning a frozen one. A pod field differs from other field types
because there can be several ways to produce the field value (ie: because there can be several ways to produce the field value (ie:
self.template can hold various templates; output file format can be self.template can hold various templates; output file format can be
odt, pdf,.... We get those precisions about the way to produce the 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 file, either:
if it does not exist, ie, when Zope runs in test mode), we use - from params p_template and p_format;
default values.''' - from the request object;
rq = getattr(obj, 'REQUEST') or Object() - from default values (the request object may not be present, ie,
when Zope runs in test mode).'''
obj = obj.appy() obj = obj.appy()
template = rq.get('template') or self.template[0] rq = obj.request
template = template or rq.get('template') or self.template[0]
format = format or rq.get('podFormat') or 'odt'
# Security check. # Security check.
if not self.showTemplate(obj, template): if not self.showTemplate(obj, template):
raise Exception(self.UNAVAILABLE_TEMPLATE) raise Exception(self.UNAVAILABLE_TEMPLATE)
# Return the frozen document if frozen. # Return the frozen document if frozen.
# if ... frozen = self.isFrozen(obj, template, format)
if frozen:
print 'RETURN FROZEN'
fileName = self.getDownloadName(obj, template, format, False)
return FileInfo(frozen, inDb=False, uploadName=fileName)
# We must call pod to compute a pod document from "template". # We must call pod to compute a pod document from "template".
tool = obj.tool tool = obj.tool
diskFolder = tool.getDiskFolder() diskFolder = tool.getDiskFolder()
@ -176,17 +187,16 @@ class Pod(Field):
templatePath = os.path.join(diskFolder, template) templatePath = os.path.join(diskFolder, template)
if not os.path.isfile(templatePath): if not os.path.isfile(templatePath):
raise Exception(self.TEMPLATE_NOT_FOUND % templatePath) raise Exception(self.TEMPLATE_NOT_FOUND % templatePath)
# Get the output format
outputFormat = rq.get('podFormat', 'odt')
# Get or compute the specific POD context # Get or compute the specific POD context
specificContext = None specificContext = None
if callable(self.context): if callable(self.context):
specificContext = self.callMethod(obj, self.context) specificContext = self.callMethod(obj, self.context)
else: else:
specificContext = self.context specificContext = self.context
# Temporary file where to generate the result # Compute the name of the result file.
tempFileName = '%s/%s_%f.%s' % ( if not result:
sutils.getOsTempFolder(), obj.uid, time.time(), outputFormat) result = '%s/%s_%f.%s' % (sutils.getOsTempFolder(),
obj.uid, time.time(), format)
# Define parameters to give to the appy.pod renderer # Define parameters to give to the appy.pod renderer
podContext = {'tool': tool, 'user': obj.user, 'self': obj, 'field':self, podContext = {'tool': tool, 'user': obj.user, 'self': obj, 'field':self,
'now': obj.o.getProductConfig().DateTime(), 'now': obj.o.getProductConfig().DateTime(),
@ -209,7 +219,7 @@ class Pod(Field):
if specificContext: if specificContext:
podContext.update(specificContext) podContext.update(specificContext)
# If a custom param comes from the request, add it to the context. A # 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 # custom param must have form "name:value". Custom params override any
# other value in the request, including values from the field-specific # other value in the request, including values from the field-specific
# context. # context.
customParams = rq.get('customParams', None) customParams = rq.get('customParams', None)
@ -222,8 +232,9 @@ class Pod(Field):
else: else:
stylesMapping = self.stylesMapping stylesMapping = self.stylesMapping
rendererParams = {'template': templatePath, 'context': podContext, rendererParams = {'template': templatePath, 'context': podContext,
'result': tempFileName, 'stylesMapping':stylesMapping, 'result': result, 'stylesMapping': stylesMapping,
'imageResolver': tool.o.getApp()} 'imageResolver': tool.o.getApp(),
'overwriteExisting': True}
if tool.unoEnabledPython: if tool.unoEnabledPython:
rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
if tool.openOfficePort: if tool.openOfficePort:
@ -233,31 +244,71 @@ class Pod(Field):
renderer = Renderer(**rendererParams) renderer = Renderer(**rendererParams)
renderer.run() renderer.run()
except PodError, pe: except PodError, pe:
if not os.path.exists(tempFileName): if not os.path.exists(result):
# In some (most?) cases, when OO returns an error, the result is # In some (most?) cases, when OO returns an error, the result is
# nevertheless generated. # nevertheless generated.
obj.log(str(pe).strip(), type='error') obj.log(str(pe).strip(), type='error')
return Pod.POD_ERROR return Pod.POD_ERROR
# Give a friendly name for this file # Give a friendly name for this file
fileName = self.getTemplateName(obj, template) fileName = self.getDownloadName(obj, template, format, isQueryRelated)
if not isQueryRelated: # Get a FileInfo instance to manipulate the file on the filesystem.
# This is a POD for a single object: personalize the file name with return FileInfo(result, inDb=False, uploadName=fileName)
# the object title.
fileName = '%s-%s' % (obj.title, fileName)
fileName = tool.normalize(fileName) + '.' + outputFormat
# Get a FileInfo instance to manipulate the temp file on the filesystem.
return FileInfo(tempFileName, inDb=False, uploadName=fileName)
# Returns the doc and removes the temp file def getFreezeName(self, template=None, format='pdf'):
try: '''Gets the name on disk on the frozen document corresponding to this
os.remove(tempFileName) pod field, p_template and p_format.'''
except Exception, e: template = template or self.template[0]
obj.log(Pod.DELETE_TEMP_DOC_ERROR % str(e).strip(), type='warning') templateName = os.path.splitext(template)[0].replace(os.sep, '_')
return res return '%s_%s.%s' % (self.name, templateName, format)
def store(self, obj, value): def isFrozen(self, obj, template=None, format='pdf'):
'''Stores (=freezes) a document (in p_value) in the field.''' '''Is there a frozen document for thid pod field, on p_obj, for
if isinstance(value, sutils.FileWrapper): p_template in p_format? If yes, it returns the absolute path to the
value = value._zopeFile frozen doc.'''
setattr(obj, self.name, value) template = template or self.template[0]
dbFolder, folder = obj.o.getFsFolder()
fileName = self.getFreezeName(template, format)
res = os.path.join(dbFolder, folder, fileName)
if os.path.exists(res): return res
def freeze(self, obj, template=None, format='pdf'):
'''Freezes, on p_obj, a document for this pod field, for p_template in
p_format.'''
# Compute the absolute path where to store the frozen document in the
# database.
dbFolder, folder = obj.o.getFsFolder(create=True)
fileName = self.getFreezeName(template, format)
result = os.path.join(dbFolder, folder, fileName)
if os.path.exists(result):
obj.log('Freeze: overwriting %s...' % result)
# Generate the document.
doc = self.getValue(obj, template=template, format=format,
result=result)
if isinstance(doc, basestring):
# An error occurred, the document was not generated.
obj.log(self.FREEZE_ERROR % (format, self.name, doc), type='error')
if format == 'odt': raise Exception(self.FREEZE_FATAL_ERROR)
obj.log('Trying to freeze the ODT version...')
# Try to freeze the ODT version of the document, which does not
# require to call LibreOffice: the risk of error is smaller.
fileName = self.getFreezeName(template, 'odt')
result = os.path.join(dbFolder, folder, fileName)
if os.path.exists(result):
obj.log('Freeze: overwriting %s...' % result)
doc = self.getValue(obj, template=template, format='odt',
result=result)
if isinstance(doc, basestring):
self.log(self.FREEZE_ERROR % ('odt', self.name, doc),
type='error')
raise Exception(self.FREEZE_FATAL_ERROR)
return doc
def unfreeze(self, obj, template=None, format='pdf'):
'''Unfreezes, on p_obj, the document for this pod field, for p_template
in p_format.'''
# Compute the absolute path to the frozen doc.
dbFolder, folder = obj.o.getFsFolder()
fileName = self.getFreezeName(template, format)
frozenName = os.path.join(dbFolder, folder, fileName)
if os.path.exists(frozenName): os.remove(frozenName)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -115,6 +115,7 @@ class ToolMixin(BaseMixin):
# Get the object on which a document must be generated. # Get the object on which a document must be generated.
obj = self.getObject(rq.get('objectUid'), appy=True) obj = self.getObject(rq.get('objectUid'), appy=True)
fieldName = rq.get('fieldName') fieldName = rq.get('fieldName')
# Get the document by accessing the value of the pod field.
res = getattr(obj, fieldName) res = getattr(obj, fieldName)
if isinstance(res, basestring): if isinstance(res, basestring):
# An error has occurred, and p_res contains the error message # An error has occurred, and p_res contains the error message
@ -122,8 +123,6 @@ class ToolMixin(BaseMixin):
return self.goto(rq.get('HTTP_REFERER')) return self.goto(rq.get('HTTP_REFERER'))
# res contains a FileInfo instance. # res contains a FileInfo instance.
res.writeResponse(rq.RESPONSE) res.writeResponse(rq.RESPONSE)
# (Try to) delete the temp file on disk.
res.removeFile()
def getAppName(self): def getAppName(self):
'''Returns the name of the application.''' '''Returns the name of the application.'''

View file

@ -13,12 +13,6 @@ from appy.shared.utils import getOsTempFolder, executeCommand, \
from appy.shared.xml_parser import XmlMarshaller from appy.shared.xml_parser import XmlMarshaller
from appy.shared.csv_parser import CsvMarshaller from appy.shared.csv_parser import CsvMarshaller
# Some error messages ----------------------------------------------------------
FREEZE_ERROR = 'Error while trying to freeze a "%s" file in POD field ' \
'"%s" (%s).'
FREEZE_FATAL_ERROR = 'A server error occurred. Please contact the system ' \
'administrator.'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class AbstractWrapper(object): class AbstractWrapper(object):
'''Any real Appy-managed Zope object has a companion object that is an '''Any real Appy-managed Zope object has a companion object that is an
@ -797,38 +791,19 @@ class AbstractWrapper(object):
zopeObj.reindex() zopeObj.reindex()
return appyObj return appyObj
def freeze(self, fieldName): def freeze(self, fieldName, template=None, format='pdf'):
'''This method freezes a POD document. TODO: allow to freeze Computed '''This method freezes the content of pod field named p_fieldName, for
fields.''' the given p_template (several templates can be given in
rq = self.request podField.template), in the given p_format ("pdf" by default).'''
field = self.o.getAppyType(fieldName) field = self.o.getAppyType(fieldName)
if field.type != 'Pod': raise 'Cannot freeze non-Pod field.' if field.type!= 'Pod': raise Exception('Cannot freeze non-Pod field.')
# Set the freeze format return field.freeze(self, template, format)
rq.set('podFormat', field.freezeFormat)
# Generate the document.
doc = field.getValue(self.o)
if isinstance(doc, basestring):
self.log(FREEZE_ERROR % (field.freezeFormat, field.name, doc),
type='error')
if field.freezeFormat == 'odt': raise FREEZE_FATAL_ERROR
self.log('Trying to freeze the ODT version...')
# Try to freeze the ODT version of the document, which does not
# require to call OpenOffice/LibreOffice, so the risk of error is
# smaller.
self.request.set('podFormat', 'odt')
doc = field.getValue(self.o)
if isinstance(doc, basestring):
self.log(FREEZE_ERROR % ('odt', field.name, doc), type='error')
raise FREEZE_FATAL_ERROR
field.store(self.o, doc)
def unFreeze(self, fieldName): def unfreeze(self, fieldName, template=None, format='pdf'):
'''This method un freezes a POD document. TODO: allow to unfreeze '''This method unfreezes a pod field.'''
Computed fields.'''
rq = self.request
field = self.o.getAppyType(fieldName) field = self.o.getAppyType(fieldName)
if field.type != 'Pod': raise 'Cannot unFreeze non-Pod field.' if field.type!= 'Pod': raise Exception('Cannot unfreeze non-Pod field.')
field.store(self.o, None) field.unfreeze(self, template, format)
def delete(self): def delete(self):
'''Deletes myself.''' '''Deletes myself.'''