Implemented Pod field.

This commit is contained in:
Gaetan Delannay 2010-02-12 10:59:42 +01:00
parent fc75a42264
commit ecd9c66ca1
15 changed files with 260 additions and 188 deletions

View file

@ -460,6 +460,23 @@ class Info(Type):
specificReadPermission, specificWritePermission, width,
height, master, masterValue, focus, historized)
class Pod(Type):
'''A pod is a field allowing to produce a (PDF, ODT, Word, RTF...) document
from data contained in Appy class and linked objects or anything you
want to put in it. It uses appy.pod.'''
def __init__(self, validator=None, index=None, default=None,
optional=False, editDefault=False, show='view',
page='main', group=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
master=None, masterValue=None, focus=False, historized=False,
template=None):
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, move, indexed, searchable,
specificReadPermission, specificWritePermission, width,
height, master, masterValue, focus, historized)
self.template = template # The path to a POD template
# Workflow-specific types ------------------------------------------------------
class State:
def __init__(self, permissions, initial=False, phase='main', show=True):

View file

@ -193,8 +193,16 @@ class ArchetypeFieldDescriptor:
self.widgetParams['visible'] = False # Archetypes will believe the
# field is invisible; we will display it ourselves (like for Ref fields)
def walkPod(self):
'''How to dump a Pod type?'''
self.fieldType = 'FileField'
self.widgetType = 'FileWidget'
self.fieldParams['storage'] = 'python:AttributeStorage()'
# Add the POD-related fields on the Flavour
Flavour._appy_addPodRelatedFields(self)
alwaysAValidatorFor = ('Ref', 'Integer', 'Float')
notToValidateFields = ('Info', 'Computed', 'Action')
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the
Archetype elements to generate.'''
@ -311,10 +319,12 @@ class ArchetypeFieldDescriptor:
elif self.appyType.type == 'Computed': self.walkComputed()
# Manage things which are specific to Actions
elif self.appyType.type == 'Action': self.walkAction()
# Manage things which are specific to reference types
# Manage things which are specific to Ref types
elif self.appyType.type == 'Ref': self.walkRef()
# Manage things which are specific to info types
# Manage things which are specific to Info types
elif self.appyType.type == 'Info': self.walkInfo()
# Manage things which are specific to Pod types
elif self.appyType.type == 'Pod': self.walkPod()
def generate(self):
'''Produces the Archetypes field definition as a string.'''
@ -524,15 +534,6 @@ class ArchetypesClassDescriptor(ClassDescriptor):
return search
return None
def addGenerateDocMethod(self):
m = self.methods
spaces = TABS
m += '\n' + ' '*spaces + 'def generateDocument(self):\n'
spaces += TABS
m += ' '*spaces + "'''Generates a document from p_self.'''\n"
m += ' '*spaces + 'return self._appy_generateDocument()\n'
self.methods = m
class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.'''
predefined = True

View file

@ -689,7 +689,6 @@ class Generator(AbstractGenerator):
self.applicationName)
if classDescr.isAbstract():
register = ''
classDescr.addGenerateDocMethod() # For POD
repls = self.repls.copy()
repls.update({
'imports': '\n'.join(imports), 'parents': parents,

View file

@ -35,6 +35,7 @@ class PloneInstaller:
# front page?
self.showPortlet = showPortlet # Must we show the application portlet?
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
self.attributes = ploneStuff['GLOBALS']['attributes']
self.toLog = StringIO()
self.typeAliases = {'sharing': '', 'gethtml': '',
'(Default)': 'skynView', 'edit': 'skyn/edit',
@ -202,6 +203,7 @@ class PloneInstaller:
def updatePodTemplates(self):
'''Creates or updates the POD templates in flavours according to pod
declarations in the application classes.'''
# Creates or updates the old-way class-related templates
i = -1
for klass in self.appClasses:
i += 1
@ -232,6 +234,30 @@ class PloneInstaller:
flavour.create(podAttr, id=podId, podTemplate=f,
title=produceNiceMessage(templateName))
f.close()
# Creates the new-way templates for Pod fields if they do not exist.
for contentType, attrNames in self.attributes.iteritems():
appyClass = self.tool.getAppyClass(contentType)
for attrName in attrNames:
appyType = getattr(appyClass, attrName)
if appyType.type == 'Pod':
# For every flavour, find the attribute that stores the
# template, and store on it the default one specified in
# the appyType if no template is stored yet.
for flavour in self.appyTool.flavours:
attrName = flavour.getAttributeName(
'podTemplate', appyClass, attrName)
fileObject = getattr(flavour.o, attrName)
if not fileObject or (fileObject.size == 0):
# There is no file. Put the one specified in the
# appyType.
fileName=os.path.join(self.appyTool.getDiskFolder(),
appyType.template)
if os.path.exists(fileName):
setattr(flavour, attrName, fileName)
else:
self.appyTool.log(
'Template "%s" was not found!' % \
fileName, type='error')
def installTool(self):
'''Configures the application tool and flavours.'''

View file

@ -1,10 +1,20 @@
# ------------------------------------------------------------------------------
import os, os.path
import os, os.path, time
from StringIO import StringIO
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder
import appy.pod
from appy.pod.renderer import Renderer
import appy.gen
from appy.gen import Type
from appy.gen.plone25.mixins import AbstractMixin
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
# Errors -----------------------------------------------------------------------
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
POD_ERROR = 'An error occurred while generating the document. Please ' \
'contact the system administrator.'
# ------------------------------------------------------------------------------
class FlavourMixin(AbstractMixin):
_appy_meta_type = 'flavour'
@ -88,7 +98,7 @@ class FlavourMixin(AbstractMixin):
podTemplates = getattr(appySelf, fieldName, [])
if not isinstance(podTemplates, list):
podTemplates = [podTemplates]
res = [r.o for r in podTemplates if r.phase==phase]
res = [r.o for r in podTemplates if r.podPhase == phase]
hasParents = True
klass = obj.__class__
while hasParents:
@ -98,15 +108,106 @@ class FlavourMixin(AbstractMixin):
podTemplates = getattr(appySelf, fieldName, [])
if not isinstance(podTemplates, list):
podTemplates = [podTemplates]
res += [r.o for r in podTemplates if r.phase==phase]
res += [r.o for r in podTemplates if r.podPhase == phase]
klass = parent
else:
hasParents = False
return res
def getMaxShownTemplates(self, obj):
attrName = 'podMaxShownTemplatesFor%s' % obj.meta_type
return getattr(self, attrName)
attrName = 'getPodMaxShownTemplatesFor%s' % obj.meta_type
return getattr(self, attrName)()
def getPodInfo(self, ploneObj, fieldName):
'''Returns POD-related information about Pod field p_fieldName defined
on class whose p_ploneObj is an instance of.'''
res = {}
appyClass = self.getParentNode().getAppyClass(ploneObj.meta_type)
appyFlavour = self.appy()
n = appyFlavour.getAttributeName('formats', appyClass, fieldName)
res['formats'] = getattr(appyFlavour, n)
n = appyFlavour.getAttributeName('podTemplate', appyClass, fieldName)
res['template'] = getattr(appyFlavour, n)
res['title'] = self.translate(getattr(appyClass, fieldName).label)
return res
def generateDocument(self):
'''Generates the document:
- from a PodTemplate instance if it is a class-wide pod template;
- from field-related info on the flavour if it is a Pod field.
UID of object that is the template target is given in the request.'''
rq = self.REQUEST
appyTool = self.getParentNode().appy()
# Get the object
objectUid = rq.get('objectUid')
obj = self.uid_catalog(UID=objectUid)[0].getObject()
# Get information about the document to render. Information comes from
# a PodTemplate instance or from the flavour itself, depending on
# whether we generate a doc from a class-wide template or from a pod
# field.
templateUid = rq.get('templateUid', None)
if templateUid:
podTemplate = self.uid_catalog(UID=templateUid)[0].getObject()
appyPt = podTemplate.appy()
format = podTemplate.getPodFormat()
template = appyPt.podTemplate.content
podTitle = podTemplate.Title()
else:
fieldName = rq.get('fieldName')
podInfo = self.getPodInfo(obj, fieldName)
format = podInfo['formats'][0]
template = podInfo['template'].content
podTitle = podInfo['title']
# 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
currentUser = self.portal_membership.getAuthenticatedMember()
podContext = {'tool': appyTool, 'flavour': self.appy(),
'user': currentUser,
'now': self.getProductConfig().DateTime(),
'projectFolder': appyTool.getDiskFolder(),
}
if templateUid:
podContext['podTemplate'] = podContext['self'] = appyPt
rendererParams = {'template': StringIO(template),
'context': podContext,
'result': tempFileName}
if appyTool.unoEnabledPython:
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
if appyTool.openOfficePort:
rendererParams['ooPort'] = appyTool.openOfficePort
# Launch the renderer
try:
renderer = Renderer(**rendererParams)
renderer.run()
except appy.pod.PodError, pe:
if not os.path.exists(tempFileName):
# In some (most?) cases, when OO returns an error, the result is
# nevertheless generated.
appyTool.log(str(pe), type='error')
appyTool.say(POD_ERROR)
return self.goto(rq.get('HTTP_REFERER'))
# Open the temp file on the filesystem
f = file(tempFileName, 'rb')
res = f.read()
# Identify the filename to return
fileName = u'%s-%s' % (obj.Title().decode('utf-8'),
podTitle.decode('utf-8'))
fileName = appyTool.normalize(fileName)
response = obj.REQUEST.RESPONSE
response.setHeader('Content-Type', mimeTypes[format])
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"'\
% (fileName, format))
f.close()
# Returns the doc and removes the temp file
try:
os.remove(tempFileName)
except OSError, oe:
appyTool.log(DELETE_TEMP_DOC_ERROR % str(oe), type='warning')
except IOError, ie:
appyTool.log(DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
return res
def getAttr(self, attrName):
'''Gets on this flavour attribute named p_attrName. Useful because we

View file

@ -1,111 +1,7 @@
# ------------------------------------------------------------------------------
import os, os.path, time, unicodedata
from appy.shared import mimeTypes
from appy.gen.plone25.mixins import AbstractMixin
from StringIO import StringIO
import appy.pod
from appy.pod.renderer import Renderer
# ------------------------------------------------------------------------------
class PodError(Exception): pass
# ------------------------------------------------------------------------------
def getOsTempFolder():
tmp = '/tmp'
if os.path.exists(tmp) and os.path.isdir(tmp):
res = tmp
elif os.environ.has_key('TMP'):
res = os.environ['TMP']
elif os.environ.has_key('TEMP'):
res = os.environ['TEMP']
else:
raise "Sorry, I can't find a temp folder on your machine."
return res
# Error-related constants ------------------------------------------------------
POD_ERROR = 'An error occurred while generating the document. Please check ' \
'the following things if you wanted to generate the document in ' \
'PDF, DOC or RTF: (1) OpenOffice is started in server mode on ' \
'the port you should have specified in the PloneMeeting ' \
'configuration (go to Site setup-> PloneMeeting configuration); ' \
'(2) if the Python interpreter running Zope and ' \
'Plone is not able to discuss with OpenOffice (it does not have ' \
'"uno" installed - check it by typing "import uno" at the Python ' \
'prompt) please specify, in the PloneMeeting configuration, ' \
'the path to a UNO-enabled Python interpreter (ie, the Python ' \
'interpreter included in the OpenOffice distribution, or, if ' \
'your server runs Ubuntu, the standard Python interpreter ' \
'installed in /usr/bin/python). Here is the error as reported ' \
'by the appy.pod library:\n\n %s'
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
# ------------------------------------------------------------------------------
class PodTemplateMixin(AbstractMixin):
_appy_meta_type = 'podtemplate'
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
def _getFileName(self, obj):
'''Returns a valid, clean fileName for the document generated from
p_self for p_obj.'''
res = u'%s-%s' % (obj.Title().decode('utf-8'),
self.Title().decode('utf-8'))
# Remove accents
res = unicodedata.normalize('NFKD', res).encode("ascii", "ignore")
# Remove unwanted chars (ie, chars that are not valid in file names
# under Windows)
finalRes = ''
for char in res:
if char not in self.unwantedChars:
finalRes += char
return finalRes
def generateDocument(self, obj):
'''Generates a document from this template, for object p_obj.'''
appySelf = self.appy()
appName = self.getProductConfig().PROJECTNAME
appModule = getattr(self.getProductConfig(), appName)
# Temporary file where to generate the result
tempFileName = '%s/%s_%f.%s' % (
getOsTempFolder(), obj.UID(), time.time(), self.getPodFormat())
# Define parameters to pass to the appy.pod renderer
currentUser = self.portal_membership.getAuthenticatedMember()
podContext = {'self': obj.appy(),
'user': currentUser,
'podTemplate': appySelf,
'now': self.getProductConfig().DateTime(),
'projectFolder': os.path.dirname(appModule.__file__)
}
rendererParams = {'template': StringIO(appySelf.podTemplate.content),
'context': podContext,
'result': tempFileName }
if appySelf.tool.unoEnabledPython:
rendererParams['pythonWithUnoPath'] = appySelf.tool.unoEnabledPython
if appySelf.tool.openOfficePort:
rendererParams['ooPort'] = appySelf.tool.openOfficePort
# Launch the renderer
try:
renderer = Renderer(**rendererParams)
renderer.run()
except appy.pod.PodError, pe:
if not os.path.exists(tempFileName):
# In some (most?) cases, when OO returns an error, the result is
# nevertheless generated.
raise PodError(POD_ERROR % str(pe))
# Open the temp file on the filesystem
f = file(tempFileName, 'rb')
res = f.read()
fileName = self._getFileName(obj)
response = obj.REQUEST.RESPONSE
response.setHeader('Content-Type', mimeTypes[self.getPodFormat()])
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"'\
% (fileName, self.getPodFormat()))
f.close()
# Returns the doc and removes the temp file
try:
os.remove(tempFileName)
except OSError, oe:
self.getProductConfig().logger.warn(DELETE_TEMP_DOC_ERROR % str(oe))
except IOError, ie:
self.getProductConfig().logger.warn(DELETE_TEMP_DOC_ERROR % str(ie))
return res
# ------------------------------------------------------------------------------

View file

@ -577,8 +577,8 @@ class ToolMixin(AbstractMixin):
else:
sourceObj = self.uid_catalog(UID=d1)[0].getObject()
label = '%s_%s' % (sourceObj.meta_type, d2)
res['backText'] = u'%s : %s' % \
(sourceObj.Title(),self.translate(label))
res['backText'] = u'%s : %s' % (sourceObj.Title().decode('utf-8'),
self.translate(label))
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
# Among, first, previous, next and last, which one do I need?
previousNeeded = False # Previous ?

View file

@ -258,6 +258,9 @@ class AbstractMixin:
for e in v]
else:
return t('%s_%s_list_%s' % (self.meta_type, name, v))
if not isinstance(v, basestring):
# Archetypes "Description" fields may hold a BaseUnit instance.
v = unicode(v)
return v
elif vType == 'Boolean':
if v: return self.translate('yes', domain='plone')
@ -318,7 +321,7 @@ class AbstractMixin:
If p_startNumber is a number, this method will return x objects,
starting at p_startNumber, x being appyType.maxPerPage.'''
appyType = self.getAppyType(fieldName)
sortedUids = getattr(self, '_appy_%s' % fieldName)
sortedUids = self._appy_getSortedField(fieldName)
batchNeeded = startNumber != None
exec 'refUids= self.getRaw%s%s()' % (fieldName[0].upper(),fieldName[1:])
# There may be too much UIDs in sortedUids because these fields
@ -370,8 +373,7 @@ class AbstractMixin:
def getAppyRefIndex(self, fieldName, obj):
'''Gets the position of p_obj within Ref field named p_fieldName.'''
sortedFieldName = '_appy_%s' % fieldName
sortedObjectsUids = getattr(self, sortedFieldName)
sortedObjectsUids = self._appy_getSortedField(fieldName)
res = sortedObjectsUids.index(obj.UID())
return res
@ -627,8 +629,7 @@ class AbstractMixin:
'''This method changes the position of object with uid p_objectUid in
reference field p_fieldName to p_newIndex i p_isDelta is False, or
to actualIndex+p_newIndex if p_isDelta is True.'''
sortedFieldName = '_appy_%s' % fieldName
sortedObjectsUids = getattr(self, sortedFieldName)
sortedObjectsUids = self._appy_getSortedField(fieldName)
oldIndex = sortedObjectsUids.index(objectUid)
sortedObjectsUids.remove(objectUid)
if isDelta:
@ -1155,31 +1156,19 @@ class AbstractMixin:
res = self.portal_type
return res
def _appy_generateDocument(self):
'''Generates the document from a template whose UID is specified in the
request for a given object whose UID is also in the request.'''
# Get the object
objectUid = self.REQUEST.get('objectUid')
obj = self.uid_catalog(UID=objectUid)[0].getObject()
# Get the POD template
templateUid = self.REQUEST.get('templateUid')
podTemplate = self.uid_catalog(UID=templateUid)[0].getObject()
return podTemplate.generateDocument(obj)
def _appy_manageSortedRefs(self):
'''For every reference field, this method creates the additional
reference lists that are ordered (if it did not already exist).'''
for field in self.schema.fields():
if field.type == 'reference':
sortedRefField = '_appy_%s' % field.getName()
if not hasattr(self.aq_base, sortedRefField):
def _appy_getSortedField(self, fieldName):
'''Gets, for reference field p_fieldName, the Appy persistent list
that contains the sorted list of referred object UIDs. If this list
does not exist, it is created.'''
sortedFieldName = '_appy_%s' % fieldName
if not hasattr(self.aq_base, sortedFieldName):
pList = self.getProductConfig().PersistentList
exec 'self.%s = pList()' % sortedRefField
exec 'self.%s = pList()' % sortedFieldName
return getattr(self, sortedFieldName)
def _appy_manageRefs(self, created):
'''Every time an object is created or updated, this method updates
the Reference fields accordingly.'''
self._appy_manageSortedRefs()
self._appy_manageRefsFromRequest()
# If the creation was initiated by another object, update the
# reference.
@ -1207,7 +1196,7 @@ class AbstractMixin:
fieldName = requestKey[9:]
fieldsInRequest.append(fieldName)
fieldValue = self.REQUEST[requestKey]
sortedRefField = getattr(self, '_appy_%s' % fieldName)
sortedRefField = self._appy_getSortedField(fieldName)
del sortedRefField[:]
if not fieldValue: fieldValue = []
if isinstance(fieldValue, basestring):

View file

@ -66,8 +66,8 @@ class PodTemplate(ModelClass):
podTemplate = File(multiplicity=(1,1))
podFormat = String(validator=['odt', 'pdf', 'rtf', 'doc'],
multiplicity=(1,1), default='odt')
phase = String(default='main')
_appy_attributes = ['description', 'podTemplate', 'podFormat', 'phase']
podPhase = String(default='main')
_appy_attributes = ['description', 'podTemplate', 'podFormat', 'podPhase']
defaultFlavourAttrs = ('number', 'enableNotifications')
flavourAttributePrefixes = ('optionalFieldsFor', 'defaultValueFor',
@ -147,6 +147,26 @@ class Flavour(ModelClass):
fieldType.page = 'data'
fieldType.group = fieldDescr.classDescr.klass.__name__
@classmethod
def _appy_addPodRelatedFields(klass, fieldDescr):
'''Adds the fields needed in the Flavour for configuring a Pod field.
The following method, m_appy_addPodField, is the previous way to
manage gen-pod integration. For the moment, both approaches coexist.
In the future, only this one may subsist.'''
className = fieldDescr.classDescr.name
# On what page and group to display those fields ?
pg = {'page': 'documentGeneration',
'group': '%s_2' % fieldDescr.classDescr.klass.__name__}
# Add the field that will store the pod template.
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = File(**pg)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
# Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
multiplicity=(1,None), default=('odt',), **pg)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
@classmethod
def _appy_addPodField(klass, classDescr):
'''Adds a POD field to the flavour and also an integer field that will

View file

@ -1,7 +1,6 @@
<tal:block metal:define-macro="master"
define="contextObj python:context.getParentNode();
errors request/errors | python:{};
Iterator python:modules['Products.Archetypes'].IndexIterator;
schematas contextObj/Schemata;
fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(contextObj, visible_only=True))];
default_fieldset python:(not schematas or schematas.has_key('default')) and 'default' or fieldsets[0];

View file

@ -1,32 +1,13 @@
<div metal:define-macro="listPodTemplates" class="appyPod" tal:condition="podTemplates"
tal:define="podTemplates python: flavour.getAvailablePodTemplates(contextObj, phase);">
<script language="javascript">
<!--
// Function that allows to generate a meeting document containing selected items.
function generatePodDocument(contextUid, templateUid) {
var theForm = document.forms["podTemplateForm"];
theForm.objectUid.value = contextUid;
theForm.templateUid.value = templateUid;
theForm.submit();
}
-->
</script>
<tal:comment replace="nothing">Form submitted when an object needs to be generated as a document.</tal:comment>
<form name="podTemplateForm" method="post"
tal:attributes="action python: contextObj.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/>
<input type="hidden" name="templateUid"/>
</form>
<tal:podTemplates define="maxShownTemplates python: flavour.getMaxShownTemplates(contextObj)">
<tal:comment replace="nothing">Display templates as links if a few number of templates must be shown</tal:comment>
<span class="discreet" tal:condition="python: len(podTemplates)&lt;=maxShownTemplates"
tal:repeat="podTemplate podTemplates">
<a style="cursor: pointer"
tal:define="podFormat podTemplate/getPodFormat"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\')' % (contextObj.UID(), podTemplate.UID())" >
<img tal:attributes="src string: $portal_url/$podFormat.png"/>
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\')' % (contextObj.UID(), podTemplate.UID())" >
<img tal:attributes="src string: $portal_url/skyn/$podFormat.png"/>
<span tal:replace="podTemplate/Title"/>
</a>
&nbsp;</span>
@ -34,7 +15,7 @@
<select tal:condition="python: len(podTemplates)&gt;maxShownTemplates">
<option value="" tal:content="python: tool.translate('choose_a_doc')"></option>
<option tal:repeat="podTemplate podTemplates" tal:content="podTemplate/Title"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\')' % (contextObj.UID(), podTemplate.UID())" />
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\')' % (contextObj.UID(), podTemplate.UID())" />
</select>
</tal:podTemplates>
</div>
@ -149,13 +130,22 @@
<tal:formattedString condition="python: fmt not in (0, 3)">
<fieldset>
<legend tal:condition="showLabel" tal:content="label"></legend>
<span tal:condition="python: appyType['format'] == 1"
<span tal:condition="python: v and (appyType['format'] == 1)"
tal:replace="structure python: v.replace('\n', '&lt;br&gt;')"/>
<span tal:condition="python: appyType['format'] == 2" tal:replace="structure v"/>
<span tal:condition="python: v and (appyType['format'] == 2)" tal:replace="structure v"/>
</fieldset>
</tal:formattedString>
</metal:showString>
<metal:showPod define-macro="showPodField"
tal:define="fieldName field/getName;
format python:flavour.getPodInfo(contextObj, fieldName)['formats'][0]">
<img tal:attributes="src string: $portal_url/skyn/${format}.png;
title label;
onClick python: 'javascript:generatePodDocument(\'%s\',\'\',\'%s\')' % (contextObj.UID(), fieldName)"
style="cursor:pointer"/>
</metal:showPod>
<div metal:define-macro="showArchetypesField"
tal:define="field fieldDescr/atField|widgetDescr/atField;
appyType fieldDescr/appyType|widgetDescr/appyType;
@ -225,6 +215,9 @@
<tal:infoField condition="python: (not isEdit) and (appyType['type'] == 'Info')">
<metal:af use-macro="here/skyn/macros/macros/showInfoField" />
</tal:infoField>
<tal:podField condition="python: (not isEdit) and (appyType['type'] == 'Pod')">
<metal:af use-macro="here/skyn/macros/macros/showPodField" />
</tal:podField>
</div>
<div metal:define-macro="showBackwardField"
@ -492,6 +485,14 @@
// Inverse the cookie value
createCookie(cookieId, newState);
}
// Function that allows to generate a document from a pod template.
function generatePodDocument(contextUid, templateUid, fieldName) {
var theForm = document.forms["podTemplateForm"];
theForm.objectUid.value = contextUid;
theForm.templateUid.value = templateUid;
theForm.fieldName.value = fieldName;
theForm.submit();
}
-->
</script>
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
@ -499,6 +500,14 @@
<input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/>
</form>
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
<form name="podTemplateForm" method="post"
tal:attributes="action python: flavour.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/>
<tal:comment replace="nothing">templateUid is given if class-wide pod, fieldName is given if podField.</tal:comment>
<input type="hidden" name="templateUid"/>
<input type="hidden" name="fieldName"/>
</form>
</div>
<div metal:define-macro="showPageHeader"

View file

@ -16,6 +16,7 @@
appName appFolder/id;
tool python: portal.get('portal_%s' % appName.lower());
contentType python:context.REQUEST.get('type_name');
flavour python: tool.getFlavour(contentType);
flavourNumber python:int(context.REQUEST.get('flavourNumber'));
searchName python:context.REQUEST.get('search', '')">

View file

@ -3,7 +3,8 @@
i18n:domain="<!applicationName!>">
<body>
<div metal:define-macro="portlet"
tal:define="tool python: context.<!toolInstanceName!>"
tal:define="tool python: context.<!toolInstanceName!>;
flavour python: tool.getFlavour(tool);"
tal:condition="tool/showPortlet">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>

View file

@ -37,6 +37,13 @@ class FlavourWrapper:
number of available templates is higher, templates are shown in a
drop-down list.
"podTemplate"
Stores the pod template for p_attrName.
"formats"
Stores the output format(s) of a given pod template for
p_attrName.
"resultColumns"
Stores the list of columns that must be show when displaying
instances of the a given root p_klass.
@ -71,8 +78,7 @@ class FlavourWrapper:
simply show the current state, be it linked to the current phase
or not.
'''
fullClassName = '%s_%s' % (klass.__module__.replace('.', '_'),
klass.__name__)
fullClassName = self.o.getPortalType(klass)
res = '%sFor%s' % (attributeType, fullClassName)
if attrName: res += '_%s' % attrName
return res

View file

@ -121,11 +121,7 @@ class AbstractWrapper:
objs.append(obj)
exec 'self.o.s%s(objs)' % postfix
# Update the ordered list of references
sortedRefField = '_appy_%s' % fieldName
if not hasattr(self.o.aq_base, sortedRefField):
exec 'self.o.%s = self.o.getProductConfig().PersistentList()' % \
sortedRefField
getattr(self.o, sortedRefField).append(obj.UID())
self.o._appy_getSortedField(fieldName).append(obj.UID())
def sort(self, fieldName):
'''Sorts referred elements linked to p_self via p_fieldName. At
@ -177,7 +173,6 @@ class AbstractWrapper:
ploneObj = getattr(folder, objId)
appyObj = ploneObj.appy()
# Set object attributes
ploneObj._appy_manageSortedRefs()
for attrName, attrValue in kwargs.iteritems():
setterName = 'set%s%s' % (attrName[0].upper(), attrName[1:])
if isinstance(attrValue, AbstractWrapper):
@ -257,9 +252,21 @@ class AbstractWrapper:
elif mType == 'error': mType = 'stop'
self.o.plone_utils.addPortalMessage(message, type=mType)
def normalize(self, s):
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
def normalize(self, s, usage='fileName'):
'''Returns a version of string p_s whose special chars have been
replaced with normal chars.'''
# 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)
if usage == 'fileName':
# Remove any char that can't be found within a file name under
# Windows.
res = ''
for char in s:
if char not in self.unwantedChars:
res += char
s = res
return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
def search(self, klass, sortBy='', maxResults=None,