From ecd9c66ca119bd428781bf4e01a915a5cc57ab7b Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Fri, 12 Feb 2010 10:59:42 +0100 Subject: [PATCH] Implemented Pod field. --- gen/__init__.py | 17 ++++ gen/plone25/descriptors.py | 25 +++--- gen/plone25/generator.py | 1 - gen/plone25/installer.py | 26 ++++++ gen/plone25/mixins/FlavourMixin.py | 111 +++++++++++++++++++++++-- gen/plone25/mixins/PodTemplateMixin.py | 104 ----------------------- gen/plone25/mixins/ToolMixin.py | 4 +- gen/plone25/mixins/__init__.py | 43 ++++------ gen/plone25/model.py | 24 +++++- gen/plone25/skin/edit.pt | 1 - gen/plone25/skin/macros.pt | 57 +++++++------ gen/plone25/skin/query.pt | 1 + gen/plone25/templates/Portlet.pt | 3 +- gen/plone25/wrappers/FlavourWrapper.py | 10 ++- gen/plone25/wrappers/__init__.py | 21 +++-- 15 files changed, 260 insertions(+), 188 deletions(-) diff --git a/gen/__init__.py b/gen/__init__.py index 0025f26..e5ae1a2 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -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): diff --git a/gen/plone25/descriptors.py b/gen/plone25/descriptors.py index c8583de..af298ea 100644 --- a/gen/plone25/descriptors.py +++ b/gen/plone25/descriptors.py @@ -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 diff --git a/gen/plone25/generator.py b/gen/plone25/generator.py index 1e0f49a..a760935 100644 --- a/gen/plone25/generator.py +++ b/gen/plone25/generator.py @@ -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, diff --git a/gen/plone25/installer.py b/gen/plone25/installer.py index b30ac39..0828613 100644 --- a/gen/plone25/installer.py +++ b/gen/plone25/installer.py @@ -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.''' diff --git a/gen/plone25/mixins/FlavourMixin.py b/gen/plone25/mixins/FlavourMixin.py index 39e888e..3e92861 100644 --- a/gen/plone25/mixins/FlavourMixin.py +++ b/gen/plone25/mixins/FlavourMixin.py @@ -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 diff --git a/gen/plone25/mixins/PodTemplateMixin.py b/gen/plone25/mixins/PodTemplateMixin.py index b0ffc7b..94de19e 100644 --- a/gen/plone25/mixins/PodTemplateMixin.py +++ b/gen/plone25/mixins/PodTemplateMixin.py @@ -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 # ------------------------------------------------------------------------------ diff --git a/gen/plone25/mixins/ToolMixin.py b/gen/plone25/mixins/ToolMixin.py index 29be430..073142f 100644 --- a/gen/plone25/mixins/ToolMixin.py +++ b/gen/plone25/mixins/ToolMixin.py @@ -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 ? diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py index f8f01dd..3acbaaf 100644 --- a/gen/plone25/mixins/__init__.py +++ b/gen/plone25/mixins/__init__.py @@ -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): - pList = self.getProductConfig().PersistentList - exec 'self.%s = pList()' % 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()' % 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): diff --git a/gen/plone25/model.py b/gen/plone25/model.py index 546ba1f..7d8248f 100644 --- a/gen/plone25/model.py +++ b/gen/plone25/model.py @@ -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 diff --git a/gen/plone25/skin/edit.pt b/gen/plone25/skin/edit.pt index eb7e351..abb9c52 100644 --- a/gen/plone25/skin/edit.pt +++ b/gen/plone25/skin/edit.pt @@ -1,7 +1,6 @@ - - - Form submitted when an object needs to be generated as a document. -
- - -
- Display templates as links if a few number of templates must be shown - + tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\')' % (contextObj.UID(), podTemplate.UID())" > +   @@ -34,7 +15,7 @@ @@ -149,13 +130,22 @@
- - +
+ + + +
+ + +
Global form for deleting an object @@ -499,6 +500,14 @@ + Global form for generating a document from a pod template. +
+ + templateUid is given if class-wide pod, fieldName is given if podField. + + +
diff --git a/gen/plone25/templates/Portlet.pt b/gen/plone25/templates/Portlet.pt index cfdef42..ca6e702 100644 --- a/gen/plone25/templates/Portlet.pt +++ b/gen/plone25/templates/Portlet.pt @@ -3,7 +3,8 @@ i18n:domain="">
diff --git a/gen/plone25/wrappers/FlavourWrapper.py b/gen/plone25/wrappers/FlavourWrapper.py index c851a42..f22682c 100644 --- a/gen/plone25/wrappers/FlavourWrapper.py +++ b/gen/plone25/wrappers/FlavourWrapper.py @@ -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 diff --git a/gen/plone25/wrappers/__init__.py b/gen/plone25/wrappers/__init__.py index ddca899..115275c 100644 --- a/gen/plone25/wrappers/__init__.py +++ b/gen/plone25/wrappers/__init__.py @@ -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,