Implemented Pod field.
This commit is contained in:
parent
fc75a42264
commit
ecd9c66ca1
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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 ?
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)<=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>
|
||||
</span>
|
||||
|
@ -34,7 +15,7 @@
|
|||
<select tal:condition="python: len(podTemplates)>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', '<br>')"/>
|
||||
<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"
|
||||
|
|
|
@ -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', '')">
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue