[gen] Pod field: allow to upload a replacement file for a frozen pod document.

This commit is contained in:
Gaetan Delannay 2014-03-25 12:05:07 +01:00
parent d0749cc365
commit e969bbf362
12 changed files with 158 additions and 79 deletions

View file

@ -58,7 +58,8 @@ class Pod(Field):
<table cellpadding="0" cellspacing="0" class="podTable"> <table cellpadding="0" cellspacing="0" class="podTable">
<tr> <tr>
<td for="fmt in info.formats" <td for="fmt in info.formats"
var2="freezeAllowed=fmt in info.freezeFormats; var2="freezeAllowed=(fmt in info.freezeFormats) and \
(field.show != 'result');
frozen=field.isFrozen(obj, info.template, fmt)"> frozen=field.isFrozen(obj, info.template, fmt)">
<!-- A clickable icon if no freeze action is allowed --> <!-- A clickable icon if no freeze action is allowed -->
<x if="not freezeAllowed">:field.pxIcon</x> <x if="not freezeAllowed">:field.pxIcon</x>
@ -249,29 +250,39 @@ class Pod(Field):
return res return res
def getValue(self, obj, template=None, format=None, result=None, def getValue(self, obj, template=None, format=None, result=None,
noSecurity=False): queryData=None, customParams=None, noSecurity=False):
'''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, either: file, either from params, or from default values.
- from params p_template and p_format; * p_template is the specific template, among self.template, that must
- from the request object; be used as base for generating the document;
- from default values (the request object may not be present, ie, * p_format is the output format of the resulting document;
when Zope runs in test mode).''' * p_result, if given, must be the absolute path of the document that
will be computed by pod. If not given, pod will produce a doc in
the OS temp folder;
* if the pod document is related to a query, the query parameters
needed to re-trigger the query are given in p_queryData;
* p_customParams may be specified. Every custom param must have form
"name:value". Custom params override any other value available in
the context, including values from the field-specific context.
'''
obj = obj.appy() obj = obj.appy()
rq = obj.request template = template or self.template[0]
template = template or rq.get('template') or self.template[0] format = format or 'odt'
format = format or rq.get('podFormat') or 'odt'
# Security check. # Security check.
if not noSecurity and not self.showTemplate(obj, template): if not noSecurity and not queryData and \
not self.showTemplate(obj, template):
raise Exception(self.UNAUTHORIZED) raise Exception(self.UNAUTHORIZED)
# Return the frozen document if frozen. # Return the possibly frozen document (not applicable for query-related
frozen = self.isFrozen(obj, template, format) # pods).
if frozen: if not queryData:
fileName = self.getDownloadName(obj, template, format, False) frozen = self.isFrozen(obj, template, format)
return FileInfo(frozen, inDb=False, uploadName=fileName) if 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()
@ -293,13 +304,12 @@ class Pod(Field):
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(),
'_': obj.translate, 'projectFolder': diskFolder} '_': obj.translate, 'projectFolder': diskFolder}
# If the POD document is related to a query, get it from the request, # If the pod document is related to a query, re-trigger it and put the
# execute it and put the result in the context. # result in the pod context.
isQueryRelated = rq.get('queryData', None) if queryData:
if isQueryRelated: # Retrieve query params
# Retrieve query params from the request
cmd = ', '.join(tool.o.queryParamNames) cmd = ', '.join(tool.o.queryParamNames)
cmd += " = rq['queryData'].split(';')" cmd += " = queryData.split(';')"
exec cmd exec cmd
# (re-)execute the query, but without any limit on the number of # (re-)execute the query, but without any limit on the number of
# results; return Appy objects. # results; return Appy objects.
@ -310,11 +320,7 @@ class Pod(Field):
# Add the field-specific context if present. # Add the field-specific context if present.
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.
# custom param must have form "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: if customParams:
paramsDict = eval(customParams) paramsDict = eval(customParams)
podContext.update(paramsDict) podContext.update(paramsDict)
@ -342,7 +348,7 @@ class Pod(Field):
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.getDownloadName(obj, template, format, isQueryRelated) fileName = self.getDownloadName(obj, template, format, queryData)
# Get a FileInfo instance to manipulate the file on the filesystem. # Get a FileInfo instance to manipulate the file on the filesystem.
return FileInfo(result, inDb=False, uploadName=fileName) return FileInfo(result, inDb=False, uploadName=fileName)
@ -364,13 +370,17 @@ class Pod(Field):
if os.path.exists(res): return res if os.path.exists(res): return res
def freeze(self, obj, template=None, format='pdf', noSecurity=True, def freeze(self, obj, template=None, format='pdf', noSecurity=True,
freezeOdtOnError=True): upload=None, freezeOdtOnError=True):
'''Freezes, on p_obj, a document for this pod field, for p_template in '''Freezes, on p_obj, a document for this pod field, for p_template in
p_format. If p_noSecurity is True, the security check, based on p_format. If p_noSecurity is True, the security check, based on
self.freezeTemplate, is bypassed. if freezeOdtOnError is True and self.freezeTemplate, is bypassed. If no p_upload file is specified,
format is not "odt", if the freezing fails we try to freeze the odt we re-compute a pod document on-the-fly and we freeze this document.
version, which is more robust because it does not require calling Else, we store the uploaded file.
LibreOffice.'''
If p_freezeOdtOnError is True and format is not "odt" (has only sense
when no p_upload file is specified), if the freezing fails we try to
freeze the odt version, which is more robust because it does not
require calling LibreOffice.'''
# Security check. # Security check.
if not noSecurity and \ if not noSecurity and \
(format not in self.getFreezeFormats(obj, template)): (format not in self.getFreezeFormats(obj, template)):
@ -381,27 +391,37 @@ class Pod(Field):
fileName = self.getFreezeName(template, format) fileName = self.getFreezeName(template, format)
result = os.path.join(dbFolder, folder, fileName) result = os.path.join(dbFolder, folder, fileName)
if os.path.exists(result): if os.path.exists(result):
obj.log('Freeze: overwriting %s...' % result) prefix = upload and 'Freeze (upload)' or 'Freeze'
# Generate the document. obj.log('%s: overwriting %s...' % (prefix, result))
doc = self.getValue(obj, template=template, format=format,result=result) if not upload:
if isinstance(doc, basestring): # Generate the document.
# An error occurred, the document was not generated. doc = self.getValue(obj, template=template, format=format,
obj.log(self.FREEZE_ERROR % (format, self.name, doc), type='error')
if not freezeOdtOnError or (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) result=result)
if isinstance(doc, basestring): if isinstance(doc, basestring):
self.log(self.FREEZE_ERROR % ('odt', self.name, doc), # An error occurred, the document was not generated.
type='error') obj.log(self.FREEZE_ERROR % (format, self.name, doc),
raise Exception(self.FREEZE_FATAL_ERROR) type='error')
if not freezeOdtOnError or (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)
else:
# Store the uploaded file in the database.
f = file(result, 'wb')
doc = FileInfo(result, inDb=False)
doc.replicateFile(upload, f)
f.close()
return doc return doc
def unfreeze(self, obj, template=None, format='pdf', noSecurity=True): def unfreeze(self, obj, template=None, format='pdf', noSecurity=True):
@ -435,4 +455,53 @@ class Pod(Field):
if frozen: if frozen:
res += ' (%s)' % obj.translate('frozen') res += ' (%s)' % obj.translate('frozen')
return res return res
def onUiRequest(self, obj, rq):
'''This method is called when an action tied to this pod field
(generate, freeze, upload...) is triggered from the user
interface.'''
# What is the action to perform?
action = rq.get('action', 'generate')
# Security check.
obj.o.allows('read', raiseError=True)
# Perform the requested action.
tool = obj.tool.o
template = rq.get('template')
format = rq.get('podFormat')
if action == 'generate':
# Generate a (or get a frozen) document.
res = self.getValue(obj, template=template, format=format,
queryData=rq.get('queryData'),
customParams=rq.get('customParams'))
if isinstance(res, basestring):
# An error has occurred, and p_res contains the error message.
obj.say(res)
return tool.goto(rq.get('HTTP_REFERER'))
# res contains a FileInfo instance.
res.writeResponse(rq.RESPONSE)
return
# Performing any other action requires write access to p_obj.
obj.o.allows('write', raiseError=True)
msg = 'action_done'
if action == 'freeze':
# (Re-)freeze a document in the database.
self.freeze(obj, template, format, noSecurity=False,
freezeOdtOnError=False)
elif action == 'unfreeze':
# Unfreeze a document in the database.
self.unfreeze(obj, template, format, noSecurity=False)
elif action == 'upload':
# Ensure a file from the correct type has been uploaded.
upload = rq.get('uploadedFile')
if not upload or not upload.filename or \
not upload.filename.endswith('.%s' % format):
# A wrong file has been uploaded (or no file at all)
msg = 'upload_invalid'
else:
# Store the uploaded file in the database.
self.freeze(obj, template, format, noSecurity=False,
upload=upload)
# Return a message to the user interface.
obj.say(obj.translate(msg))
return tool.goto(rq.get('HTTP_REFERER'))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -113,31 +113,7 @@ class ToolMixin(BaseMixin):
rq = self.REQUEST rq = self.REQUEST
# Get the object that is the target of this action. # Get the object that is the target of this action.
obj = self.getObject(rq.get('objectUid'), appy=True) obj = self.getObject(rq.get('objectUid'), appy=True)
fieldName = rq.get('fieldName') return obj.getField(rq.get('fieldName')).onUiRequest(obj, rq)
# What is the action to perform?
action = rq.get('action', 'generate')
if action == 'generate':
# Generate a (or get a frozen) document by accessing the value of
# the pod field.
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 FileInfo instance.
res.writeResponse(rq.RESPONSE)
elif action == 'freeze':
# (Re-)freeze a document in the database.
res = obj.freeze(fieldName, rq.get('template'), rq.get('podFormat'),
noSecurity=False, freezeOdtOnError=False)
obj.say(obj.translate('action_done'))
return self.goto(rq.get('HTTP_REFERER'))
elif action == 'unfreeze':
# Unfreeze a document in the database.
obj.unfreeze(fieldName, rq.get('template'), rq.get('podFormat'),
noSecurity=False)
obj.say(obj.translate('action_done'))
return self.goto(rq.get('HTTP_REFERER'))
def getAppName(self): def getAppName(self):
'''Returns the name of the application.''' '''Returns the name of the application.'''

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -296,6 +296,10 @@ msgstr "Unfreeze"
msgid "uploadField" msgid "uploadField"
msgstr "Upload a new file" msgstr "Upload a new file"
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr "Please upload a file of the same type."
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "Welcome to this Appy-powered site." msgstr "Welcome to this Appy-powered site."

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -296,6 +296,10 @@ msgstr "Dégeler"
msgid "uploadField" msgid "uploadField"
msgstr "Écraser par..." msgstr "Écraser par..."
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr "Veuillez uploader un fichier du même type."
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "Bienvenue sur ce site fabriqué avec <a href=\"http://appyframework.org\" target=\"_blank\">Appy</a>" msgstr "Bienvenue sur ce site fabriqué avec <a href=\"http://appyframework.org\" target=\"_blank\">Appy</a>"

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -295,6 +295,10 @@ msgstr ""
msgid "uploadField" msgid "uploadField"
msgstr "" msgstr ""
#. Default: "Please upload a file of the same type."
msgid "upload_invalid"
msgstr ""
#. Default: "Welcome to this Appy-powered site." #. Default: "Welcome to this Appy-powered site."
msgid "front_page_text" msgid "front_page_text"
msgstr "" msgstr ""

View file

@ -545,13 +545,14 @@ function freezePod(uid, fieldName, template, podFormat, action) {
askConfirm('form', 'podForm', action_confirm); askConfirm('form', 'podForm', action_confirm);
} }
// Function that allows to upload a file for freezing it in a od field. // Function that allows to upload a file for freezing it in a pod field.
function uploadPod(uid, fieldName, template, podFormat) { function uploadPod(uid, fieldName, template, podFormat) {
var f = document.getElementById('uploadForm'); var f = document.getElementById('uploadForm');
f.objectUid.value = uid; f.objectUid.value = uid;
f.fieldName.value = fieldName; f.fieldName.value = fieldName;
f.template.value = template; f.template.value = template;
f.podFormat.value = podFormat; f.podFormat.value = podFormat;
f.uploadedFile.value = null;
openPopup('uploadPopup'); openPopup('uploadPopup');
} }

View file

@ -126,11 +126,12 @@ class AbstractWrapper(object):
<!-- Popup for uploading a file in a pod field --> <!-- Popup for uploading a file in a pod field -->
<div id="uploadPopup" class="popup" align="center"> <div id="uploadPopup" class="popup" align="center">
<form id="uploadForm" name="uploadForm" enctype="multipart/form-data" <form id="uploadForm" name="uploadForm" enctype="multipart/form-data"
method="post" action=":ztool.absolute_url() + '/uploadPod'"> method="post" action=":ztool.absolute_url() + '/doPod'">
<input type="hidden" name="objectUid"/> <input type="hidden" name="objectUid"/>
<input type="hidden" name="fieldName"/> <input type="hidden" name="fieldName"/>
<input type="hidden" name="template"/> <input type="hidden" name="template"/>
<input type="hidden" name="podFormat"/> <input type="hidden" name="podFormat"/>
<input type="hidden" name="action" value="upload"/>
<input type="file" name="uploadedFile"/><br/><br/> <input type="file" name="uploadedFile"/><br/><br/>
<input type="submit" value=":_('object_save')"/> <input type="submit" value=":_('object_save')"/>
<input type="button" onclick="closePopup('uploadPopup')" <input type="button" onclick="closePopup('uploadPopup')"