appy.gen: added default validation for wrong dates; for Action fields, added value 'filetmp' for param 'result' for removing temp files that are returned as the result of an action; values entered by the user in the search screens are not stripped; wrapper method 'export' can now export an object in a CSV file; appy.pod: bullets for default list styles in any ODT file generated through pod are now smaller.
This commit is contained in:
parent
9f418439aa
commit
39d68f6490
13 changed files with 787 additions and 628 deletions
|
@ -126,6 +126,7 @@ class Generator(AbstractGenerator):
|
|||
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
|
||||
msg('bad_long', '', msg.BAD_LONG),
|
||||
msg('bad_float', '', msg.BAD_FLOAT),
|
||||
msg('bad_date', '', msg.BAD_DATE),
|
||||
msg('bad_email', '', msg.BAD_EMAIL),
|
||||
msg('bad_url', '', msg.BAD_URL),
|
||||
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
|
||||
|
|
|
@ -509,7 +509,7 @@ class ToolMixin(BaseMixin):
|
|||
if not importPath: continue
|
||||
objectId = os.path.basename(importPath)
|
||||
self.appy().create(appyClass, id=objectId, _data=importPath)
|
||||
self.plone_utils.addPortalMessage(self.translate('import_done'))
|
||||
self.say(self.translate('import_done'))
|
||||
return self.goto(rq['HTTP_REFERER'])
|
||||
|
||||
def isAlreadyImported(self, contentType, importPath):
|
||||
|
@ -584,13 +584,14 @@ class ToolMixin(BaseMixin):
|
|||
# given field.
|
||||
attrValue = rq.form[attrName]
|
||||
if attrName.find('*') != -1:
|
||||
attrValue = attrValue.strip()
|
||||
# The type of the value is encoded after char "*".
|
||||
attrName, attrType = attrName.split('*')
|
||||
if attrType == 'bool':
|
||||
exec 'attrValue = %s' % attrValue
|
||||
elif attrType in ('int', 'float'):
|
||||
# Get the "from" value
|
||||
if not attrValue.strip(): attrValue = None
|
||||
if not attrValue: attrValue = None
|
||||
else:
|
||||
exec 'attrValue = %s(attrValue)' % attrType
|
||||
# Get the "to" value
|
||||
|
|
|
@ -94,7 +94,7 @@ class BaseMixin:
|
|||
urlBack = self.getTool().getSiteUrl()
|
||||
else:
|
||||
urlBack = self.getUrl(rq['HTTP_REFERER'])
|
||||
self.plone_utils.addPortalMessage(self.translate('delete_done'))
|
||||
self.say(self.translate('delete_done'))
|
||||
self.goto(urlBack)
|
||||
|
||||
def onCreate(self):
|
||||
|
@ -189,8 +189,7 @@ class BaseMixin:
|
|||
urlBack = tool.getSiteUrl()
|
||||
else:
|
||||
urlBack = self.getUrl()
|
||||
self.plone_utils.addPortalMessage(
|
||||
self.translate('Changes canceled.', domain='plone'))
|
||||
self.say(self.translate('Changes canceled.', domain='plone'))
|
||||
return self.goto(urlBack)
|
||||
|
||||
# Object for storing validation errors
|
||||
|
@ -202,7 +201,7 @@ class BaseMixin:
|
|||
self.intraFieldValidation(errors, values)
|
||||
if errors.__dict__:
|
||||
rq.set('errors', errors.__dict__)
|
||||
self.plone_utils.addPortalMessage(errorMessage)
|
||||
self.say(errorMessage)
|
||||
return self.skyn.edit(self)
|
||||
|
||||
# Trigger inter-field validation
|
||||
|
@ -210,7 +209,7 @@ class BaseMixin:
|
|||
if not msg: msg = errorMessage
|
||||
if errors.__dict__:
|
||||
rq.set('errors', errors.__dict__)
|
||||
self.plone_utils.addPortalMessage(msg)
|
||||
self.say(msg)
|
||||
return self.skyn.edit(self)
|
||||
|
||||
# Before saving data, must we ask a confirmation by the user ?
|
||||
|
@ -240,7 +239,7 @@ class BaseMixin:
|
|||
return self.goto(tool.getSiteUrl(), msg)
|
||||
if rq.get('buttonOk.x', None) or saveConfirmed:
|
||||
# Go to the consult view for this object
|
||||
obj.plone_utils.addPortalMessage(msg)
|
||||
obj.say(msg)
|
||||
return self.goto(obj.getUrl())
|
||||
if rq.get('buttonPrevious.x', None):
|
||||
# Go to the previous page for this object.
|
||||
|
@ -260,7 +259,7 @@ class BaseMixin:
|
|||
else:
|
||||
return self.goto(obj.getUrl(page=pageName))
|
||||
else:
|
||||
obj.plone_utils.addPortalMessage(msg)
|
||||
obj.say(msg)
|
||||
return self.goto(obj.getUrl())
|
||||
if rq.get('buttonNext.x', None):
|
||||
# Go to the next page for this object
|
||||
|
@ -277,10 +276,30 @@ class BaseMixin:
|
|||
else:
|
||||
return self.goto(obj.getUrl(page=pageName))
|
||||
else:
|
||||
obj.plone_utils.addPortalMessage(msg)
|
||||
obj.say(msg)
|
||||
return self.goto(obj.getUrl())
|
||||
return obj.skyn.edit(obj)
|
||||
|
||||
def say(self, msg, type='info'):
|
||||
'''Prints a p_msg in the user interface. p_logLevel may be "info",
|
||||
"warning" or "error".'''
|
||||
mType = type
|
||||
if mType == 'warning': mType = 'warn'
|
||||
elif mType == 'error': mType = 'stop'
|
||||
try:
|
||||
self.plone_utils.addPortalMessage(msg, type=mType)
|
||||
except UnicodeDecodeError:
|
||||
self.plone_utils.addPortalMessage(msg.decode('utf-8'), type=mType)
|
||||
|
||||
def log(self, msg, type='info'):
|
||||
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
|
||||
or "error".'''
|
||||
logger = self.getProductConfig().logger
|
||||
if type == 'warning': logMethod = logger.warn
|
||||
elif type == 'error': logMethod = logger.error
|
||||
else: logMethod = logger.info
|
||||
logMethod(msg)
|
||||
|
||||
def rememberPreviousData(self):
|
||||
'''This method is called before updating an object and remembers, for
|
||||
every historized field, the previous value. Result is a dict
|
||||
|
@ -880,16 +899,27 @@ class BaseMixin:
|
|||
label = '%s_action_%s' % (appyType.labelId, suffix)
|
||||
msg = self.translate(label)
|
||||
if (resultType == 'computation') or not successfull:
|
||||
self.plone_utils.addPortalMessage(msg)
|
||||
self.say(msg)
|
||||
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||
elif resultType == 'file':
|
||||
elif resultType.startswith('file'):
|
||||
# msg does not contain a message, but a file instance.
|
||||
response = self.REQUEST.RESPONSE
|
||||
response.setHeader('Content-Type',mimetypes.guess_type(msg.name)[0])
|
||||
response.setHeader('Content-Disposition', 'inline;filename="%s"' %\
|
||||
msg.name)
|
||||
os.path.basename(msg.name))
|
||||
response.write(msg.read())
|
||||
msg.close()
|
||||
if resultType == 'filetmp':
|
||||
# p_msg is a temp file. We need to delete it.
|
||||
try:
|
||||
os.remove(msg.name)
|
||||
self.log('Temp file "%s" was deleted.' % msg.name)
|
||||
except IOError, err:
|
||||
self.log('Could not remove temp "%s" (%s).' % \
|
||||
(msg.name, str(err)), type='warning')
|
||||
except OSError, err:
|
||||
self.log('Could not remove temp "%s" (%s).' % \
|
||||
(msg.name, str(err)), type='warning')
|
||||
elif resultType == 'redirect':
|
||||
# msg does not contain a message, but the URL where to redirect
|
||||
# the user.
|
||||
|
|
|
@ -113,7 +113,7 @@ class Translation(ModelClass):
|
|||
# All methods defined below are fake. Real versions are in the wrapper.
|
||||
def getPoFile(self): pass
|
||||
po = Action(action=getPoFile, page=Page('actions', show='view'),
|
||||
result='file')
|
||||
result='filetmp')
|
||||
title = String(show=False, indexed=True)
|
||||
def computeLabel(self): pass
|
||||
def showField(self, name): pass
|
||||
|
|
|
@ -729,10 +729,10 @@
|
|||
<tal:comment replace="nothing">
|
||||
This macro displays the global message on the page.
|
||||
</tal:comment>
|
||||
<metal:message define-macro="message" i18n:domain="plone" >
|
||||
<metal:message define-macro="message">
|
||||
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
|
||||
<div tal:define="msg request/portal_status_message | nothing"
|
||||
tal:condition="msg" class="portalMessage" tal:content="structure msg" i18n:translate=""></div>
|
||||
tal:condition="msg" class="portalMessage" tal:content="structure msg"></div>
|
||||
|
||||
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
|
||||
<tal:messages define="messages putils/showPortalMessages" condition="messages">
|
||||
|
@ -741,7 +741,7 @@
|
|||
repeat="msg messages">
|
||||
<div tal:define="mtype msg/type | nothing;"
|
||||
tal:attributes="class python:mtype and type_css_map[mtype] or 'info';"
|
||||
tal:content="structure msg/message | nothing" i18n:translate=""></div>
|
||||
tal:content="structure msg/message | nothing"></div>
|
||||
</tal:msgs>
|
||||
</tal:messages>
|
||||
</metal:message>
|
||||
|
|
|
@ -182,5 +182,5 @@ def do(transitionName, stateChange, logger):
|
|||
if not msg:
|
||||
msg = ploneObj.translate(u'Your content\'s status has been modified.',
|
||||
domain='plone')
|
||||
ploneObj.plone_utils.addPortalMessage(msg)
|
||||
ploneObj.say(msg)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -8,6 +8,7 @@ from appy.gen import Search
|
|||
from appy.gen.utils import sequenceTypes
|
||||
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
|
||||
from appy.shared.xml_parser import XmlMarshaller
|
||||
from appy.shared.csv_parser import CsvMarshaller
|
||||
|
||||
# Some error messages ----------------------------------------------------------
|
||||
WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
||||
|
@ -19,6 +20,7 @@ class AbstractWrapper:
|
|||
'''Any real Zope object has a companion object that is an instance of this
|
||||
class.'''
|
||||
def __init__(self, o): self.__dict__['o'] = o
|
||||
def appy(self): return self
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
appyType = self.o.getAppyType(name)
|
||||
|
@ -248,22 +250,8 @@ class AbstractWrapper:
|
|||
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
||||
del self.o._v_appy_do
|
||||
|
||||
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 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 log(self, message, type='info'): return self.o.log(message, type)
|
||||
def say(self, message, type='info'): return self.o.say(message, type)
|
||||
|
||||
def normalize(self, s, usage='fileName'):
|
||||
'''Returns a version of string p_s whose special chars have been
|
||||
|
@ -346,29 +334,46 @@ class AbstractWrapper:
|
|||
method in those cases.'''
|
||||
self.o.reindexObject()
|
||||
|
||||
def export(self, at='string'):
|
||||
'''Creates an "exportable", XML version of this object. If p_at is
|
||||
"string", this method returns the XML version, without the XML
|
||||
prologue. Else, (a) if not p_at, the XML will be exported on disk,
|
||||
in the OS temp folder, with an ugly name; (b) else, it will be
|
||||
exported at path p_at.'''
|
||||
# Determine where to put the result
|
||||
toDisk = (at != 'string')
|
||||
if toDisk and not at:
|
||||
at = getOsTempFolder() + '/' + self.o.UID() + '.xml'
|
||||
# Create the XML version of the object
|
||||
marshaller = XmlMarshaller(cdata=True, dumpUnicode=True,
|
||||
dumpXmlPrologue=toDisk,
|
||||
rootTag=self.klass.__name__)
|
||||
xml = marshaller.marshall(self.o, objectType='appy')
|
||||
# Produce the desired result
|
||||
if toDisk:
|
||||
f = file(at, 'w')
|
||||
f.write(xml.encode('utf-8'))
|
||||
f.close()
|
||||
return at
|
||||
else:
|
||||
return xml
|
||||
def export(self, at='string', format='xml', include=None, exclude=None):
|
||||
'''Creates an "exportable" version of this object. p_format is XML by
|
||||
default, but can also be "csv". If p_format is:
|
||||
* "xml", if p_at is "string", this method returns the XML version,
|
||||
without the XML prologue. Else, (a) if not p_at, the XML
|
||||
will be exported on disk, in the OS temp folder, with an
|
||||
ugly name; (b) else, it will be exported at path p_at.
|
||||
* "csv", if p_at is "string", this method returns the CSV data as a
|
||||
string. If p_at is an opened file handler, the CSV line will
|
||||
be appended in it.
|
||||
If p_include is given, only fields whose names are in it will be
|
||||
included. p_exclude, if given, contains names of fields that will
|
||||
not be included in the result.
|
||||
'''
|
||||
if format == 'xml':
|
||||
# Todo: take p_include and p_exclude into account.
|
||||
# Determine where to put the result
|
||||
toDisk = (at != 'string')
|
||||
if toDisk and not at:
|
||||
at = getOsTempFolder() + '/' + self.o.UID() + '.xml'
|
||||
# Create the XML version of the object
|
||||
marshaller = XmlMarshaller(cdata=True, dumpUnicode=True,
|
||||
dumpXmlPrologue=toDisk,
|
||||
rootTag=self.klass.__name__)
|
||||
xml = marshaller.marshall(self.o, objectType='appy')
|
||||
# Produce the desired result
|
||||
if toDisk:
|
||||
f = file(at, 'w')
|
||||
f.write(xml.encode('utf-8'))
|
||||
f.close()
|
||||
return at
|
||||
else:
|
||||
return xml
|
||||
elif format == 'csv':
|
||||
if isinstance(at, basestring):
|
||||
marshaller = CsvMarshaller(include=include, exclude=exclude)
|
||||
return marshaller.marshall(self)
|
||||
else:
|
||||
marshaller = CsvMarshaller(at, include=include, exclude=exclude)
|
||||
marshaller.marshall(self)
|
||||
|
||||
def historize(self, data):
|
||||
'''This method allows to add "manually" a "data-change" event into the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue