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:
parent
38f71be89a
commit
90553381a3
|
@ -506,7 +506,8 @@ class Type:
|
|||
search results (p_usage="search") or when sorting reference fields
|
||||
(p_usage="ref")?'''
|
||||
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':
|
||||
return self.type in ('Integer', 'Float', 'Boolean', 'Date') or \
|
||||
((self.type == 'String') and (self.format == 0))
|
||||
|
@ -514,7 +515,6 @@ class Type:
|
|||
def isShowable(self, obj, layoutType):
|
||||
'''When displaying p_obj on a given p_layoutType, must we show this
|
||||
field?'''
|
||||
isEdit = layoutType == 'edit'
|
||||
# Do not show field if it is optional and not selected in tool
|
||||
if self.optional:
|
||||
tool = obj.getTool().appy()
|
||||
|
@ -524,7 +524,7 @@ class Type:
|
|||
return False
|
||||
# Check if the user has the permission to view or edit the field
|
||||
user = obj.portal_membership.getAuthenticatedMember()
|
||||
if isEdit:
|
||||
if layoutType == 'edit':
|
||||
perm = self.writePermission
|
||||
else:
|
||||
perm = self.readPermission
|
||||
|
@ -535,14 +535,9 @@ class Type:
|
|||
res = self.callMethod(obj, self.show)
|
||||
else:
|
||||
res = self.show
|
||||
# Take into account possible values 'view' and 'edit' for 'show' param.
|
||||
if res == 'view':
|
||||
if isEdit: res = False
|
||||
else: res = True
|
||||
elif res == 'edit':
|
||||
if isEdit: res = True
|
||||
else: res = False
|
||||
return res
|
||||
# Take into account possible values 'view', 'edit', 'search'...
|
||||
if res in ('view', 'edit', 'result'): return res == layoutType
|
||||
return bool(res)
|
||||
|
||||
def isClientVisible(self, obj):
|
||||
'''This method returns True if this field is visible according to
|
||||
|
@ -1641,6 +1636,16 @@ class Ref(Type):
|
|||
toDelete.append(uid)
|
||||
for uid in toDelete:
|
||||
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,
|
||||
# represent a subset of all referred objects
|
||||
res = SomeObjects()
|
||||
|
@ -1751,6 +1756,10 @@ class Computed(Type):
|
|||
self.method = method
|
||||
# Does field computation produce plain text or XHTML?
|
||||
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,
|
||||
False, show, page, group, layouts, move, indexed, False,
|
||||
specificReadPermission, specificWritePermission, width,
|
||||
|
@ -1758,9 +1767,29 @@ class Computed(Type):
|
|||
sync)
|
||||
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):
|
||||
'''Computes the value instead of getting it in the database.'''
|
||||
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)
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
|
@ -1864,7 +1893,7 @@ class Pod(Type):
|
|||
specificWritePermission=False, width=None, height=None,
|
||||
colspan=1, master=None, masterValue=None, focus=False,
|
||||
historized=False, template=None, context=None, action=None,
|
||||
askAction=False):
|
||||
askAction=False, stylesMapping=None):
|
||||
# The following param stores the path to a POD template
|
||||
self.template = template
|
||||
# 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
|
||||
# checks a checkbox, which, by default, will be unchecked.
|
||||
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,
|
||||
False, show, page, group, layouts, move, indexed,
|
||||
searchable, specificReadPermission,
|
||||
|
|
|
@ -434,7 +434,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
self.addField(fieldName, fieldType)
|
||||
# Add the field that will store the output format(s)
|
||||
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)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
||||
|
|
|
@ -18,8 +18,14 @@ COMMON_METHODS = '''
|
|||
def getTool(self): return self.%s
|
||||
def getProductConfig(self): return Products.%s.config
|
||||
def skynView(self):
|
||||
"""Redirects to skyn/view."""
|
||||
return self.REQUEST.RESPONSE.redirect(self.getUrl())
|
||||
"""Redirects to skyn/view. Transfers the status message if any."""
|
||||
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):
|
||||
|
@ -140,6 +146,10 @@ class Generator(AbstractGenerator):
|
|||
msg('field_invalid', '', msg.FIELD_INVALID),
|
||||
msg('file_required', '', msg.FILE_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
|
||||
for role in self.getAllUsedRoles():
|
||||
|
@ -153,7 +163,7 @@ class Generator(AbstractGenerator):
|
|||
if self.config.frontPage:
|
||||
self.generateFrontPage()
|
||||
self.copyFile('Install.py', self.repls, destFolder='Extensions')
|
||||
self.copyFile('configure.zcml', self.repls)
|
||||
self.generateConfigureZcml()
|
||||
self.copyFile('import_steps.xml', self.repls,
|
||||
destFolder='profiles/default')
|
||||
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
|
||||
|
@ -304,6 +314,17 @@ class Generator(AbstractGenerator):
|
|||
if isBack: res += '.back'
|
||||
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):
|
||||
repls = self.repls.copy()
|
||||
# Get some lists of classes
|
||||
|
|
|
@ -45,6 +45,7 @@ class ToolMixin(BaseMixin):
|
|||
res['title'] = self.translate(appyType.labelId)
|
||||
res['context'] = appyType.context
|
||||
res['action'] = appyType.action
|
||||
res['stylesMapping'] = appyType.stylesMapping
|
||||
return res
|
||||
|
||||
def getSiteUrl(self):
|
||||
|
@ -68,7 +69,7 @@ class ToolMixin(BaseMixin):
|
|||
template = podInfo['template'].content
|
||||
podTitle = podInfo['title']
|
||||
if podInfo['context']:
|
||||
if type(podInfo['context']) == types.FunctionType:
|
||||
if callable(podInfo['context']):
|
||||
specificPodContext = podInfo['context'](appyObj)
|
||||
else:
|
||||
specificPodContext = podInfo['context']
|
||||
|
@ -76,16 +77,38 @@ class ToolMixin(BaseMixin):
|
|||
# Temporary file where to generate the result
|
||||
tempFileName = '%s/%s_%f.%s' % (
|
||||
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()
|
||||
podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj,
|
||||
'now': self.getProductConfig().DateTime(),
|
||||
'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:
|
||||
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),
|
||||
'context': podContext, 'result': tempFileName}
|
||||
if stylesMapping:
|
||||
rendererParams['stylesMapping'] = stylesMapping
|
||||
if appyTool.unoEnabledPython:
|
||||
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
|
||||
if appyTool.openOfficePort:
|
||||
|
@ -105,7 +128,13 @@ class ToolMixin(BaseMixin):
|
|||
f = file(tempFileName, 'rb')
|
||||
res = f.read()
|
||||
# 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)
|
||||
response = obj.REQUEST.RESPONSE
|
||||
response.setHeader('Content-Type', mimeTypes[format])
|
||||
|
@ -189,6 +218,19 @@ class ToolMixin(BaseMixin):
|
|||
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
||||
'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):
|
||||
'''Returns the list of elements that can be imported from p_path for
|
||||
p_contentType.'''
|
||||
|
@ -863,4 +905,10 @@ class ToolMixin(BaseMixin):
|
|||
os.remove(fileName)
|
||||
return content
|
||||
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')]
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- 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
|
||||
from appy.gen import Type, String, Selection, Role
|
||||
from appy.gen.utils import *
|
||||
|
@ -67,13 +67,15 @@ class BaseMixin:
|
|||
initiator.appy().link(fieldName, obj)
|
||||
|
||||
# Call the custom "onEdit" if available
|
||||
msg = None # The message to display to the user. It can be set by onEdit
|
||||
if obj.wrapperClass:
|
||||
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
|
||||
obj._appy_managePermissions()
|
||||
obj.reindexObject()
|
||||
return obj
|
||||
return obj, msg
|
||||
|
||||
def delete(self):
|
||||
'''This methods is self's suicide.'''
|
||||
|
@ -219,10 +221,21 @@ class BaseMixin:
|
|||
return self.skyn.edit(self)
|
||||
|
||||
# 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
|
||||
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:
|
||||
# Go to the consult view for this object
|
||||
obj.plone_utils.addPortalMessage(msg)
|
||||
|
@ -321,8 +334,10 @@ class BaseMixin:
|
|||
if 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.'''
|
||||
if msg:
|
||||
url += '?' + urllib.urlencode([('portal_status_message',msg)])
|
||||
return self.REQUEST.RESPONSE.redirect(url)
|
||||
|
||||
def showField(self, name, layoutType='view'):
|
||||
|
|
13
gen/plone25/skin/callMacro.pt
Normal file
13
gen/plone25/skin/callMacro.pt
Normal 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>
|
|
@ -141,7 +141,7 @@
|
|||
params['filterValue'] = filterWidget.value;
|
||||
}
|
||||
}
|
||||
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params);
|
||||
askAjaxChunk(hookId,'GET',objectUrl, 'result', 'queryResult', params);
|
||||
}
|
||||
|
||||
function askObjectHistory(hookId, objectUrl, startNumber) {
|
||||
|
@ -244,12 +244,13 @@
|
|||
createCookie(cookieId, newState);
|
||||
}
|
||||
// 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];
|
||||
theForm.objectUid.value = contextUid;
|
||||
theForm.fieldName.value = fieldName;
|
||||
theForm.podFormat.value = podFormat;
|
||||
theForm.askAction.value = "False";
|
||||
theForm.queryData.value = queryData;
|
||||
var askActionWidget = document.getElementById(contextUid + '_' + fieldName);
|
||||
if (askActionWidget && askActionWidget.checked) {
|
||||
theForm.askAction.value = "True";
|
||||
|
@ -376,6 +377,7 @@
|
|||
<input type="hidden" name="fieldName"/>
|
||||
<input type="hidden" name="podFormat"/>
|
||||
<input type="hidden" name="askAction"/>
|
||||
<input type="hidden" name="queryData"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -536,6 +538,7 @@
|
|||
</tal:comment>
|
||||
<div metal:define-macro="header"
|
||||
tal:define="showCommonInfo python: layoutType == 'view';
|
||||
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type);
|
||||
hasHistory contextObj/hasHistory;
|
||||
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
|
||||
creator contextObj/Creator"
|
||||
|
@ -717,7 +720,7 @@
|
|||
<metal:message define-macro="message" i18n:domain="plone" >
|
||||
<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="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:messages define="messages putils/showPortalMessages" condition="messages">
|
||||
|
|
|
@ -34,9 +34,20 @@
|
|||
</tal:newSearch>
|
||||
</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"/>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" width="100%"><tr>
|
||||
<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/>
|
||||
</td>
|
||||
<td align="right" width="25%">
|
|
@ -25,8 +25,7 @@
|
|||
appName appFolder/getId;
|
||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
||||
page request/page|python:'main';
|
||||
phase phaseInfo/name;
|
||||
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type)">
|
||||
phase phaseInfo/name;">
|
||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
||||
<metal:show use-macro="here/skyn/page/macros/show"/>
|
||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</tal:askAction>
|
||||
<img tal:repeat="podFormat python: tool.getPodInfo(contextObj, name)['formats']"
|
||||
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"
|
||||
style="cursor:pointer"/>
|
||||
</metal:view>
|
||||
|
|
|
@ -239,8 +239,9 @@
|
|||
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
||||
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
||||
|
||||
<select tal:attributes="name rname; size widget/height;
|
||||
multiple python: isMultiple and 'multiple' or ''">
|
||||
<select tal:attributes="name rname;
|
||||
size python: test(isMultiple, widget['height'], '');
|
||||
multiple python: test(isMultiple, 'multiple', '')">
|
||||
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
|
||||
<tal:ref repeat="refObj allObjects">
|
||||
<option tal:define="uid python: contextObj.getReferenceUid(refObj)"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
layout The layout object that will dictate how object content
|
||||
will be rendered.
|
||||
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.
|
||||
Defaults to portal.skyn
|
||||
</tal:comment>
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
|
||||
i18n_domain="<!applicationName!>">
|
||||
|
||||
<!--five:deprecatedManageAddDelete class=".Meeting.Meeting"/-->
|
||||
|
||||
<!deprecated!>
|
||||
<genericsetup:registerProfile name="default"
|
||||
title="<!applicationName!>" description=""
|
||||
provides="Products.GenericSetup.interfaces.EXTENSION"
|
||||
|
|
|
@ -30,6 +30,11 @@ class ToolWrapper(AbstractWrapper):
|
|||
return NOT_UNO_ENABLED_PYTHON % value
|
||||
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):
|
||||
'''Retrieves the object that triggered the creation of the object
|
||||
being currently created (if any).'''
|
||||
|
|
|
@ -16,8 +16,8 @@ WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
class AbstractWrapper:
|
||||
'''Any real web framework object has a companion object that is an instance
|
||||
of this class.'''
|
||||
'''Any real Zope object has a companion object that is an instance of this
|
||||
class.'''
|
||||
def __init__(self, o): self.__dict__['o'] = o
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
|
@ -118,6 +118,7 @@ class AbstractWrapper:
|
|||
# Update the Archetypes reference field.
|
||||
exec 'objs = self.o.g%s()' % postfix
|
||||
if not objs: return
|
||||
if type(objs) not in sequenceTypes: objs = [objs]
|
||||
# Remove p_obj from existing objects
|
||||
if type(obj) in sequenceTypes:
|
||||
for o in obj:
|
||||
|
@ -190,14 +191,6 @@ class AbstractWrapper:
|
|||
appyObj = ploneObj.appy()
|
||||
# Set object attributes
|
||||
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)
|
||||
if isField:
|
||||
# Link the object to this one
|
||||
|
@ -272,7 +265,6 @@ class AbstractWrapper:
|
|||
elif mType == 'error': mType = 'stop'
|
||||
self.o.plone_utils.addPortalMessage(message, type=mType)
|
||||
|
||||
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
|
||||
def normalize(self, s, usage='fileName'):
|
||||
'''Returns a version of string p_s whose special chars have been
|
||||
replaced with normal chars.'''
|
||||
|
|
|
@ -109,6 +109,10 @@ class PoMessage:
|
|||
FILE_REQUIRED = 'Please select a file.'
|
||||
FIELD_INVALID = 'Please fill or correct this.'
|
||||
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=[],
|
||||
niceDefault=False):
|
||||
|
|
|
@ -84,8 +84,10 @@ class BufferIterator:
|
|||
self.remainingElemIndexes = self.buffer.elements.keys()
|
||||
self.remainingSubBufferIndexes.sort()
|
||||
self.remainingElemIndexes.sort()
|
||||
|
||||
def hasNext(self):
|
||||
return self.remainingSubBufferIndexes or self.remainingElemIndexes
|
||||
|
||||
def next(self):
|
||||
nextSubBufferIndex = None
|
||||
if self.remainingSubBufferIndexes:
|
||||
|
@ -113,36 +115,45 @@ class BufferIterator:
|
|||
class Buffer:
|
||||
'''Abstract class representing any buffer used during rendering.'''
|
||||
elementRex = re.compile('([\w-]+:[\w-]+)\s*(.*?)>', re.S)
|
||||
|
||||
def __init__(self, env, parent):
|
||||
self.parent = parent
|
||||
self.subBuffers = {} # ~{i_bufferIndex: Buffer}~
|
||||
self.env = env
|
||||
|
||||
def addSubBuffer(self, subBuffer=None):
|
||||
if not subBuffer:
|
||||
subBuffer = MemoryBuffer(self.env, self)
|
||||
self.subBuffers[self.getLength()] = subBuffer
|
||||
subBuffer.parent = self
|
||||
return subBuffer
|
||||
|
||||
def removeLastSubBuffer(self):
|
||||
subBufferIndexes = self.subBuffers.keys()
|
||||
subBufferIndexes.sort()
|
||||
lastIndex = subBufferIndexes.pop()
|
||||
del self.subBuffers[lastIndex]
|
||||
|
||||
def write(self, something): pass # To be overridden
|
||||
|
||||
def getLength(self): pass # To be overridden
|
||||
|
||||
def dumpStartElement(self, elem, attrs={}):
|
||||
self.write('<%s' % elem)
|
||||
for name, value in attrs.items():
|
||||
self.write(' %s="%s"' % (name, value))
|
||||
self.write('>')
|
||||
|
||||
def dumpEndElement(self, elem):
|
||||
self.write('</%s>' % elem)
|
||||
|
||||
def dumpElement(self, elem, content=None, attrs={}):
|
||||
'''For dumping a whole element at once.'''
|
||||
self.dumpStartElement(elem, attrs)
|
||||
if content:
|
||||
self.dumpContent(content)
|
||||
self.dumpEndElement(elem)
|
||||
|
||||
def dumpContent(self, content):
|
||||
'''Dumps string p_content into the buffer.'''
|
||||
for c in content:
|
||||
|
@ -150,6 +161,7 @@ class Buffer:
|
|||
self.write(XML_SPECIAL_CHARS[c])
|
||||
else:
|
||||
self.write(c)
|
||||
|
||||
def dumpAttribute(self, name, value):
|
||||
self.write(''' %s="%s" ''' % (name, value))
|
||||
|
||||
|
@ -160,17 +172,21 @@ class FileBuffer(Buffer):
|
|||
self.result = result
|
||||
self.content = file(result, 'w')
|
||||
self.content.write(xmlPrologue)
|
||||
def getLength(self): return 0
|
||||
|
||||
# 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
|
||||
# care about where it will be inserted into the FileBuffer.
|
||||
def getLength(self): return 0
|
||||
|
||||
def write(self, something):
|
||||
self.content.write(something.encode('utf-8'))
|
||||
|
||||
def addExpression(self, expression):
|
||||
try:
|
||||
self.dumpContent(Expression(expression).evaluate(self.env.context))
|
||||
except Exception, e:
|
||||
PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False)
|
||||
|
||||
def pushSubBuffer(self, subBuffer): pass
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -179,24 +195,30 @@ class MemoryBuffer(Buffer):
|
|||
'(?:\s+(for|if|else|with)\s*(.*))?')
|
||||
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
|
||||
varRex = re.compile('\s*([\w\-_]+)\s*=\s*(.*)')
|
||||
|
||||
def __init__(self, env, parent):
|
||||
Buffer.__init__(self, env, parent)
|
||||
self.content = u''
|
||||
self.elements = {}
|
||||
self.action = None
|
||||
|
||||
def addSubBuffer(self, subBuffer=None):
|
||||
sb = Buffer.addSubBuffer(self, subBuffer)
|
||||
self.content += ' ' # To avoid having several subbuffers referenced at
|
||||
# the same place within this buffer.
|
||||
return sb
|
||||
|
||||
def getFileBuffer(self):
|
||||
if isinstance(self.parent, FileBuffer):
|
||||
res = self.parent
|
||||
else:
|
||||
res = self.parent.getFileBuffer()
|
||||
return res
|
||||
|
||||
def getLength(self): return len(self.content)
|
||||
|
||||
def write(self, thing): self.content += thing
|
||||
|
||||
def getIndex(self, podElemName):
|
||||
res = -1
|
||||
for index, podElem in self.elements.iteritems():
|
||||
|
@ -204,11 +226,13 @@ class MemoryBuffer(Buffer):
|
|||
if index > res:
|
||||
res = index
|
||||
return res
|
||||
|
||||
def getMainElement(self):
|
||||
res = None
|
||||
if self.elements.has_key(0):
|
||||
res = self.elements[0]
|
||||
return res
|
||||
|
||||
def isMainElement(self, elem):
|
||||
res = False
|
||||
mainElem = self.getMainElement()
|
||||
|
@ -221,6 +245,7 @@ class MemoryBuffer(Buffer):
|
|||
res = False
|
||||
break
|
||||
return res
|
||||
|
||||
def unreferenceElement(self, elem):
|
||||
# Find last occurrence of this element
|
||||
elemIndex = -1
|
||||
|
@ -229,6 +254,7 @@ class MemoryBuffer(Buffer):
|
|||
if (podElem.OD.elem == elem) and (index > elemIndex):
|
||||
elemIndex = index
|
||||
del self.elements[elemIndex]
|
||||
|
||||
def pushSubBuffer(self, subBuffer):
|
||||
'''Sets p_subBuffer at the very end of the buffer.'''
|
||||
subIndex = None
|
||||
|
@ -242,6 +268,7 @@ class MemoryBuffer(Buffer):
|
|||
del self.subBuffers[subIndex]
|
||||
self.subBuffers[self.getLength()] = subBuffer
|
||||
self.content += u' '
|
||||
|
||||
def transferAllContent(self):
|
||||
'''Transfer all content to parent.'''
|
||||
if isinstance(self.parent, FileBuffer):
|
||||
|
@ -263,18 +290,21 @@ class MemoryBuffer(Buffer):
|
|||
MemoryBuffer.__init__(self, self.env, self.parent)
|
||||
# Change buffer position wrt parent
|
||||
self.parent.pushSubBuffer(self)
|
||||
|
||||
def addElement(self, elem):
|
||||
newElem = PodElement.create(elem)
|
||||
self.elements[self.getLength()] = newElem
|
||||
if isinstance(newElem, Cell) or isinstance(newElem, Table):
|
||||
newElem.tableInfo = self.env.getTable()
|
||||
|
||||
def addExpression(self, expression):
|
||||
# Create the POD expression
|
||||
expr = Expression(expression)
|
||||
expr.expr = expression
|
||||
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.
|
||||
|
||||
def createAction(self, statementGroup):
|
||||
'''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
|
||||
|
@ -363,6 +393,7 @@ class MemoryBuffer(Buffer):
|
|||
except ParsingError, ppe:
|
||||
PodError.dump(self, ppe, removeFirstLine=True)
|
||||
return res
|
||||
|
||||
def cut(self, index, keepFirstPart):
|
||||
'''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
|
||||
|
@ -418,6 +449,7 @@ class MemoryBuffer(Buffer):
|
|||
res.write(self.content[:index])
|
||||
self.content = self.content[index:]
|
||||
return res
|
||||
|
||||
def getElementIndexes(self, expressions=True):
|
||||
res = []
|
||||
for index, elem in self.elements.iteritems():
|
||||
|
@ -427,6 +459,7 @@ class MemoryBuffer(Buffer):
|
|||
if condition:
|
||||
res.append(index)
|
||||
return res
|
||||
|
||||
def transferActionIndependentContent(self, actionElemIndex):
|
||||
# Manage content to transfer to parent buffer
|
||||
if actionElemIndex != 0:
|
||||
|
@ -451,6 +484,7 @@ class MemoryBuffer(Buffer):
|
|||
else:
|
||||
res = self
|
||||
return res
|
||||
|
||||
def getStartIndex(self, removeMainElems):
|
||||
'''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
|
||||
|
@ -476,6 +510,7 @@ class MemoryBuffer(Buffer):
|
|||
else:
|
||||
res = 0
|
||||
return res
|
||||
|
||||
def getStopIndex(self, removeMainElems):
|
||||
'''This method returns the stop index of the buffer part I must dump.'''
|
||||
if removeMainElems:
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# 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:
|
||||
|
@ -151,21 +151,35 @@ def executeCommand(cmd):
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
|
||||
alphaRex = re.compile('[a-zA-Z]')
|
||||
alphanumRex = re.compile('[a-zA-Z0-9]')
|
||||
def normalizeString(s, usage='fileName'):
|
||||
'''Returns a version of string p_s whose special chars have been
|
||||
replaced with normal chars.'''
|
||||
'''Returns a version of string p_s whose special chars (like accents) have
|
||||
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.
|
||||
if isinstance(s, str): s = s.decode('utf-8')
|
||||
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':
|
||||
# Remove any char that can't be found within a file name under
|
||||
# Windows or that could lead to problems with OpenOffice.
|
||||
res = ''
|
||||
for char in s:
|
||||
if char not in unwantedChars:
|
||||
res += char
|
||||
s = res
|
||||
return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
|
||||
if char not in unwantedChars: res += char
|
||||
elif usage.startswith('alpha'):
|
||||
exec 'rex = %sRex' % usage
|
||||
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,
|
||||
|
|
Loading…
Reference in a new issue