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, specificReadPermission, specificWritePermission, width,
height, master, masterValue, focus, historized) 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 ------------------------------------------------------ # Workflow-specific types ------------------------------------------------------
class State: class State:
def __init__(self, permissions, initial=False, phase='main', show=True): 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 self.widgetParams['visible'] = False # Archetypes will believe the
# field is invisible; we will display it ourselves (like for Ref fields) # 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') alwaysAValidatorFor = ('Ref', 'Integer', 'Float')
notToValidateFields = ('Info', 'Computed', 'Action') notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
def walkAppyType(self): def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the '''Walks into the Appy type definition and gathers data about the
Archetype elements to generate.''' Archetype elements to generate.'''
@ -311,10 +319,12 @@ class ArchetypeFieldDescriptor:
elif self.appyType.type == 'Computed': self.walkComputed() elif self.appyType.type == 'Computed': self.walkComputed()
# Manage things which are specific to Actions # Manage things which are specific to Actions
elif self.appyType.type == 'Action': self.walkAction() 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() 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() 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): def generate(self):
'''Produces the Archetypes field definition as a string.''' '''Produces the Archetypes field definition as a string.'''
@ -524,15 +534,6 @@ class ArchetypesClassDescriptor(ClassDescriptor):
return search return search
return None 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): class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.''' '''Represents the POD-specific fields that must be added to the tool.'''
predefined = True predefined = True

View file

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

View file

@ -35,6 +35,7 @@ class PloneInstaller:
# front page? # front page?
self.showPortlet = showPortlet # Must we show the application portlet? self.showPortlet = showPortlet # Must we show the application portlet?
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
self.attributes = ploneStuff['GLOBALS']['attributes']
self.toLog = StringIO() self.toLog = StringIO()
self.typeAliases = {'sharing': '', 'gethtml': '', self.typeAliases = {'sharing': '', 'gethtml': '',
'(Default)': 'skynView', 'edit': 'skyn/edit', '(Default)': 'skynView', 'edit': 'skyn/edit',
@ -202,6 +203,7 @@ class PloneInstaller:
def updatePodTemplates(self): def updatePodTemplates(self):
'''Creates or updates the POD templates in flavours according to pod '''Creates or updates the POD templates in flavours according to pod
declarations in the application classes.''' declarations in the application classes.'''
# Creates or updates the old-way class-related templates
i = -1 i = -1
for klass in self.appClasses: for klass in self.appClasses:
i += 1 i += 1
@ -232,6 +234,30 @@ class PloneInstaller:
flavour.create(podAttr, id=podId, podTemplate=f, flavour.create(podAttr, id=podId, podTemplate=f,
title=produceNiceMessage(templateName)) title=produceNiceMessage(templateName))
f.close() 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): def installTool(self):
'''Configures the application tool and flavours.''' '''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 import appy.gen
from appy.gen import Type from appy.gen import Type
from appy.gen.plone25.mixins import AbstractMixin from appy.gen.plone25.mixins import AbstractMixin
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor 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): class FlavourMixin(AbstractMixin):
_appy_meta_type = 'flavour' _appy_meta_type = 'flavour'
@ -88,7 +98,7 @@ class FlavourMixin(AbstractMixin):
podTemplates = getattr(appySelf, fieldName, []) podTemplates = getattr(appySelf, fieldName, [])
if not isinstance(podTemplates, list): if not isinstance(podTemplates, list):
podTemplates = [podTemplates] 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 hasParents = True
klass = obj.__class__ klass = obj.__class__
while hasParents: while hasParents:
@ -98,15 +108,106 @@ class FlavourMixin(AbstractMixin):
podTemplates = getattr(appySelf, fieldName, []) podTemplates = getattr(appySelf, fieldName, [])
if not isinstance(podTemplates, list): if not isinstance(podTemplates, list):
podTemplates = [podTemplates] 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 klass = parent
else: else:
hasParents = False hasParents = False
return res return res
def getMaxShownTemplates(self, obj): def getMaxShownTemplates(self, obj):
attrName = 'podMaxShownTemplatesFor%s' % obj.meta_type attrName = 'getPodMaxShownTemplatesFor%s' % obj.meta_type
return getattr(self, attrName) 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): def getAttr(self, attrName):
'''Gets on this flavour attribute named p_attrName. Useful because we '''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 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): class PodTemplateMixin(AbstractMixin):
_appy_meta_type = 'podtemplate' _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: else:
sourceObj = self.uid_catalog(UID=d1)[0].getObject() sourceObj = self.uid_catalog(UID=d1)[0].getObject()
label = '%s_%s' % (sourceObj.meta_type, d2) label = '%s_%s' % (sourceObj.meta_type, d2)
res['backText'] = u'%s : %s' % \ res['backText'] = u'%s : %s' % (sourceObj.Title().decode('utf-8'),
(sourceObj.Title(),self.translate(label)) self.translate(label))
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber) newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
# Among, first, previous, next and last, which one do I need? # Among, first, previous, next and last, which one do I need?
previousNeeded = False # Previous ? previousNeeded = False # Previous ?

View file

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

View file

@ -66,8 +66,8 @@ class PodTemplate(ModelClass):
podTemplate = File(multiplicity=(1,1)) podTemplate = File(multiplicity=(1,1))
podFormat = String(validator=['odt', 'pdf', 'rtf', 'doc'], podFormat = String(validator=['odt', 'pdf', 'rtf', 'doc'],
multiplicity=(1,1), default='odt') multiplicity=(1,1), default='odt')
phase = String(default='main') podPhase = String(default='main')
_appy_attributes = ['description', 'podTemplate', 'podFormat', 'phase'] _appy_attributes = ['description', 'podTemplate', 'podFormat', 'podPhase']
defaultFlavourAttrs = ('number', 'enableNotifications') defaultFlavourAttrs = ('number', 'enableNotifications')
flavourAttributePrefixes = ('optionalFieldsFor', 'defaultValueFor', flavourAttributePrefixes = ('optionalFieldsFor', 'defaultValueFor',
@ -147,6 +147,26 @@ class Flavour(ModelClass):
fieldType.page = 'data' fieldType.page = 'data'
fieldType.group = fieldDescr.classDescr.klass.__name__ 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 @classmethod
def _appy_addPodField(klass, classDescr): def _appy_addPodField(klass, classDescr):
'''Adds a POD field to the flavour and also an integer field that will '''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" <tal:block metal:define-macro="master"
define="contextObj python:context.getParentNode(); define="contextObj python:context.getParentNode();
errors request/errors | python:{}; errors request/errors | python:{};
Iterator python:modules['Products.Archetypes'].IndexIterator;
schematas contextObj/Schemata; schematas contextObj/Schemata;
fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(contextObj, visible_only=True))]; 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]; 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" <div metal:define-macro="listPodTemplates" class="appyPod" tal:condition="podTemplates"
tal:define="podTemplates python: flavour.getAvailablePodTemplates(contextObj, phase);"> 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: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> <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" <span class="discreet" tal:condition="python: len(podTemplates)&lt;=maxShownTemplates"
tal:repeat="podTemplate podTemplates"> tal:repeat="podTemplate podTemplates">
<a style="cursor: pointer" <a style="cursor: pointer"
tal:define="podFormat podTemplate/getPodFormat" tal:define="podFormat podTemplate/getPodFormat"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\')' % (contextObj.UID(), podTemplate.UID())" > tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\')' % (contextObj.UID(), podTemplate.UID())" >
<img tal:attributes="src string: $portal_url/$podFormat.png"/> <img tal:attributes="src string: $portal_url/skyn/$podFormat.png"/>
<span tal:replace="podTemplate/Title"/> <span tal:replace="podTemplate/Title"/>
</a> </a>
&nbsp;</span> &nbsp;</span>
@ -34,7 +15,7 @@
<select tal:condition="python: len(podTemplates)&gt;maxShownTemplates"> <select tal:condition="python: len(podTemplates)&gt;maxShownTemplates">
<option value="" tal:content="python: tool.translate('choose_a_doc')"></option> <option value="" tal:content="python: tool.translate('choose_a_doc')"></option>
<option tal:repeat="podTemplate podTemplates" tal:content="podTemplate/Title" <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> </select>
</tal:podTemplates> </tal:podTemplates>
</div> </div>
@ -149,13 +130,22 @@
<tal:formattedString condition="python: fmt not in (0, 3)"> <tal:formattedString condition="python: fmt not in (0, 3)">
<fieldset> <fieldset>
<legend tal:condition="showLabel" tal:content="label"></legend> <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;')"/> 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> </fieldset>
</tal:formattedString> </tal:formattedString>
</metal:showString> </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" <div metal:define-macro="showArchetypesField"
tal:define="field fieldDescr/atField|widgetDescr/atField; tal:define="field fieldDescr/atField|widgetDescr/atField;
appyType fieldDescr/appyType|widgetDescr/appyType; appyType fieldDescr/appyType|widgetDescr/appyType;
@ -225,6 +215,9 @@
<tal:infoField condition="python: (not isEdit) and (appyType['type'] == 'Info')"> <tal:infoField condition="python: (not isEdit) and (appyType['type'] == 'Info')">
<metal:af use-macro="here/skyn/macros/macros/showInfoField" /> <metal:af use-macro="here/skyn/macros/macros/showInfoField" />
</tal:infoField> </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>
<div metal:define-macro="showBackwardField" <div metal:define-macro="showBackwardField"
@ -492,6 +485,14 @@
// Inverse the cookie value // Inverse the cookie value
createCookie(cookieId, newState); 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> </script>
<tal:comment replace="nothing">Global form for deleting an object</tal:comment> <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="action" value="Delete"/>
<input type="hidden" name="objectUid"/> <input type="hidden" name="objectUid"/>
</form> </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>
<div metal:define-macro="showPageHeader" <div metal:define-macro="showPageHeader"

View file

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

View file

@ -3,7 +3,8 @@
i18n:domain="<!applicationName!>"> i18n:domain="<!applicationName!>">
<body> <body>
<div metal:define-macro="portlet" <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"> tal:condition="tool/showPortlet">
<metal:block metal:use-macro="here/global_defines/macros/defines" /> <metal:block metal:use-macro="here/global_defines/macros/defines" />
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/> <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 number of available templates is higher, templates are shown in a
drop-down list. 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" "resultColumns"
Stores the list of columns that must be show when displaying Stores the list of columns that must be show when displaying
instances of the a given root p_klass. 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 simply show the current state, be it linked to the current phase
or not. or not.
''' '''
fullClassName = '%s_%s' % (klass.__module__.replace('.', '_'), fullClassName = self.o.getPortalType(klass)
klass.__name__)
res = '%sFor%s' % (attributeType, fullClassName) res = '%sFor%s' % (attributeType, fullClassName)
if attrName: res += '_%s' % attrName if attrName: res += '_%s' % attrName
return res return res

View file

@ -121,11 +121,7 @@ class AbstractWrapper:
objs.append(obj) objs.append(obj)
exec 'self.o.s%s(objs)' % postfix exec 'self.o.s%s(objs)' % postfix
# Update the ordered list of references # Update the ordered list of references
sortedRefField = '_appy_%s' % fieldName self.o._appy_getSortedField(fieldName).append(obj.UID())
if not hasattr(self.o.aq_base, sortedRefField):
exec 'self.o.%s = self.o.getProductConfig().PersistentList()' % \
sortedRefField
getattr(self.o, sortedRefField).append(obj.UID())
def sort(self, fieldName): def sort(self, fieldName):
'''Sorts referred elements linked to p_self via p_fieldName. At '''Sorts referred elements linked to p_self via p_fieldName. At
@ -177,7 +173,6 @@ class AbstractWrapper:
ploneObj = getattr(folder, objId) ploneObj = getattr(folder, objId)
appyObj = ploneObj.appy() appyObj = ploneObj.appy()
# Set object attributes # Set object attributes
ploneObj._appy_manageSortedRefs()
for attrName, attrValue in kwargs.iteritems(): for attrName, attrValue in kwargs.iteritems():
setterName = 'set%s%s' % (attrName[0].upper(), attrName[1:]) setterName = 'set%s%s' % (attrName[0].upper(), attrName[1:])
if isinstance(attrValue, AbstractWrapper): if isinstance(attrValue, AbstractWrapper):
@ -257,9 +252,21 @@ 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)
def normalize(self, s): unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
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.'''
# 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") return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
def search(self, klass, sortBy='', maxResults=None, def search(self, klass, sortBy='', maxResults=None,