292 lines
13 KiB
Python
292 lines
13 KiB
Python
# ------------------------------------------------------------------------------
|
|
import os, os.path, time, types
|
|
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'
|
|
def getPortalType(self, metaTypeOrAppyType):
|
|
'''Returns the name of the portal_type that is based on
|
|
p_metaTypeOrAppyType in this flavour.'''
|
|
res = metaTypeOrAppyType
|
|
isPredefined = False
|
|
isAppy = False
|
|
appName = self.getProductConfig().PROJECTNAME
|
|
if not isinstance(res, basestring):
|
|
res = ArchetypesClassDescriptor.getClassName(res)
|
|
isAppy = True
|
|
if res.find('Extensions_appyWrappers') != -1:
|
|
isPredefined = True
|
|
elems = res.split('_')
|
|
res = '%s%s' % (elems[1], elems[4])
|
|
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Tool):
|
|
# This is the custom tool
|
|
isPredefined = True
|
|
res = '%sTool' % appName
|
|
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Flavour):
|
|
# This is the custom Flavour
|
|
isPredefined = True
|
|
res = '%sFlavour' % appName
|
|
if not isPredefined:
|
|
if self.getNumber() != 1:
|
|
res = '%s_%d' % (res, self.number)
|
|
return res
|
|
|
|
def registerPortalTypes(self):
|
|
'''Registers, into portal_types, the portal types which are specific
|
|
to this flavour.'''
|
|
i = -1
|
|
registeredFactoryTypes = self.portal_factory.getFactoryTypes().keys()
|
|
factoryTypesToRegister = []
|
|
appName = self.getProductConfig().PROJECTNAME
|
|
for metaTypeName in self.allMetaTypes:
|
|
i += 1
|
|
portalTypeName = '%s_%d' % (metaTypeName, self.number)
|
|
# If the portal type corresponding to the meta type is
|
|
# registered in portal_factory (in the model:
|
|
# use_portal_factory=True), we must also register the new
|
|
# portal_type we are currently creating.
|
|
if metaTypeName in registeredFactoryTypes:
|
|
factoryTypesToRegister.append(portalTypeName)
|
|
if not hasattr(self.portal_types, portalTypeName) and \
|
|
hasattr(self.portal_types, metaTypeName):
|
|
# Indeed abstract meta_types have no associated portal_type
|
|
typeInfoName = "%s: %s (%s)" % (appName, metaTypeName,
|
|
metaTypeName)
|
|
self.portal_types.manage_addTypeInformation(
|
|
getattr(self.portal_types, metaTypeName).meta_type,
|
|
id=portalTypeName, typeinfo_name=typeInfoName)
|
|
# Set the human readable title explicitly
|
|
portalType = getattr(self.portal_types, portalTypeName)
|
|
portalType.title = portalTypeName
|
|
# Associate a workflow for this new portal type.
|
|
pf = self.portal_workflow
|
|
workflowChain = pf.getChainForPortalType(metaTypeName)
|
|
pf.setChainForPortalTypes([portalTypeName],workflowChain)
|
|
# Copy actions from the base portal type
|
|
basePortalType = getattr(self.portal_types, metaTypeName)
|
|
portalType._actions = tuple(basePortalType._cloneActions())
|
|
# Copy aliases from the base portal type
|
|
portalType.setMethodAliases(basePortalType.getMethodAliases())
|
|
# Update the factory tool with the list of types to register
|
|
self.portal_factory.manage_setPortalFactoryTypes(
|
|
listOfTypeIds=factoryTypesToRegister+registeredFactoryTypes)
|
|
|
|
def getClassFolder(self, className):
|
|
'''Return the folder related to p_className.'''
|
|
return getattr(self, className)
|
|
|
|
def getAvailablePodTemplates(self, obj, phase='main'):
|
|
'''Returns the POD templates which are available for generating a
|
|
document from p_obj.'''
|
|
appySelf = self.appy()
|
|
fieldName = 'podTemplatesFor%s' % obj.meta_type
|
|
res = []
|
|
podTemplates = getattr(appySelf, fieldName, [])
|
|
if not isinstance(podTemplates, list):
|
|
podTemplates = [podTemplates]
|
|
res = [r.o for r in podTemplates if r.podPhase == phase]
|
|
hasParents = True
|
|
klass = obj.__class__
|
|
while hasParents:
|
|
parent = klass.__bases__[-1]
|
|
if hasattr(parent, 'wrapperClass'):
|
|
fieldName = 'podTemplatesFor%s' % parent.meta_type
|
|
podTemplates = getattr(appySelf, fieldName, [])
|
|
if not isinstance(podTemplates, list):
|
|
podTemplates = [podTemplates]
|
|
res += [r.o for r in podTemplates if r.podPhase == phase]
|
|
klass = parent
|
|
else:
|
|
hasParents = False
|
|
return res
|
|
|
|
def getMaxShownTemplates(self, obj):
|
|
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)
|
|
appyType = ploneObj.getAppyType(fieldName)
|
|
res['title'] = self.translate(appyType['label'])
|
|
res['context'] = appyType['context']
|
|
res['action'] = appyType['action']
|
|
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()
|
|
appyObj = obj.appy()
|
|
# 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)
|
|
specificPodContext = None
|
|
if templateUid:
|
|
podTemplate = self.uid_catalog(UID=templateUid)[0].getObject()
|
|
appyPt = podTemplate.appy()
|
|
format = podTemplate.getPodFormat()
|
|
template = appyPt.podTemplate.content
|
|
podTitle = podTemplate.Title()
|
|
doAction = False
|
|
else:
|
|
fieldName = rq.get('fieldName')
|
|
format = rq.get('podFormat')
|
|
podInfo = self.getPodInfo(obj, fieldName)
|
|
template = podInfo['template'].content
|
|
podTitle = podInfo['title']
|
|
if podInfo['context']:
|
|
if type(podInfo['context']) == types.FunctionType:
|
|
specificPodContext = podInfo['context'](appyObj)
|
|
else:
|
|
specificPodContext = podInfo['context']
|
|
doAction = rq.get('askAction') == 'True'
|
|
# 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, 'self': appyObj,
|
|
'now': self.getProductConfig().DateTime(),
|
|
'projectFolder': appyTool.getDiskFolder(),
|
|
}
|
|
if specificPodContext:
|
|
podContext.update(specificPodContext)
|
|
if templateUid:
|
|
podContext['podTemplate'] = 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)
|
|
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()
|
|
# Execute the related action if relevant
|
|
if doAction and podInfo['action']:
|
|
podInfo['action'](appyObj, podContext)
|
|
# 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
|
|
can't use getattr directly in Zope Page Templates.'''
|
|
return getattr(self, attrName, None)
|
|
|
|
def _appy_getAllFields(self, contentType):
|
|
'''Returns the (translated) names of fields of p_contentType.'''
|
|
res = []
|
|
for attrName in self.getProductConfig().attributes[contentType]:
|
|
if attrName != 'title': # Will be included by default.
|
|
label = '%s_%s' % (contentType, attrName)
|
|
res.append((attrName, self.translate(label)))
|
|
# Add object state
|
|
res.append(('workflowState', self.translate('workflow_state')))
|
|
return res
|
|
|
|
def _appy_getSearchableFields(self, contentType):
|
|
'''Returns the (translated) names of fields that may be searched on
|
|
objects of type p_contentType (=indexed fields).'''
|
|
tool = self.getParentNode()
|
|
appyClass = tool.getAppyClass(contentType)
|
|
attrNames = self.getProductConfig().attributes[contentType]
|
|
res = []
|
|
for attrName in attrNames:
|
|
attr = getattr(appyClass, attrName)
|
|
if isinstance(attr, Type) and attr.indexed:
|
|
label = '%s_%s' % (contentType, attrName)
|
|
res.append((attrName, self.translate(label)))
|
|
return res
|
|
|
|
def getSearchableFields(self, contentType):
|
|
'''Returns, among the list of all searchable fields (see method above),
|
|
the list of fields that the user has configured in the flavour as
|
|
being effectively used in the search screen.'''
|
|
res = []
|
|
appyClass = self.getAppyClass(contentType)
|
|
for attrName in getattr(self, 'searchFieldsFor%s' % contentType):
|
|
attr = getattr(appyClass, attrName)
|
|
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
|
|
res.append((attrName, dAttr))
|
|
return res
|
|
|
|
def getImportElements(self, contentType):
|
|
'''Returns the list of elements that can be imported from p_path for
|
|
p_contentType.'''
|
|
tool = self.getParentNode()
|
|
appyClass = tool.getAppyClass(contentType)
|
|
importParams = tool.getCreateMeans(appyClass)['import']
|
|
onElement = importParams['onElement'].__get__('')
|
|
sortMethod = importParams['sort']
|
|
if sortMethod: sortMethod = sortMethod.__get__('')
|
|
elems = []
|
|
importPath = getattr(self, 'importPathFor%s' % contentType)
|
|
for elem in os.listdir(importPath):
|
|
elemFullPath = os.path.join(importPath, elem)
|
|
elemInfo = onElement(elemFullPath)
|
|
if elemInfo:
|
|
elemInfo.insert(0, elemFullPath) # To the result, I add the full
|
|
# path of the elem, which will not be shown.
|
|
elems.append(elemInfo)
|
|
if sortMethod:
|
|
elems = sortMethod(elems)
|
|
return [importParams['headers'], elems]
|
|
# ------------------------------------------------------------------------------
|