Added the possibility to define POD templates for any search result (Pod field with param view='search'), bugfix while getting default value for a Ref field, added Computed fields that computes a ZPT macro given as a string to param 'method', added the possibility to define a global style mapping for every Pod field, stopped to generate a field-specific set of i18n labels for pod output formats, carry portal_status_message even through page redirections, added 'deprecatedAddRemove' tags in generated configure.zcml, onEdit can now return a customized message, added possibility to normalize strings for other usages than 'fileName', in appy.shared.utils.normalizeString (for alpha and alphanum usages)

This commit is contained in:
Gaetan Delannay 2011-01-28 14:36:30 +01:00
parent 38f71be89a
commit 90553381a3
18 changed files with 250 additions and 59 deletions

View file

@ -506,7 +506,8 @@ class Type:
search results (p_usage="search") or when sorting reference fields search results (p_usage="search") or when sorting reference fields
(p_usage="ref")?''' (p_usage="ref")?'''
if usage == 'search': if usage == 'search':
return self.indexed and not self.isMultiValued() return self.indexed and not self.isMultiValued() and not \
((self.type == 'String') and self.isSelection())
elif usage == 'ref': elif usage == 'ref':
return self.type in ('Integer', 'Float', 'Boolean', 'Date') or \ return self.type in ('Integer', 'Float', 'Boolean', 'Date') or \
((self.type == 'String') and (self.format == 0)) ((self.type == 'String') and (self.format == 0))
@ -514,7 +515,6 @@ class Type:
def isShowable(self, obj, layoutType): def isShowable(self, obj, layoutType):
'''When displaying p_obj on a given p_layoutType, must we show this '''When displaying p_obj on a given p_layoutType, must we show this
field?''' field?'''
isEdit = layoutType == 'edit'
# Do not show field if it is optional and not selected in tool # Do not show field if it is optional and not selected in tool
if self.optional: if self.optional:
tool = obj.getTool().appy() tool = obj.getTool().appy()
@ -524,7 +524,7 @@ class Type:
return False return False
# Check if the user has the permission to view or edit the field # Check if the user has the permission to view or edit the field
user = obj.portal_membership.getAuthenticatedMember() user = obj.portal_membership.getAuthenticatedMember()
if isEdit: if layoutType == 'edit':
perm = self.writePermission perm = self.writePermission
else: else:
perm = self.readPermission perm = self.readPermission
@ -535,14 +535,9 @@ class Type:
res = self.callMethod(obj, self.show) res = self.callMethod(obj, self.show)
else: else:
res = self.show res = self.show
# Take into account possible values 'view' and 'edit' for 'show' param. # Take into account possible values 'view', 'edit', 'search'...
if res == 'view': if res in ('view', 'edit', 'result'): return res == layoutType
if isEdit: res = False return bool(res)
else: res = True
elif res == 'edit':
if isEdit: res = True
else: res = False
return res
def isClientVisible(self, obj): def isClientVisible(self, obj):
'''This method returns True if this field is visible according to '''This method returns True if this field is visible according to
@ -1641,6 +1636,16 @@ class Ref(Type):
toDelete.append(uid) toDelete.append(uid)
for uid in toDelete: for uid in toDelete:
uids.remove(uid) uids.remove(uid)
if not uids:
# Maybe is there a default value?
defValue = Type.getValue(self, obj)
if defValue:
# I must prefix call to function "type" with "__builtins__"
# because this name was overridden by a method parameter.
if __builtins__['type'](defValue) in sequenceTypes:
uids = [o.o.UID() for o in defValue]
else:
uids = [defValue.o.UID()]
# Prepare the result: an instance of SomeObjects, that, in this case, # Prepare the result: an instance of SomeObjects, that, in this case,
# represent a subset of all referred objects # represent a subset of all referred objects
res = SomeObjects() res = SomeObjects()
@ -1751,6 +1756,10 @@ class Computed(Type):
self.method = method self.method = method
# Does field computation produce plain text or XHTML? # Does field computation produce plain text or XHTML?
self.plainText = plainText self.plainText = plainText
if isinstance(method, basestring):
# When field computation is done with a macro, we know the result
# will be HTML.
self.plainText = False
Type.__init__(self, None, multiplicity, index, default, optional, Type.__init__(self, None, multiplicity, index, default, optional,
False, show, page, group, layouts, move, indexed, False, False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
@ -1758,9 +1767,29 @@ class Computed(Type):
sync) sync)
self.validable = False self.validable = False
def callMacro(self, obj, macroPath):
'''Returns the macro corresponding to p_macroPath. The base folder
where we search is "skyn".'''
# Get the special page in Appy that allows to call a macro
macroPage = obj.skyn.callMacro
# Get, from p_macroPath, the page where the macro lies, and the macro
# name.
names = self.method.split('/')
# Get the page where the macro lies
page = obj.skyn
for name in names[:-1]:
page = getattr(page, name)
macroName = names[-1]
return macroPage(obj, contextObj=obj, page=page, macroName=macroName)
def getValue(self, obj): def getValue(self, obj):
'''Computes the value instead of getting it in the database.''' '''Computes the value instead of getting it in the database.'''
if not self.method: return if not self.method: return
if isinstance(self.method, basestring):
# self.method is a path to a macro that will produce the field value
return self.callMacro(obj, self.method)
else:
# self.method is a method that will return the field value
return self.callMethod(obj, self.method, raiseOnError=False) return self.callMethod(obj, self.method, raiseOnError=False)
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
@ -1864,7 +1893,7 @@ class Pod(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False, colspan=1, master=None, masterValue=None, focus=False,
historized=False, template=None, context=None, action=None, historized=False, template=None, context=None, action=None,
askAction=False): askAction=False, stylesMapping=None):
# The following param stores the path to a POD template # The following param stores the path to a POD template
self.template = template self.template = template
# The context is a dict containing a specific pod context, or a method # The context is a dict containing a specific pod context, or a method
@ -1876,6 +1905,8 @@ class Pod(Type):
# If askAction is True, the action will be triggered only if the user # If askAction is True, the action will be triggered only if the user
# checks a checkbox, which, by default, will be unchecked. # checks a checkbox, which, by default, will be unchecked.
self.askAction = askAction self.askAction = askAction
# A global styles mapping that would apply to the whole template
self.stylesMapping = stylesMapping
Type.__init__(self, None, (0,1), index, default, optional, Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed, False, show, page, group, layouts, move, indexed,
searchable, specificReadPermission, searchable, specificReadPermission,

View file

@ -434,7 +434,7 @@ class ToolClassDescriptor(ClassDescriptor):
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Add the field that will store the output format(s) # Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName) fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'), fieldType = String(validator=Selection('getPodOutputFormats'),
multiplicity=(1,None), default=('odt',), **pg) multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)

View file

@ -18,8 +18,14 @@ COMMON_METHODS = '''
def getTool(self): return self.%s def getTool(self): return self.%s
def getProductConfig(self): return Products.%s.config def getProductConfig(self): return Products.%s.config
def skynView(self): def skynView(self):
"""Redirects to skyn/view.""" """Redirects to skyn/view. Transfers the status message if any."""
return self.REQUEST.RESPONSE.redirect(self.getUrl()) rq = self.REQUEST
msg = rq.get('portal_status_message', '')
if msg:
url = self.getUrl(portal_status_message=msg)
else:
url = self.getUrl()
return rq.RESPONSE.redirect(url)
''' '''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Generator(AbstractGenerator): class Generator(AbstractGenerator):
@ -140,6 +146,10 @@ class Generator(AbstractGenerator):
msg('field_invalid', '', msg.FIELD_INVALID), msg('field_invalid', '', msg.FIELD_INVALID),
msg('file_required', '', msg.FILE_REQUIRED), msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED), msg('image_required', '', msg.IMAGE_REQUIRED),
msg('odt', '', msg.FORMAT_ODT),
msg('pdf', '', msg.FORMAT_PDF),
msg('doc', '', msg.FORMAT_DOC),
msg('rtf', '', msg.FORMAT_RTF),
] ]
# Create a label for every role added by this application # Create a label for every role added by this application
for role in self.getAllUsedRoles(): for role in self.getAllUsedRoles():
@ -153,7 +163,7 @@ class Generator(AbstractGenerator):
if self.config.frontPage: if self.config.frontPage:
self.generateFrontPage() self.generateFrontPage()
self.copyFile('Install.py', self.repls, destFolder='Extensions') self.copyFile('Install.py', self.repls, destFolder='Extensions')
self.copyFile('configure.zcml', self.repls) self.generateConfigureZcml()
self.copyFile('import_steps.xml', self.repls, self.copyFile('import_steps.xml', self.repls,
destFolder='profiles/default') destFolder='profiles/default')
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles', self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
@ -304,6 +314,17 @@ class Generator(AbstractGenerator):
if isBack: res += '.back' if isBack: res += '.back'
return res return res
def generateConfigureZcml(self):
'''Generates file configure.zcml.'''
repls = self.repls.copy()
# Note every class as "deprecated".
depr = ''
for klass in self.getClasses(include='all'):
depr += '<five:deprecatedManageAddDelete class=".%s.%s"/>\n' % \
(klass.name, klass.name)
repls['deprecated'] = depr
self.copyFile('configure.zcml', repls)
def generateConfig(self): def generateConfig(self):
repls = self.repls.copy() repls = self.repls.copy()
# Get some lists of classes # Get some lists of classes

View file

@ -45,6 +45,7 @@ class ToolMixin(BaseMixin):
res['title'] = self.translate(appyType.labelId) res['title'] = self.translate(appyType.labelId)
res['context'] = appyType.context res['context'] = appyType.context
res['action'] = appyType.action res['action'] = appyType.action
res['stylesMapping'] = appyType.stylesMapping
return res return res
def getSiteUrl(self): def getSiteUrl(self):
@ -68,7 +69,7 @@ class ToolMixin(BaseMixin):
template = podInfo['template'].content template = podInfo['template'].content
podTitle = podInfo['title'] podTitle = podInfo['title']
if podInfo['context']: if podInfo['context']:
if type(podInfo['context']) == types.FunctionType: if callable(podInfo['context']):
specificPodContext = podInfo['context'](appyObj) specificPodContext = podInfo['context'](appyObj)
else: else:
specificPodContext = podInfo['context'] specificPodContext = podInfo['context']
@ -76,16 +77,38 @@ class ToolMixin(BaseMixin):
# Temporary file where to generate the result # Temporary file where to generate the result
tempFileName = '%s/%s_%f.%s' % ( tempFileName = '%s/%s_%f.%s' % (
getOsTempFolder(), obj.UID(), time.time(), format) getOsTempFolder(), obj.UID(), time.time(), format)
# Define parameters to pass to the appy.pod renderer # Define parameters to give to the appy.pod renderer
currentUser = self.portal_membership.getAuthenticatedMember() currentUser = self.portal_membership.getAuthenticatedMember()
podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj, podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj,
'now': self.getProductConfig().DateTime(), 'now': self.getProductConfig().DateTime(),
'projectFolder': appyTool.getDiskFolder(), '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: if specificPodContext:
podContext.update(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), rendererParams = {'template': StringIO.StringIO(template),
'context': podContext, 'result': tempFileName} 'context': podContext, 'result': tempFileName}
if stylesMapping:
rendererParams['stylesMapping'] = stylesMapping
if appyTool.unoEnabledPython: if appyTool.unoEnabledPython:
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
if appyTool.openOfficePort: if appyTool.openOfficePort:
@ -105,7 +128,13 @@ class ToolMixin(BaseMixin):
f = file(tempFileName, 'rb') f = file(tempFileName, 'rb')
res = f.read() res = f.read()
# Identify the filename to return # Identify the filename to return
fileName = u'%s-%s' % (obj.Title().decode('utf-8'), podTitle) 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) fileName = appyTool.normalize(fileName)
response = obj.REQUEST.RESPONSE response = obj.REQUEST.RESPONSE
response.setHeader('Content-Type', mimeTypes[format]) response.setHeader('Content-Type', mimeTypes[format])
@ -189,6 +218,19 @@ class ToolMixin(BaseMixin):
return {'fields': fields, 'nbOfColumns': nbOfColumns, return {'fields': fields, 'nbOfColumns': nbOfColumns,
'fieldDicts': fieldDicts} 'fieldDicts': fieldDicts}
queryParamNames = ('type_name', 'search', 'sortKey', 'sortOrder',
'filterKey', 'filterValue')
def getQueryInfo(self):
'''If we are showing search results, this method encodes in a string all
the params in the request that are required for re-triggering the
search.'''
rq = self.REQUEST
res = ''
if rq.has_key('search'):
res = ';'.join([rq.get(key,'').replace(';','') \
for key in self.queryParamNames])
return res
def getImportElements(self, contentType): def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for '''Returns the list of elements that can be imported from p_path for
p_contentType.''' p_contentType.'''
@ -863,4 +905,10 @@ class ToolMixin(BaseMixin):
os.remove(fileName) os.remove(fileName)
return content return content
return 'File does not exist' return 'File does not exist'
def getResultPodFields(self, contentType):
'''Finds, among fields defined on p_contentType, which ones are Pod
fields that need to be shown on a page displaying query results.'''
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -3,7 +3,7 @@
- mixins/ToolMixin is mixed in with the generated application Tool class.''' - mixins/ToolMixin is mixed in with the generated application Tool class.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes import os, os.path, sys, types, mimetypes, urllib
import appy.gen import appy.gen
from appy.gen import Type, String, Selection, Role from appy.gen import Type, String, Selection, Role
from appy.gen.utils import * from appy.gen.utils import *
@ -67,13 +67,15 @@ class BaseMixin:
initiator.appy().link(fieldName, obj) initiator.appy().link(fieldName, obj)
# Call the custom "onEdit" if available # Call the custom "onEdit" if available
msg = None # The message to display to the user. It can be set by onEdit
if obj.wrapperClass: if obj.wrapperClass:
appyObject = obj.appy() appyObject = obj.appy()
if hasattr(appyObject, 'onEdit'): appyObject.onEdit(created) if hasattr(appyObject, 'onEdit'):
msg = appyObject.onEdit(created)
# Manage "add" permissions and reindex the object # Manage "add" permissions and reindex the object
obj._appy_managePermissions() obj._appy_managePermissions()
obj.reindexObject() obj.reindexObject()
return obj return obj, msg
def delete(self): def delete(self):
'''This methods is self's suicide.''' '''This methods is self's suicide.'''
@ -219,10 +221,21 @@ class BaseMixin:
return self.skyn.edit(self) return self.skyn.edit(self)
# Create or update the object in the database # Create or update the object in the database
obj = self.createOrUpdate(isNew, values) obj, msg = self.createOrUpdate(isNew, values)
# Redirect the user to the appropriate page # Redirect the user to the appropriate page
msg = obj.translate('Changes saved.', domain='plone') if not msg: msg = obj.translate('Changes saved.', domain='plone')
# If the object has already been deleted (ie, it is a kind of transient
# object like a one-shot form and has already been deleted in method
# onEdit), redirect to the main site page.
if not getattr(obj.getParentNode(), obj.id, None):
obj.unindexObject()
return self.goto(tool.getSiteUrl(), msg)
# If the user can't access the object anymore, redirect him to the
# main site page.
user = self.portal_membership.getAuthenticatedMember()
if not user.has_permission('View', obj):
return self.goto(tool.getSiteUrl(), msg)
if rq.get('buttonOk.x', None) or saveConfirmed: if rq.get('buttonOk.x', None) or saveConfirmed:
# Go to the consult view for this object # Go to the consult view for this object
obj.plone_utils.addPortalMessage(msg) obj.plone_utils.addPortalMessage(msg)
@ -321,8 +334,10 @@ class BaseMixin:
if previousData: if previousData:
self.addDataChange(previousData) self.addDataChange(previousData)
def goto(self, url, addParams=False): def goto(self, url, msg=None):
'''Brings the user to some p_url after an action has been executed.''' '''Brings the user to some p_url after an action has been executed.'''
if msg:
url += '?' + urllib.urlencode([('portal_status_message',msg)])
return self.REQUEST.RESPONSE.redirect(url) return self.REQUEST.RESPONSE.redirect(url)
def showField(self, name, layoutType='view'): def showField(self, name, layoutType='view'):

View file

@ -0,0 +1,13 @@
<tal:comment replace="nothing">
This page allows to call any macro from Python code, for example.
</tal:comment>
<tal:call tal:define="macroName options/macroName;
page python: options['page'];
contextObj python: options['contextObj'];
tool python: contextObj.getTool();
layoutType python:'view';
putils python: contextObj.plone_utils;
portal python: contextObj.portal_url.getPortalObject();
portal_url python: contextObj.portal_url();">
<metal:callMacro use-macro="python: page.macros[macroName]"/>
</tal:call>

View file

@ -141,7 +141,7 @@
params['filterValue'] = filterWidget.value; params['filterValue'] = filterWidget.value;
} }
} }
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params); askAjaxChunk(hookId,'GET',objectUrl, 'result', 'queryResult', params);
} }
function askObjectHistory(hookId, objectUrl, startNumber) { function askObjectHistory(hookId, objectUrl, startNumber) {
@ -244,12 +244,13 @@
createCookie(cookieId, newState); createCookie(cookieId, newState);
} }
// Function that allows to generate a document from a pod template. // Function that allows to generate a document from a pod template.
function generatePodDocument(contextUid, fieldName, podFormat) { function generatePodDocument(contextUid, fieldName, podFormat, queryData) {
var theForm = document.getElementsByName("podTemplateForm")[0]; var theForm = document.getElementsByName("podTemplateForm")[0];
theForm.objectUid.value = contextUid; theForm.objectUid.value = contextUid;
theForm.fieldName.value = fieldName; theForm.fieldName.value = fieldName;
theForm.podFormat.value = podFormat; theForm.podFormat.value = podFormat;
theForm.askAction.value = "False"; theForm.askAction.value = "False";
theForm.queryData.value = queryData;
var askActionWidget = document.getElementById(contextUid + '_' + fieldName); var askActionWidget = document.getElementById(contextUid + '_' + fieldName);
if (askActionWidget && askActionWidget.checked) { if (askActionWidget && askActionWidget.checked) {
theForm.askAction.value = "True"; theForm.askAction.value = "True";
@ -376,6 +377,7 @@
<input type="hidden" name="fieldName"/> <input type="hidden" name="fieldName"/>
<input type="hidden" name="podFormat"/> <input type="hidden" name="podFormat"/>
<input type="hidden" name="askAction"/> <input type="hidden" name="askAction"/>
<input type="hidden" name="queryData"/>
</form> </form>
</div> </div>
@ -536,6 +538,7 @@
</tal:comment> </tal:comment>
<div metal:define-macro="header" <div metal:define-macro="header"
tal:define="showCommonInfo python: layoutType == 'view'; tal:define="showCommonInfo python: layoutType == 'view';
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type);
hasHistory contextObj/hasHistory; hasHistory contextObj/hasHistory;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded'; historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
creator contextObj/Creator" creator contextObj/Creator"
@ -717,7 +720,7 @@
<metal:message define-macro="message" i18n:domain="plone" > <metal:message define-macro="message" i18n:domain="plone" >
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment> <tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
<div tal:define="msg request/portal_status_message | nothing" <div tal:define="msg request/portal_status_message | nothing"
tal:condition="msg" class="portalMessage" tal:content="msg" i18n:translate=""></div> tal:condition="msg" class="portalMessage" tal:content="structure msg" i18n:translate=""></div>
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment> <tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
<tal:messages define="messages putils/showPortalMessages" condition="messages"> <tal:messages define="messages putils/showPortalMessages" condition="messages">

View file

@ -34,9 +34,20 @@
</tal:newSearch> </tal:newSearch>
</legend> </legend>
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
<table align="right" cellpadding="0" cellspacing="0"
tal:define="widgets python: tool.getResultPodFields(contentType);
layoutType python:'view'"
tal:condition="python: objs and widgets">
<tr><td tal:define="contextObj python: objs[0]"
tal:repeat="widget widgets">
<metal:pod use-macro="here/skyn/widgets/show/macros/field"/>&nbsp;&nbsp;&nbsp;
</td></tr>
</table>
<table cellpadding="0" cellspacing="0" width="100%"><tr> <table cellpadding="0" cellspacing="0" width="100%"><tr>
<td tal:define="descr python: tool.translate(searchDescr)" <td tal:define="descr python: tool.translate(searchDescr)"
tal:condition="python: searchName and descr"> tal:condition="python: searchName and descr.strip()">
<span class="discreet" tal:content="descr"></span><br/><br/> <span class="discreet" tal:content="descr"></span><br/><br/>
</td> </td>
<td align="right" width="25%"> <td align="right" width="25%">

View file

@ -25,8 +25,7 @@
appName appFolder/getId; appName appFolder/getId;
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view'); phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
page request/page|python:'main'; page request/page|python:'main';
phase phaseInfo/name; phase phaseInfo/name;">
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type)">
<metal:prologue use-macro="here/skyn/page/macros/prologue"/> <metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<metal:show use-macro="here/skyn/page/macros/show"/> <metal:show use-macro="here/skyn/page/macros/show"/>
<metal:footer use-macro="here/skyn/page/macros/footer"/> <metal:footer use-macro="here/skyn/page/macros/footer"/>

View file

@ -9,7 +9,7 @@
</tal:askAction> </tal:askAction>
<img tal:repeat="podFormat python: tool.getPodInfo(contextObj, name)['formats']" <img tal:repeat="podFormat python: tool.getPodInfo(contextObj, name)['formats']"
tal:attributes="src string: $portal_url/skyn/${podFormat}.png; tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
onClick python: 'generatePodDocument(\'%s\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat); onClick python: 'generatePodDocument(\'%s\',\'%s\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat, tool.getQueryInfo());
title podFormat/capitalize" title podFormat/capitalize"
style="cursor:pointer"/> style="cursor:pointer"/>
</metal:view> </metal:view>

View file

@ -239,8 +239,9 @@
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']]; refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())"> isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
<select tal:attributes="name rname; size widget/height; <select tal:attributes="name rname;
multiple python: isMultiple and 'multiple' or ''"> size python: test(isMultiple, widget['height'], '');
multiple python: test(isMultiple, 'multiple', '')">
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option> <option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
<tal:ref repeat="refObj allObjects"> <tal:ref repeat="refObj allObjects">
<option tal:define="uid python: contextObj.getReferenceUid(refObj)" <option tal:define="uid python: contextObj.getReferenceUid(refObj)"

View file

@ -6,7 +6,7 @@
layout The layout object that will dictate how object content layout The layout object that will dictate how object content
will be rendered. will be rendered.
Options: Options:
contextMacro The base on folder containing the macros to call for contextMacro The base folder containing the macros to call for
rendering the elements within the layout. rendering the elements within the layout.
Defaults to portal.skyn Defaults to portal.skyn
</tal:comment> </tal:comment>

View file

@ -4,8 +4,7 @@
xmlns:genericsetup="http://namespaces.zope.org/genericsetup" xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="<!applicationName!>"> i18n_domain="<!applicationName!>">
<!--five:deprecatedManageAddDelete class=".Meeting.Meeting"/--> <!deprecated!>
<genericsetup:registerProfile name="default" <genericsetup:registerProfile name="default"
title="<!applicationName!>" description="" title="<!applicationName!>" description=""
provides="Products.GenericSetup.interfaces.EXTENSION" provides="Products.GenericSetup.interfaces.EXTENSION"

View file

@ -30,6 +30,11 @@ class ToolWrapper(AbstractWrapper):
return NOT_UNO_ENABLED_PYTHON % value return NOT_UNO_ENABLED_PYTHON % value
return None return None
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf')
def getPodOutputFormats(self):
'''Gets the available output formats for POD documents.'''
return [(of, self.translate(of)) for of in self.podOutputFormats]
def getInitiator(self): def getInitiator(self):
'''Retrieves the object that triggered the creation of the object '''Retrieves the object that triggered the creation of the object
being currently created (if any).''' being currently created (if any).'''

View file

@ -16,8 +16,8 @@ WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class AbstractWrapper: class AbstractWrapper:
'''Any real web framework object has a companion object that is an instance '''Any real Zope object has a companion object that is an instance of this
of this class.''' class.'''
def __init__(self, o): self.__dict__['o'] = o def __init__(self, o): self.__dict__['o'] = o
def __setattr__(self, name, value): def __setattr__(self, name, value):
@ -118,6 +118,7 @@ class AbstractWrapper:
# Update the Archetypes reference field. # Update the Archetypes reference field.
exec 'objs = self.o.g%s()' % postfix exec 'objs = self.o.g%s()' % postfix
if not objs: return if not objs: return
if type(objs) not in sequenceTypes: objs = [objs]
# Remove p_obj from existing objects # Remove p_obj from existing objects
if type(obj) in sequenceTypes: if type(obj) in sequenceTypes:
for o in obj: for o in obj:
@ -190,14 +191,6 @@ class AbstractWrapper:
appyObj = ploneObj.appy() appyObj = ploneObj.appy()
# Set object attributes # Set object attributes
for attrName, attrValue in kwargs.iteritems(): for attrName, attrValue in kwargs.iteritems():
if isinstance(attrValue, AbstractWrapper):
try:
refAppyType = getattr(appyObj.__class__.__bases__[-1],
attrName)
appyObj.link(attrName, attrValue.o)
except AttributeError, ae:
pass
else:
setattr(appyObj, attrName, attrValue) setattr(appyObj, attrName, attrValue)
if isField: if isField:
# Link the object to this one # Link the object to this one
@ -272,7 +265,6 @@ class AbstractWrapper:
elif mType == 'error': mType = 'stop' elif mType == 'error': mType = 'stop'
self.o.plone_utils.addPortalMessage(message, type=mType) self.o.plone_utils.addPortalMessage(message, type=mType)
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
def normalize(self, s, usage='fileName'): def normalize(self, s, usage='fileName'):
'''Returns a version of string p_s whose special chars have been '''Returns a version of string p_s whose special chars have been
replaced with normal chars.''' replaced with normal chars.'''

View file

@ -109,6 +109,10 @@ class PoMessage:
FILE_REQUIRED = 'Please select a file.' FILE_REQUIRED = 'Please select a file.'
FIELD_INVALID = 'Please fill or correct this.' FIELD_INVALID = 'Please fill or correct this.'
IMAGE_REQUIRED = 'The uploaded file must be an image.' IMAGE_REQUIRED = 'The uploaded file must be an image.'
FORMAT_ODT = 'ODT'
FORMAT_PDF = 'PDF'
FORMAT_DOC = 'DOC'
FORMAT_RTF = 'RTF'
def __init__(self, id, msg, default, fuzzy=False, comments=[], def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False): niceDefault=False):

View file

@ -84,8 +84,10 @@ class BufferIterator:
self.remainingElemIndexes = self.buffer.elements.keys() self.remainingElemIndexes = self.buffer.elements.keys()
self.remainingSubBufferIndexes.sort() self.remainingSubBufferIndexes.sort()
self.remainingElemIndexes.sort() self.remainingElemIndexes.sort()
def hasNext(self): def hasNext(self):
return self.remainingSubBufferIndexes or self.remainingElemIndexes return self.remainingSubBufferIndexes or self.remainingElemIndexes
def next(self): def next(self):
nextSubBufferIndex = None nextSubBufferIndex = None
if self.remainingSubBufferIndexes: if self.remainingSubBufferIndexes:
@ -113,36 +115,45 @@ class BufferIterator:
class Buffer: class Buffer:
'''Abstract class representing any buffer used during rendering.''' '''Abstract class representing any buffer used during rendering.'''
elementRex = re.compile('([\w-]+:[\w-]+)\s*(.*?)>', re.S) elementRex = re.compile('([\w-]+:[\w-]+)\s*(.*?)>', re.S)
def __init__(self, env, parent): def __init__(self, env, parent):
self.parent = parent self.parent = parent
self.subBuffers = {} # ~{i_bufferIndex: Buffer}~ self.subBuffers = {} # ~{i_bufferIndex: Buffer}~
self.env = env self.env = env
def addSubBuffer(self, subBuffer=None): def addSubBuffer(self, subBuffer=None):
if not subBuffer: if not subBuffer:
subBuffer = MemoryBuffer(self.env, self) subBuffer = MemoryBuffer(self.env, self)
self.subBuffers[self.getLength()] = subBuffer self.subBuffers[self.getLength()] = subBuffer
subBuffer.parent = self subBuffer.parent = self
return subBuffer return subBuffer
def removeLastSubBuffer(self): def removeLastSubBuffer(self):
subBufferIndexes = self.subBuffers.keys() subBufferIndexes = self.subBuffers.keys()
subBufferIndexes.sort() subBufferIndexes.sort()
lastIndex = subBufferIndexes.pop() lastIndex = subBufferIndexes.pop()
del self.subBuffers[lastIndex] del self.subBuffers[lastIndex]
def write(self, something): pass # To be overridden def write(self, something): pass # To be overridden
def getLength(self): pass # To be overridden def getLength(self): pass # To be overridden
def dumpStartElement(self, elem, attrs={}): def dumpStartElement(self, elem, attrs={}):
self.write('<%s' % elem) self.write('<%s' % elem)
for name, value in attrs.items(): for name, value in attrs.items():
self.write(' %s="%s"' % (name, value)) self.write(' %s="%s"' % (name, value))
self.write('>') self.write('>')
def dumpEndElement(self, elem): def dumpEndElement(self, elem):
self.write('</%s>' % elem) self.write('</%s>' % elem)
def dumpElement(self, elem, content=None, attrs={}): def dumpElement(self, elem, content=None, attrs={}):
'''For dumping a whole element at once.''' '''For dumping a whole element at once.'''
self.dumpStartElement(elem, attrs) self.dumpStartElement(elem, attrs)
if content: if content:
self.dumpContent(content) self.dumpContent(content)
self.dumpEndElement(elem) self.dumpEndElement(elem)
def dumpContent(self, content): def dumpContent(self, content):
'''Dumps string p_content into the buffer.''' '''Dumps string p_content into the buffer.'''
for c in content: for c in content:
@ -150,6 +161,7 @@ class Buffer:
self.write(XML_SPECIAL_CHARS[c]) self.write(XML_SPECIAL_CHARS[c])
else: else:
self.write(c) self.write(c)
def dumpAttribute(self, name, value): def dumpAttribute(self, name, value):
self.write(''' %s="%s" ''' % (name, value)) self.write(''' %s="%s" ''' % (name, value))
@ -160,17 +172,21 @@ class FileBuffer(Buffer):
self.result = result self.result = result
self.content = file(result, 'w') self.content = file(result, 'w')
self.content.write(xmlPrologue) self.content.write(xmlPrologue)
def getLength(self): return 0
# getLength is used to manage insertions into sub-buffers. But in the case # getLength is used to manage insertions into sub-buffers. But in the case
# of a FileBuffer, we will only have 1 sub-buffer at a time, and we don't # of a FileBuffer, we will only have 1 sub-buffer at a time, and we don't
# care about where it will be inserted into the FileBuffer. # care about where it will be inserted into the FileBuffer.
def getLength(self): return 0
def write(self, something): def write(self, something):
self.content.write(something.encode('utf-8')) self.content.write(something.encode('utf-8'))
def addExpression(self, expression): def addExpression(self, expression):
try: try:
self.dumpContent(Expression(expression).evaluate(self.env.context)) self.dumpContent(Expression(expression).evaluate(self.env.context))
except Exception, e: except Exception, e:
PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False) PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False)
def pushSubBuffer(self, subBuffer): pass def pushSubBuffer(self, subBuffer): pass
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -179,24 +195,30 @@ class MemoryBuffer(Buffer):
'(?:\s+(for|if|else|with)\s*(.*))?') '(?:\s+(for|if|else|with)\s*(.*))?')
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)') forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
varRex = re.compile('\s*([\w\-_]+)\s*=\s*(.*)') varRex = re.compile('\s*([\w\-_]+)\s*=\s*(.*)')
def __init__(self, env, parent): def __init__(self, env, parent):
Buffer.__init__(self, env, parent) Buffer.__init__(self, env, parent)
self.content = u'' self.content = u''
self.elements = {} self.elements = {}
self.action = None self.action = None
def addSubBuffer(self, subBuffer=None): def addSubBuffer(self, subBuffer=None):
sb = Buffer.addSubBuffer(self, subBuffer) sb = Buffer.addSubBuffer(self, subBuffer)
self.content += ' ' # To avoid having several subbuffers referenced at self.content += ' ' # To avoid having several subbuffers referenced at
# the same place within this buffer. # the same place within this buffer.
return sb return sb
def getFileBuffer(self): def getFileBuffer(self):
if isinstance(self.parent, FileBuffer): if isinstance(self.parent, FileBuffer):
res = self.parent res = self.parent
else: else:
res = self.parent.getFileBuffer() res = self.parent.getFileBuffer()
return res return res
def getLength(self): return len(self.content) def getLength(self): return len(self.content)
def write(self, thing): self.content += thing def write(self, thing): self.content += thing
def getIndex(self, podElemName): def getIndex(self, podElemName):
res = -1 res = -1
for index, podElem in self.elements.iteritems(): for index, podElem in self.elements.iteritems():
@ -204,11 +226,13 @@ class MemoryBuffer(Buffer):
if index > res: if index > res:
res = index res = index
return res return res
def getMainElement(self): def getMainElement(self):
res = None res = None
if self.elements.has_key(0): if self.elements.has_key(0):
res = self.elements[0] res = self.elements[0]
return res return res
def isMainElement(self, elem): def isMainElement(self, elem):
res = False res = False
mainElem = self.getMainElement() mainElem = self.getMainElement()
@ -221,6 +245,7 @@ class MemoryBuffer(Buffer):
res = False res = False
break break
return res return res
def unreferenceElement(self, elem): def unreferenceElement(self, elem):
# Find last occurrence of this element # Find last occurrence of this element
elemIndex = -1 elemIndex = -1
@ -229,6 +254,7 @@ class MemoryBuffer(Buffer):
if (podElem.OD.elem == elem) and (index > elemIndex): if (podElem.OD.elem == elem) and (index > elemIndex):
elemIndex = index elemIndex = index
del self.elements[elemIndex] del self.elements[elemIndex]
def pushSubBuffer(self, subBuffer): def pushSubBuffer(self, subBuffer):
'''Sets p_subBuffer at the very end of the buffer.''' '''Sets p_subBuffer at the very end of the buffer.'''
subIndex = None subIndex = None
@ -242,6 +268,7 @@ class MemoryBuffer(Buffer):
del self.subBuffers[subIndex] del self.subBuffers[subIndex]
self.subBuffers[self.getLength()] = subBuffer self.subBuffers[self.getLength()] = subBuffer
self.content += u' ' self.content += u' '
def transferAllContent(self): def transferAllContent(self):
'''Transfer all content to parent.''' '''Transfer all content to parent.'''
if isinstance(self.parent, FileBuffer): if isinstance(self.parent, FileBuffer):
@ -263,11 +290,13 @@ class MemoryBuffer(Buffer):
MemoryBuffer.__init__(self, self.env, self.parent) MemoryBuffer.__init__(self, self.env, self.parent)
# Change buffer position wrt parent # Change buffer position wrt parent
self.parent.pushSubBuffer(self) self.parent.pushSubBuffer(self)
def addElement(self, elem): def addElement(self, elem):
newElem = PodElement.create(elem) newElem = PodElement.create(elem)
self.elements[self.getLength()] = newElem self.elements[self.getLength()] = newElem
if isinstance(newElem, Cell) or isinstance(newElem, Table): if isinstance(newElem, Cell) or isinstance(newElem, Table):
newElem.tableInfo = self.env.getTable() newElem.tableInfo = self.env.getTable()
def addExpression(self, expression): def addExpression(self, expression):
# Create the POD expression # Create the POD expression
expr = Expression(expression) expr = Expression(expression)
@ -275,6 +304,7 @@ class MemoryBuffer(Buffer):
self.elements[self.getLength()] = expr self.elements[self.getLength()] = expr
self.content += u' '# To be sure that an expr and an elem can't be found self.content += u' '# To be sure that an expr and an elem can't be found
# at the same index in the buffer. # at the same index in the buffer.
def createAction(self, statementGroup): def createAction(self, statementGroup):
'''Tries to create an action based on p_statementGroup. If the statement '''Tries to create an action based on p_statementGroup. If the statement
is not correct, r_ is -1. Else, r_ is the index of the element within is not correct, r_ is -1. Else, r_ is the index of the element within
@ -363,6 +393,7 @@ class MemoryBuffer(Buffer):
except ParsingError, ppe: except ParsingError, ppe:
PodError.dump(self, ppe, removeFirstLine=True) PodError.dump(self, ppe, removeFirstLine=True)
return res return res
def cut(self, index, keepFirstPart): def cut(self, index, keepFirstPart):
'''Cuts this buffer into 2 parts. Depending on p_keepFirstPart, the 1st '''Cuts this buffer into 2 parts. Depending on p_keepFirstPart, the 1st
(from 0 to index-1) or the second (from index to the end) part of the (from 0 to index-1) or the second (from index to the end) part of the
@ -418,6 +449,7 @@ class MemoryBuffer(Buffer):
res.write(self.content[:index]) res.write(self.content[:index])
self.content = self.content[index:] self.content = self.content[index:]
return res return res
def getElementIndexes(self, expressions=True): def getElementIndexes(self, expressions=True):
res = [] res = []
for index, elem in self.elements.iteritems(): for index, elem in self.elements.iteritems():
@ -427,6 +459,7 @@ class MemoryBuffer(Buffer):
if condition: if condition:
res.append(index) res.append(index)
return res return res
def transferActionIndependentContent(self, actionElemIndex): def transferActionIndependentContent(self, actionElemIndex):
# Manage content to transfer to parent buffer # Manage content to transfer to parent buffer
if actionElemIndex != 0: if actionElemIndex != 0:
@ -451,6 +484,7 @@ class MemoryBuffer(Buffer):
else: else:
res = self res = self
return res return res
def getStartIndex(self, removeMainElems): def getStartIndex(self, removeMainElems):
'''When I must dump the buffer, sometimes (if p_removeMainElems is '''When I must dump the buffer, sometimes (if p_removeMainElems is
True), I must dump only a subset of it. This method returns the start True), I must dump only a subset of it. This method returns the start
@ -476,6 +510,7 @@ class MemoryBuffer(Buffer):
else: else:
res = 0 res = 0
return res return res
def getStopIndex(self, removeMainElems): def getStopIndex(self, removeMainElems):
'''This method returns the stop index of the buffer part I must dump.''' '''This method returns the stop index of the buffer part I must dump.'''
if removeMainElems: if removeMainElems:

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, traceback, unicodedata, shutil import os, os.path, re, sys, traceback, unicodedata, shutil
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class FolderDeleter: class FolderDeleter:
@ -151,21 +151,35 @@ def executeCommand(cmd):
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ') unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
alphaRex = re.compile('[a-zA-Z]')
alphanumRex = re.compile('[a-zA-Z0-9]')
def normalizeString(s, usage='fileName'): def normalizeString(s, usage='fileName'):
'''Returns a version of string p_s whose special chars have been '''Returns a version of string p_s whose special chars (like accents) have
replaced with normal chars.''' been replaced with normal chars. Moreover, if p_usage is:
* fileName: it removes any char that can't be part of a file name;
* alphanum: it removes any non-alphanumeric char;
* alpha: it removes any non-letter char.
'''
# We work in unicode. Convert p_s to unicode if not unicode. # We work in unicode. Convert p_s to unicode if not unicode.
if isinstance(s, str): s = s.decode('utf-8') if isinstance(s, str): s = s.decode('utf-8')
elif not isinstance(s, unicode): s = unicode(s) elif not isinstance(s, unicode): s = unicode(s)
# Remove any special char like accents.
s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore')
# Remove any other char, depending on p_usage.
if usage == 'fileName': if usage == 'fileName':
# Remove any char that can't be found within a file name under # Remove any char that can't be found within a file name under
# Windows or that could lead to problems with OpenOffice. # Windows or that could lead to problems with OpenOffice.
res = '' res = ''
for char in s: for char in s:
if char not in unwantedChars: if char not in unwantedChars: res += char
res += char elif usage.startswith('alpha'):
s = res exec 'rex = %sRex' % usage
return unicodedata.normalize('NFKD', s).encode("ascii","ignore") res = ''
for char in s:
if rex.match(char): res += char
else:
res = s
return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode, typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode,