Initial import
This commit is contained in:
commit
4043163fc4
427 changed files with 18387 additions and 0 deletions
42
gen/plone25/mixins/ClassMixin.py
Normal file
42
gen/plone25/mixins/ClassMixin.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ClassMixin(AbstractMixin):
|
||||
_appy_meta_type = 'class'
|
||||
def _appy_fieldIsUsed(self, portalTypeName, fieldName):
|
||||
tool = self.getTool()
|
||||
flavour = tool.getFlavour(portalTypeName)
|
||||
optionalFieldsAccessor = 'getOptionalFieldsFor%s' % self.meta_type
|
||||
exec 'usedFields = flavour.%s()' % optionalFieldsAccessor
|
||||
res = False
|
||||
if fieldName in usedFields:
|
||||
res = True
|
||||
return res
|
||||
|
||||
def _appy_getDefaultValueFor(self, portalTypeName, fieldName):
|
||||
tool = self.getTool()
|
||||
flavour = tool.getFlavour(portalTypeName)
|
||||
fieldFound = False
|
||||
klass = self.__class__
|
||||
while not fieldFound:
|
||||
metaType = klass.meta_type
|
||||
defValueAccessor = 'getDefaultValueFor%s_%s' % (metaType, fieldName)
|
||||
if not hasattr(flavour, defValueAccessor):
|
||||
# The field belongs to a super-class.
|
||||
klass = klass.__bases__[-1]
|
||||
else:
|
||||
fieldFound = True
|
||||
exec 'res = flavour.%s()' % defValueAccessor
|
||||
return res
|
||||
|
||||
def fieldIsUsed(self, fieldName):
|
||||
'''Checks in the corresponding flavour if p_fieldName is used.'''
|
||||
portalTypeName = self._appy_getPortalType(self.REQUEST)
|
||||
return self._appy_fieldIsUsed(portalTypeName, fieldName)
|
||||
|
||||
def getDefaultValueFor(self, fieldName):
|
||||
'''Gets in the flavour the default value for p_fieldName.'''
|
||||
portalTypeName = self._appy_getPortalType(self.REQUEST)
|
||||
return self._appy_getDefaultValueFor(portalTypeName,fieldName)
|
||||
# ------------------------------------------------------------------------------
|
113
gen/plone25/mixins/FlavourMixin.py
Normal file
113
gen/plone25/mixins/FlavourMixin.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import appy.gen
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
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_getWrapper()
|
||||
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.phase==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.phase==phase]
|
||||
klass = parent
|
||||
else:
|
||||
hasParents = False
|
||||
return res
|
||||
|
||||
def getMaxShownTemplates(self, obj):
|
||||
attrName = 'podMaxShownTemplatesFor%s' % obj.meta_type
|
||||
return getattr(self, attrName)
|
||||
|
||||
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)
|
||||
# ------------------------------------------------------------------------------
|
97
gen/plone25/mixins/PodTemplateMixin.py
Normal file
97
gen/plone25/mixins/PodTemplateMixin.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, time
|
||||
from appy.shared import mimeTypes
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
from StringIO import StringIO
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
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'
|
||||
def generateDocument(self, obj):
|
||||
'''Generates a document from this template, for object p_obj.'''
|
||||
appySelf = self._appy_getWrapper(force=True)
|
||||
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_getWrapper(force=True),
|
||||
'user': currentUser,
|
||||
'podTemplate': appySelf,
|
||||
'now': self.getProductConfig().DateTime(),
|
||||
'projectFolder': os.path.dirname(appModule.__file__)
|
||||
}
|
||||
rendererParams = {'template': StringIO(appySelf.podTemplate),
|
||||
'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
|
||||
import appy.pod
|
||||
try:
|
||||
renderer = appy.pod.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')
|
||||
forBrowser = True
|
||||
if forBrowser:
|
||||
# Create a OFS.Image.File object that will manage correclty HTTP
|
||||
# headers, etc.
|
||||
theFile = self.getProductConfig().File('dummyId', 'dummyTitle', f,
|
||||
content_type=mimeTypes[appySelf.podFormat])
|
||||
res = theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
||||
else:
|
||||
# I must return the raw document content.
|
||||
res = f.read()
|
||||
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
|
||||
# ------------------------------------------------------------------------------
|
192
gen/plone25/mixins/ToolMixin.py
Normal file
192
gen/plone25/mixins/ToolMixin.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import re, os, os.path
|
||||
from appy.gen.utils import FieldDescr
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
|
||||
_PY = 'Please specify a file corresponding to a Python interpreter ' \
|
||||
'(ie "/usr/bin/python").'
|
||||
FILE_NOT_FOUND = 'Path "%s" was not found.'
|
||||
VALUE_NOT_FILE = 'Path "%s" is not a file. ' + _PY
|
||||
NO_PYTHON = "Name '%s' does not starts with 'python'. " + _PY
|
||||
NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
|
||||
'To check if a Python interpreter is UNO-enabled, ' \
|
||||
'launch it and type "import uno". If you have no ' \
|
||||
'ImportError exception it is ok.'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ToolMixin(AbstractMixin):
|
||||
_appy_meta_type = 'tool'
|
||||
def _appy_validateUnoEnabledPython(self, value):
|
||||
'''This method represents the validator for field unoEnabledPython.
|
||||
This field is present on the Tool only if POD is needed.'''
|
||||
if value:
|
||||
if not os.path.exists(value):
|
||||
return FILE_NOT_FOUND % value
|
||||
if not os.path.isfile(value):
|
||||
return VALUE_NOT_FILE % value
|
||||
if not os.path.basename(value).startswith('python'):
|
||||
return NO_PYTHON % value
|
||||
if os.system('%s -c "import uno"' % value):
|
||||
return NOT_UNO_ENABLED_PYTHON % value
|
||||
return None
|
||||
|
||||
def getFlavour(self, contextObjOrPortalType, appy=False):
|
||||
'''Gets the flavour that corresponds to p_contextObjOrPortalType.'''
|
||||
if isinstance(contextObjOrPortalType, basestring):
|
||||
portalTypeName = contextObjOrPortalType
|
||||
else:
|
||||
# It is the contextObj, not a portal type name
|
||||
portalTypeName = contextObjOrPortalType.portal_type
|
||||
res = None
|
||||
appyTool = self._appy_getWrapper(force=True)
|
||||
flavourNumber = None
|
||||
nameElems = portalTypeName.split('_')
|
||||
if len(nameElems) > 1:
|
||||
try:
|
||||
flavourNumber = int(nameElems[-1])
|
||||
except ValueError:
|
||||
pass
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if flavourNumber != None:
|
||||
for flavour in appyTool.flavours:
|
||||
if flavourNumber == flavour.number:
|
||||
res = flavour
|
||||
elif portalTypeName == ('%sFlavour' % appName):
|
||||
# Current object is the Flavour itself. In this cas we simply
|
||||
# return the wrapped contextObj. Here we are sure that
|
||||
# contextObjOrPortalType is an object, not a portal type.
|
||||
res = contextObjOrPortalType._appy_getWrapper(force=True)
|
||||
if not res and appyTool.flavours:
|
||||
res = appyTool.flavours[0]
|
||||
# If appy=False, return the Plone object and not the Appy wrapper
|
||||
# (this way, we avoid Zope security/access-related problems while
|
||||
# using this object in Zope Page Templates)
|
||||
if res and not appy:
|
||||
res = res.o
|
||||
return res
|
||||
|
||||
def getFlavoursInfo(self):
|
||||
'''Returns information about flavours.'''
|
||||
res = []
|
||||
appyTool = self._appy_getWrapper(force=True)
|
||||
for flavour in appyTool.flavours:
|
||||
if isinstance(flavour.o, FlavourMixin):
|
||||
# This is a bug: sometimes other objects are associated as
|
||||
# flavours.
|
||||
res.append({'title': flavour.title, 'number':flavour.number})
|
||||
return res
|
||||
|
||||
def getAppFolder(self):
|
||||
'''Returns the folder at the root of the Plone site that is dedicated
|
||||
to this application.'''
|
||||
portal = self.getProductConfig().getToolByName(
|
||||
self, 'portal_url').getPortalObject()
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
return getattr(portal, appName)
|
||||
|
||||
def showPortlet(self):
|
||||
return not self.portal_membership.isAnonymousUser()
|
||||
|
||||
def executeQuery(self, queryName, flavourNumber):
|
||||
if queryName.find(',') != -1:
|
||||
# Several content types are specified
|
||||
portalTypes = queryName.split(',')
|
||||
if flavourNumber != 1:
|
||||
portalTypes = ['%s_%d' % (pt, flavourNumber) \
|
||||
for pt in portalTypes]
|
||||
else:
|
||||
portalTypes = queryName
|
||||
params = {'portal_type': portalTypes, 'batch': True}
|
||||
res = self.portal_catalog.searchResults(**params)
|
||||
batchStart = self.REQUEST.get('b_start', 0)
|
||||
res = self.getProductConfig().Batch(res,
|
||||
self.getNumberOfResultsPerPage(), int(batchStart), orphan=0)
|
||||
return res
|
||||
|
||||
def getResultColumnsNames(self, queryName):
|
||||
contentTypes = queryName.strip(',').split(',')
|
||||
resSet = None # Temporary set for computing intersections.
|
||||
res = [] # Final, sorted result.
|
||||
flavour = None
|
||||
fieldNames = None
|
||||
for cType in contentTypes:
|
||||
# Get the flavour tied to those content types
|
||||
if not flavour:
|
||||
flavour = self.getFlavour(cType, appy=True)
|
||||
if flavour.number != 1:
|
||||
cType = cType.rsplit('_', 1)[0]
|
||||
fieldNames = getattr(flavour, 'resultColumnsFor%s' % cType)
|
||||
if not resSet:
|
||||
resSet = set(fieldNames)
|
||||
else:
|
||||
resSet = resSet.intersection(fieldNames)
|
||||
# By converting to set, we've lost order. Let's put things in the right
|
||||
# order.
|
||||
for fieldName in fieldNames:
|
||||
if fieldName in resSet:
|
||||
res.append(fieldName)
|
||||
return res
|
||||
|
||||
def getResultColumns(self, anObject, queryName):
|
||||
'''What columns must I show when displaying a list of root class
|
||||
instances? Result is a list of tuples containing the name of the
|
||||
column (=name of the field) and a FieldDescr instance.'''
|
||||
res = []
|
||||
for fieldName in self.getResultColumnsNames(queryName):
|
||||
if fieldName == 'workflowState':
|
||||
# We do not return a FieldDescr instance if the attributes is
|
||||
# not a *real* attribute but the workfow state.
|
||||
res.append(fieldName)
|
||||
else:
|
||||
# Create a FieldDescr instance
|
||||
appyType = anObject.getAppyType(fieldName)
|
||||
atField = anObject.schema.get(fieldName)
|
||||
fieldDescr = FieldDescr(atField, appyType, None)
|
||||
res.append(fieldDescr.get())
|
||||
return res
|
||||
|
||||
xhtmlToText = re.compile('<.*?>', re.S)
|
||||
def getReferenceLabel(self, brain, appyType):
|
||||
'''p_appyType is a Ref with link=True. I need to display, on an edit
|
||||
view, the referenced object p_brain in the listbox that will allow
|
||||
the user to choose which object(s) to link through the Ref.
|
||||
According to p_appyType, the label may only be the object title,
|
||||
or more if parameter appyType.shownInfo is used.'''
|
||||
res = brain.Title
|
||||
if 'title' in appyType['shownInfo']:
|
||||
# We may place it at another place
|
||||
res = ''
|
||||
appyObj = brain.getObject()._appy_getWrapper(force=True)
|
||||
for fieldName in appyType['shownInfo']:
|
||||
value = getattr(appyObj, fieldName)
|
||||
if isinstance(value, AbstractWrapper):
|
||||
value = value.title.decode('utf-8')
|
||||
elif isinstance(value, basestring):
|
||||
value = value.decode('utf-8')
|
||||
refAppyType = appyObj.o.getAppyType(fieldName)
|
||||
if refAppyType and (refAppyType['type'] == 'String') and \
|
||||
(refAppyType['format'] == 2):
|
||||
value = self.xhtmlToText.sub(' ', value)
|
||||
else:
|
||||
value = str(value)
|
||||
prefix = ''
|
||||
if res:
|
||||
prefix = ' | '
|
||||
res += prefix + value.encode('utf-8')
|
||||
maxWidth = self.getListBoxesMaximumWidth()
|
||||
if len(res) > maxWidth:
|
||||
res = res[:maxWidth-2] + '...'
|
||||
return res
|
||||
|
||||
translationMapping = {'portal_path': ''}
|
||||
def translateWithMapping(self, label):
|
||||
'''Translates p_label in the application domain, with a default
|
||||
translation mapping.'''
|
||||
if not self.translationMapping['portal_path']:
|
||||
self.translationMapping['portal_path'] = \
|
||||
self.portal_url.getPortalPath()
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
return self.utranslate(label, self.translationMapping, domain=appName)
|
||||
# ------------------------------------------------------------------------------
|
840
gen/plone25/mixins/__init__.py
Normal file
840
gen/plone25/mixins/__init__.py
Normal file
|
@ -0,0 +1,840 @@
|
|||
'''This package contains mixin classes that are mixed in with generated classes:
|
||||
- mixins/ClassMixin is mixed in with Standard Archetypes classes;
|
||||
- mixins/ToolMixin is mixed in with the generated application Tool class;
|
||||
- mixins/FlavourMixin is mixed in with the generated application Flavour
|
||||
class.
|
||||
The AbstractMixin defined hereafter is the base class of any mixin.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, types
|
||||
import appy.gen
|
||||
from appy.gen import String
|
||||
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
||||
ValidationErrors, sequenceTypes
|
||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||
from appy.gen.plone25.utils import updateRolesForPermission, getAppyRequest
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class AbstractMixin:
|
||||
'''Every Archetype class generated by appy.gen inherits from a mixin that
|
||||
inherits from this class. It contains basic functions allowing to
|
||||
minimize the amount of generated code.'''
|
||||
|
||||
def getAppyType(self, fieldName):
|
||||
'''Returns the Appy type corresponding to p_fieldName.'''
|
||||
res = None
|
||||
if fieldName == 'id': return res
|
||||
if self.wrapperClass:
|
||||
baseClass = self.wrapperClass.__bases__[-1]
|
||||
try:
|
||||
# If I get the attr on self instead of baseClass, I get the
|
||||
# property field that is redefined at the wrapper level.
|
||||
appyType = getattr(baseClass, fieldName)
|
||||
res = self._appy_getTypeAsDict(fieldName, appyType, baseClass)
|
||||
except AttributeError:
|
||||
# Check for another parent
|
||||
if self.wrapperClass.__bases__[0].__bases__:
|
||||
baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
|
||||
try:
|
||||
appyType = getattr(baseClass, fieldName)
|
||||
res = self._appy_getTypeAsDict(fieldName, appyType,
|
||||
baseClass)
|
||||
except AttributeError:
|
||||
pass
|
||||
return res
|
||||
|
||||
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
||||
noListIfSingleObj=False):
|
||||
'''p_fieldName is the name of a Ref field. This method returns an
|
||||
ordered list containing the objects linked to p_self through this
|
||||
field. If p_ploneObjects is True, the method returns the "true"
|
||||
Plone objects instead of the Appy wrappers.'''
|
||||
res = []
|
||||
sortedFieldName = '_appy_%s' % fieldName
|
||||
exec 'objs = self.get%s%s()' % (fieldName[0].upper(), fieldName[1:])
|
||||
if objs:
|
||||
if type(objs) != list:
|
||||
objs = [objs]
|
||||
objectsUids = [o.UID() for o in objs]
|
||||
sortedObjectsUids = getattr(self, sortedFieldName)
|
||||
# The list of UIDs may contain too much UIDs; indeed, when deleting
|
||||
# objects, the list of UIDs are not updated.
|
||||
uidsToDelete = []
|
||||
for uid in sortedObjectsUids:
|
||||
try:
|
||||
uidIndex = objectsUids.index(uid)
|
||||
obj = objs[uidIndex]
|
||||
if not ploneObjects:
|
||||
obj = obj._appy_getWrapper(force=True)
|
||||
res.append(obj)
|
||||
except ValueError:
|
||||
uidsToDelete.append(uid)
|
||||
# Delete unused UIDs
|
||||
for uid in uidsToDelete:
|
||||
sortedObjectsUids.remove(uid)
|
||||
if res and noListIfSingleObj:
|
||||
appyType = self.getAppyType(fieldName)
|
||||
if appyType['multiplicity'][1] == 1:
|
||||
res = res[0]
|
||||
return res
|
||||
|
||||
def getAppyRefs(self, fieldName):
|
||||
'''Gets the objects linked to me through p_fieldName.'''
|
||||
return self._appy_getRefs(fieldName, ploneObjects=True)
|
||||
|
||||
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)
|
||||
res = sortedObjectsUids.index(obj.UID())
|
||||
return res
|
||||
|
||||
def getAppyBackRefs(self):
|
||||
'''Returns the list of back references (=types) that are defined for
|
||||
this class.'''
|
||||
className = self.__class__.__name__
|
||||
referers = self.getProductConfig().referers
|
||||
res = []
|
||||
if referers.has_key(className):
|
||||
for appyType, relationship in referers[className]:
|
||||
d = appyType.__dict__
|
||||
d['backd'] = appyType.back.__dict__
|
||||
res.append((d, relationship))
|
||||
return res
|
||||
|
||||
def getAppyRefPortalType(self, fieldName):
|
||||
'''Gets the portal type of objects linked to me through Ref field named
|
||||
p_fieldName.'''
|
||||
appyType = self.getAppyType(fieldName)
|
||||
tool = self.getTool()
|
||||
if self._appy_meta_type == 'flavour':
|
||||
flavour = self._appy_getWrapper(force=True)
|
||||
else:
|
||||
portalTypeName = self._appy_getPortalType(self.REQUEST)
|
||||
flavour = tool.getFlavour(portalTypeName)
|
||||
return self._appy_getAtType(appyType['klass'], flavour)
|
||||
|
||||
def _appy_getOrderedFields(self, isEdit):
|
||||
'''Gets all fields (normal fields, back references, fields to show,
|
||||
fields to hide) in order, in the form of a list of FieldDescr
|
||||
instances.'''
|
||||
orderedFields = []
|
||||
# Browse Archetypes fields
|
||||
for atField in self.Schema().filterFields(isMetadata=0):
|
||||
fieldName = atField.getName()
|
||||
appyType = self.getAppyType(fieldName)
|
||||
if not appyType:
|
||||
if isEdit and (fieldName == 'title'):
|
||||
# We must provide a dummy appy type for it. Else, it will
|
||||
# not be rendered in the "edit" form.
|
||||
appyType = String(multiplicity=(1,1)).__dict__
|
||||
else:
|
||||
continue # Special fields like 'id' are not relevant
|
||||
# Do not display title on view page; it is already in the header
|
||||
if not isEdit and (fieldName=='title'): pass
|
||||
else:
|
||||
orderedFields.append(FieldDescr(atField, appyType, None))
|
||||
# Browse back references
|
||||
for appyType, fieldRel in self.getAppyBackRefs():
|
||||
orderedFields.append(FieldDescr(None, appyType, fieldRel))
|
||||
# If some fields must be moved, do it now
|
||||
res = []
|
||||
for fieldDescr in orderedFields:
|
||||
if fieldDescr.appyType['move']:
|
||||
newPosition = len(res) - abs(fieldDescr.appyType['move'])
|
||||
if newPosition <= 0:
|
||||
newPosition = 0
|
||||
res.insert(newPosition, fieldDescr)
|
||||
else:
|
||||
res.append(fieldDescr)
|
||||
return res
|
||||
|
||||
def showField(self, fieldDescr, isEdit=False):
|
||||
'''Must I show field corresponding to p_fieldDescr?'''
|
||||
if isinstance(fieldDescr, FieldDescr):
|
||||
fieldDescr = fieldDescr.__dict__
|
||||
appyType = fieldDescr['appyType']
|
||||
if isEdit and (appyType['type']=='Ref') and appyType['add']:
|
||||
return False
|
||||
if (fieldDescr['widgetType'] == 'backField') and \
|
||||
not self.getBRefs(fieldDescr['fieldRel']):
|
||||
return False
|
||||
# Do not show field if it is optional and not selected in flavour
|
||||
if appyType['optional']:
|
||||
tool = self.getTool()
|
||||
flavour = tool.getFlavour(self, appy=True)
|
||||
flavourAttrName = 'optionalFieldsFor%s' % self.meta_type
|
||||
flavourAttrValue = getattr(flavour, flavourAttrName, ())
|
||||
if fieldDescr['atField'].getName() not in flavourAttrValue:
|
||||
return False
|
||||
# Check if the user has the permission to view or edit the field
|
||||
if fieldDescr['widgetType'] != 'backField':
|
||||
user = self.portal_membership.getAuthenticatedMember()
|
||||
if isEdit:
|
||||
perm = fieldDescr['atField'].write_permission
|
||||
else:
|
||||
perm = fieldDescr['atField'].read_permission
|
||||
if not user.has_permission(perm, self):
|
||||
return False
|
||||
# Evaluate fieldDescr['show']
|
||||
if callable(fieldDescr['show']):
|
||||
obj = self._appy_getWrapper(force=True)
|
||||
res = fieldDescr['show'](obj)
|
||||
else:
|
||||
res = fieldDescr['show']
|
||||
return res
|
||||
|
||||
def getAppyFields(self, isEdit, page):
|
||||
'''Returns the fields sorted by group. For every field, a dict
|
||||
containing the relevant info needed by the view or edit templates is
|
||||
given.'''
|
||||
res = []
|
||||
groups = {} # The already encountered groups
|
||||
for fieldDescr in self._appy_getOrderedFields(isEdit):
|
||||
# Select only widgets shown on current page
|
||||
if fieldDescr.page != page:
|
||||
continue
|
||||
# Do not take into account hidden fields and fields that can't be
|
||||
# edited through the edit view
|
||||
if not self.showField(fieldDescr, isEdit): continue
|
||||
if not fieldDescr.group:
|
||||
res.append(fieldDescr.get())
|
||||
else:
|
||||
# Have I already met this group?
|
||||
groupName, cols = GroupDescr.getGroupInfo(fieldDescr.group)
|
||||
if not groups.has_key(groupName):
|
||||
groupDescr = GroupDescr(groupName, cols,
|
||||
fieldDescr.appyType['page']).get()
|
||||
groups[groupName] = groupDescr
|
||||
res.append(groupDescr)
|
||||
else:
|
||||
groupDescr = groups[groupName]
|
||||
groupDescr['fields'].append(fieldDescr.get())
|
||||
if groups:
|
||||
for groupDict in groups.itervalues():
|
||||
GroupDescr.computeRows(groupDict)
|
||||
return res
|
||||
|
||||
def getAppyStates(self, phase, currentOnly=False):
|
||||
'''Returns information about the states that are related to p_phase.
|
||||
If p_currentOnly is True, we return the current state, even if not
|
||||
related to p_phase.'''
|
||||
res = []
|
||||
dcWorkflow = self.getWorkflow(appy=False)
|
||||
if not dcWorkflow: return res
|
||||
currentState = self.portal_workflow.getInfoFor(self, 'review_state')
|
||||
if currentOnly:
|
||||
return [StateDescr(currentState,'current').get()]
|
||||
workflow = self.getWorkflow(appy=True)
|
||||
if workflow:
|
||||
stateStatus = 'done'
|
||||
for stateName in workflow._states:
|
||||
if stateName == currentState:
|
||||
stateStatus = 'current'
|
||||
elif stateStatus != 'done':
|
||||
stateStatus = 'future'
|
||||
state = getattr(workflow, stateName)
|
||||
if (state.phase == phase) and \
|
||||
(self._appy_showState(workflow, state.show)):
|
||||
res.append(StateDescr(stateName, stateStatus).get())
|
||||
return res
|
||||
|
||||
def getAppyPage(self, isEdit, phaseInfo, appyName=True):
|
||||
'''On which page am I? p_isEdit indicates if the current page is an
|
||||
edit or consult view. p_phaseInfo indicates the current phase.'''
|
||||
pageAttr = 'pageName'
|
||||
if isEdit:
|
||||
pageAttr = 'fieldset' # Archetypes page name
|
||||
default = phaseInfo['pages'][0]
|
||||
# Default page is the first page of the current phase
|
||||
res = self.REQUEST.get(pageAttr, default)
|
||||
if appyName and (res == 'default'):
|
||||
res = 'main'
|
||||
return res
|
||||
|
||||
def getAppyPages(self, phase='main'):
|
||||
'''Gets the list of pages that are defined for this content type.'''
|
||||
res = []
|
||||
for atField in self.Schema().filterFields(isMetadata=0):
|
||||
appyType = self.getAppyType(atField.getName())
|
||||
if not appyType: continue
|
||||
if (appyType['phase'] == phase) and (appyType['page'] not in res) \
|
||||
and self._appy_showPage(appyType['page'], appyType['pageShow']):
|
||||
res.append(appyType['page'])
|
||||
for appyType, fieldRel in self.getAppyBackRefs():
|
||||
if (appyType['backd']['phase'] == phase) and \
|
||||
(appyType['backd']['page'] not in res) and \
|
||||
self._appy_showPage(appyType['backd']['page'],
|
||||
appyType['backd']['pageShow']):
|
||||
res.append(appyType['backd']['page'])
|
||||
return res
|
||||
|
||||
def getAppyPhases(self, currentOnly=False, fieldset=None, forPlone=False):
|
||||
'''Gets the list of phases that are defined for this content type. If
|
||||
p_currentOnly is True, the search is limited to the current phase.
|
||||
If p_fieldset is not None, the search is limited to the phase
|
||||
corresponding the Plone fieldset whose name is given in this
|
||||
parameter. If p_forPlone=True, among phase info we write Plone
|
||||
fieldset names, which are a bit different from Appy page names.'''
|
||||
# Get the list of phases
|
||||
res = [] # Ordered list of phases
|
||||
phases = {} # Dict of phases
|
||||
for atField in self.Schema().filterFields(isMetadata=0):
|
||||
appyType = self.getAppyType(atField.getName())
|
||||
if not appyType: continue
|
||||
if appyType['phase'] not in phases:
|
||||
phase = PhaseDescr(appyType['phase'],
|
||||
self.getAppyStates(appyType['phase']), forPlone, self)
|
||||
res.append(phase.__dict__)
|
||||
phases[appyType['phase']] = phase
|
||||
else:
|
||||
phase = phases[appyType['phase']]
|
||||
phase.addPage(appyType, self)
|
||||
for appyType, fieldRel in self.getAppyBackRefs():
|
||||
if appyType['backd']['phase'] not in phases:
|
||||
phase = PhaseDescr(appyType['backd']['phase'],
|
||||
self.getAppyStates(appyType['backd']['phase']),
|
||||
forPlone, self)
|
||||
res.append(phase.__dict__)
|
||||
phases[appyType['phase']] = phase
|
||||
else:
|
||||
phase = phases[appyType['backd']['phase']]
|
||||
phase.addPage(appyType['backd'], self)
|
||||
# Remove phases that have no visible page
|
||||
for i in range(len(res)-1, -1, -1):
|
||||
if not res[i]['pages']:
|
||||
del phases[res[i]['name']]
|
||||
del res[i]
|
||||
# Then, compute status of phases
|
||||
for ph in phases.itervalues():
|
||||
ph.computeStatus()
|
||||
ph.totalNbOfPhases = len(res)
|
||||
# Restrict the result if we must not produce the whole list of phases
|
||||
if currentOnly:
|
||||
for phaseInfo in res:
|
||||
if phaseInfo['phaseStatus'] == 'Current':
|
||||
return phaseInfo
|
||||
elif fieldset:
|
||||
for phaseInfo in res:
|
||||
if fieldset in phaseInfo['pages']:
|
||||
return phaseInfo
|
||||
else:
|
||||
return res
|
||||
|
||||
def changeAppyRefOrder(self, fieldName, objectUid, newIndex, isDelta):
|
||||
'''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)
|
||||
oldIndex = sortedObjectsUids.index(objectUid)
|
||||
sortedObjectsUids.remove(objectUid)
|
||||
if isDelta:
|
||||
newIndex = oldIndex + newIndex
|
||||
else:
|
||||
pass # To implement later on
|
||||
sortedObjectsUids.insert(newIndex, objectUid)
|
||||
|
||||
def getWorkflow(self, appy=True):
|
||||
'''Returns the Appy workflow instance that is relevant for this
|
||||
object. If p_appy is False, it returns the DC workflow.'''
|
||||
res = None
|
||||
if appy:
|
||||
# Get the workflow class first
|
||||
workflowClass = None
|
||||
if self.wrapperClass:
|
||||
appyClass = self.wrapperClass.__bases__[1]
|
||||
if hasattr(appyClass, 'workflow'):
|
||||
workflowClass = appyClass.workflow
|
||||
if workflowClass:
|
||||
# Get the corresponding prototypical workflow instance
|
||||
res = self.getProductConfig().workflowInstances[workflowClass]
|
||||
else:
|
||||
dcWorkflows = self.portal_workflow.getWorkflowsFor(self)
|
||||
if dcWorkflows:
|
||||
res = dcWorkflows[0]
|
||||
return res
|
||||
|
||||
def getWorkflowLabel(self, stateName=None):
|
||||
'''Gets the i18n label for the workflow current state. If no p_stateName
|
||||
is given, workflow label is given for the current state.'''
|
||||
res = ''
|
||||
wf = self.getWorkflow(appy=False)
|
||||
if wf:
|
||||
res = stateName
|
||||
if not res:
|
||||
res = self.portal_workflow.getInfoFor(self, 'review_state')
|
||||
appyWf = self.getWorkflow(appy=True)
|
||||
if appyWf:
|
||||
res = '%s_%s' % (wf.id, res)
|
||||
return res
|
||||
|
||||
def getComputedValue(self, appyType):
|
||||
'''Computes on p_self the value of the Computed field corresponding to
|
||||
p_appyType.'''
|
||||
res = ''
|
||||
obj = self._appy_getWrapper(force=True)
|
||||
if appyType['method']:
|
||||
try:
|
||||
res = appyType['method'](obj)
|
||||
if not isinstance(res, basestring):
|
||||
res = repr(res)
|
||||
except Exception, e:
|
||||
res = str(e)
|
||||
return res
|
||||
|
||||
def may(self, transitionName):
|
||||
'''May the user execute transition named p_transitionName?'''
|
||||
# Get the Appy workflow instance
|
||||
workflow = self.getWorkflow()
|
||||
res = False
|
||||
if workflow:
|
||||
# Get the corresponding Appy transition
|
||||
transition = workflow._transitionsMapping[transitionName]
|
||||
user = self.portal_membership.getAuthenticatedMember()
|
||||
if isinstance(transition.condition, basestring):
|
||||
# It is a role. Transition may be triggered if the user has this
|
||||
# role.
|
||||
res = user.has_role(transition.condition, self)
|
||||
elif type(transition.condition) == types.FunctionType:
|
||||
obj = self._appy_getWrapper()
|
||||
res = transition.condition(workflow, obj)
|
||||
elif type(transition.condition) in (tuple, list):
|
||||
# It is a list of roles and or functions. Transition may be
|
||||
# triggered if user has at least one of those roles and if all
|
||||
# functions return True.
|
||||
hasRole = None
|
||||
for roleOrFunction in transition.condition:
|
||||
if isinstance(roleOrFunction, basestring):
|
||||
if hasRole == None:
|
||||
hasRole = False
|
||||
if user.has_role(roleOrFunction, self):
|
||||
hasRole = True
|
||||
elif type(roleOrFunction) == types.FunctionType:
|
||||
obj = self._appy_getWrapper()
|
||||
if not roleOrFunction(workflow, obj):
|
||||
return False
|
||||
if hasRole != False:
|
||||
res = True
|
||||
return res
|
||||
|
||||
def executeAppyAction(self, actionName, reindex=True):
|
||||
'''Executes action with p_fieldName on this object.'''
|
||||
appyClass = self.wrapperClass.__bases__[1]
|
||||
res = getattr(appyClass, actionName)(self._appy_getWrapper(force=True))
|
||||
self.reindexObject()
|
||||
return res
|
||||
|
||||
def callAppySelect(self, selectMethod, brains):
|
||||
'''Selects objects from a Reference field.'''
|
||||
if selectMethod:
|
||||
obj = self._appy_getWrapper(force=True)
|
||||
allObjects = [b.getObject()._appy_getWrapper() \
|
||||
for b in brains]
|
||||
filteredObjects = selectMethod(obj, allObjects)
|
||||
filteredUids = [o.o.UID() for o in filteredObjects]
|
||||
res = []
|
||||
for b in brains:
|
||||
if b.UID in filteredUids:
|
||||
res.append(b)
|
||||
else:
|
||||
res = brains
|
||||
return res
|
||||
|
||||
def getCssClasses(self, appyType, asSlave=True):
|
||||
'''Gets the CSS classes (used for master/slave relationships) for this
|
||||
object, either as slave (p_asSlave=True) either as master. The HTML
|
||||
element on which to define the CSS class for a slave or a master is
|
||||
different. So this method is called either for getting CSS classes
|
||||
as slave or as master.'''
|
||||
res = ''
|
||||
if not asSlave and appyType['slaves']:
|
||||
res = 'appyMaster master_%s' % appyType['id']
|
||||
elif asSlave and appyType['master']:
|
||||
res = 'slave_%s' % appyType['master'].id
|
||||
res += ' slaveValue_%s_%s' % (appyType['master'].id,
|
||||
appyType['masterValue'])
|
||||
return res
|
||||
|
||||
def fieldValueSelected(self, fieldName, value, vocabValue):
|
||||
'''When displaying a selection box (ie a String with a validator being a
|
||||
list), must the _vocabValue appear as selected?'''
|
||||
# Check according to database value
|
||||
if (type(value) in sequenceTypes):
|
||||
if vocabValue in value: return True
|
||||
else:
|
||||
if vocabValue == value: return True
|
||||
# Check according to value in request
|
||||
valueInReq = self.REQUEST.get(fieldName, None)
|
||||
if type(valueInReq) in sequenceTypes:
|
||||
if vocabValue in valueInReq: return True
|
||||
else:
|
||||
if vocabValue == valueInReq: return True
|
||||
return False
|
||||
|
||||
def checkboxChecked(self, fieldName, value):
|
||||
'''When displaying a checkbox, must it be checked or not?'''
|
||||
valueInReq = self.REQUEST.get(fieldName, None)
|
||||
if valueInReq != None:
|
||||
return valueInReq in ('True', 1, '1')
|
||||
else:
|
||||
return value
|
||||
|
||||
def getLabelPrefix(self, fieldName=None):
|
||||
'''For some i18n labels, wee need to determine a prefix, which may be
|
||||
linked to p_fieldName. Indeed, the prefix may be based on the name
|
||||
of the (super-)class where p_fieldName is defined.'''
|
||||
res = self.meta_type
|
||||
if fieldName:
|
||||
appyType = self.getAppyType(fieldName)
|
||||
res = '%s_%s' % (self._appy_getAtType(appyType['selfClass']),
|
||||
fieldName)
|
||||
return res
|
||||
|
||||
def _appy_getWrapper(self, force=False):
|
||||
'''Returns the wrapper object for p_self. It is created if it did not
|
||||
exist.'''
|
||||
if (not hasattr(self.aq_base, 'appyWrapper')) or force:
|
||||
# In some cases (p_force=True), we need to re-generate the
|
||||
# wrapper object. Else, acquisition may be lost on wrapper.o.
|
||||
self.appyWrapper = self.wrapperClass(self)
|
||||
return self.appyWrapper
|
||||
|
||||
def _appy_getSourceClass(self, fieldName, baseClass):
|
||||
'''We know that p_fieldName was defined on Python class p_baseClass or
|
||||
one of its parents. This method returns the exact class (p_baseClass
|
||||
or a parent) where it was defined.'''
|
||||
if fieldName in baseClass.__dict__:
|
||||
return baseClass
|
||||
else:
|
||||
return self._appy_getSourceClass(fieldName, baseClass.__bases__[0])
|
||||
|
||||
def _appy_getTypeAsDict(self, fieldName, appyType, baseClass):
|
||||
'''Within page templates, the appyType is given as a dict instead of
|
||||
an object in order to avoid security problems.'''
|
||||
appyType.selfClass = self._appy_getSourceClass(fieldName, baseClass)
|
||||
res = appyType.__dict__
|
||||
if res.has_key('back') and res['back'] and (not res.has_key('backd')):
|
||||
res['backd'] = res['back'].__dict__
|
||||
# I create a new entry "backd"; if I put the dict in "back" I
|
||||
# really modify the initial appyType object and I don't want to do
|
||||
# this.
|
||||
return res
|
||||
|
||||
def _appy_getAtType(self, appyClass, flavour=None):
|
||||
'''Gets the name of the Archetypes class that corresponds to
|
||||
p_appyClass (which is a Python class coming from the user
|
||||
application). If p_flavour is specified, the method returns the name
|
||||
of the specific Archetypes class in this flavour (ie suffixed with
|
||||
the flavour number).'''
|
||||
res = ArchetypesClassDescriptor.getClassName(appyClass)
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if res.find('Extensions_appyWrappers') != -1:
|
||||
# This is not a content type defined Maybe I am a tool or flavour
|
||||
res = appName + appyClass.__name__
|
||||
elif issubclass(appyClass, appy.gen.Tool):
|
||||
# This is the custom tool
|
||||
res = '%sTool' % appName
|
||||
elif issubclass(appyClass, appy.gen.Flavour):
|
||||
# This is the custom Flavour
|
||||
res = '%sFlavour' % appName
|
||||
else:
|
||||
if flavour and flavour.number != 1:
|
||||
res += '_%d' % flavour.number
|
||||
return res
|
||||
|
||||
def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False,
|
||||
noListIfSingleObj=False):
|
||||
'''This method returns the list of objects linked to this one
|
||||
through the BackRef corresponding to the Archetypes
|
||||
relationship named p_relName.'''
|
||||
res = []
|
||||
referers = self.getProductConfig().referers
|
||||
objs = self.getBRefs(relName)
|
||||
for obj in objs:
|
||||
if not ploneObjects:
|
||||
obj = obj._appy_getWrapper(force=True)
|
||||
res.append(obj)
|
||||
if res and noListIfSingleObj:
|
||||
className = self.__class__.__name__
|
||||
appyType = None
|
||||
for anAppyType, rel in referers[className]:
|
||||
if rel == relName:
|
||||
appyType = anAppyType
|
||||
break
|
||||
if appyType.back.multiplicity[1] == 1:
|
||||
res = res[0]
|
||||
return res
|
||||
|
||||
def _appy_showPage(self, page, pageShow):
|
||||
'''Must I show p_page?'''
|
||||
if callable(pageShow):
|
||||
return pageShow(self._appy_getWrapper(force=True))
|
||||
else: return pageShow
|
||||
|
||||
def _appy_showState(self, workflow, stateShow):
|
||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
||||
if callable(stateShow):
|
||||
return stateShow(workflow, self._appy_getWrapper())
|
||||
else: return stateShow
|
||||
|
||||
def _appy_managePermissions(self):
|
||||
'''When an object is created or updated, we must update "add"
|
||||
permissions accordingly: if the object is a folder, we must set on
|
||||
it permissions that will allow to create, inside it, objects through
|
||||
Ref fields; if it is not a folder, we must update permissions on its
|
||||
parent folder instead.'''
|
||||
# Determine on which folder we need to set "add" permissions
|
||||
folder = self
|
||||
if not self.isPrincipiaFolderish:
|
||||
folder = self.getParentNode()
|
||||
# On this folder, set "add" permissions for every content type that will
|
||||
# be created through reference fields
|
||||
allCreators = set()
|
||||
for field in self.schema.fields():
|
||||
if field.type == 'reference':
|
||||
refContentTypeName= self.getAppyRefPortalType(field.getName())
|
||||
refContentType = getattr(self.portal_types, refContentTypeName)
|
||||
refMetaType = refContentType.content_meta_type
|
||||
if refMetaType in self.getProductConfig(\
|
||||
).ADD_CONTENT_PERMISSIONS:
|
||||
# No specific "add" permission is defined for tool and
|
||||
# flavour, for example.
|
||||
appyClass = refContentType.wrapperClass.__bases__[-1]
|
||||
# Get roles that may add this content type
|
||||
creators = getattr(appyClass, 'creators', None)
|
||||
if not creators:
|
||||
creators = self.getProductConfig().defaultAddRoles
|
||||
allCreators = allCreators.union(creators)
|
||||
# Grant this "add" permission to those roles
|
||||
updateRolesForPermission(
|
||||
self.getProductConfig().ADD_CONTENT_PERMISSIONS[\
|
||||
refMetaType], creators, folder)
|
||||
# Beyond content-type-specific "add" permissions, creators must also
|
||||
# have the main permission "Add portal content".
|
||||
if allCreators:
|
||||
updateRolesForPermission('Add portal content', tuple(allCreators),
|
||||
folder)
|
||||
|
||||
def _appy_onEdit(self, created):
|
||||
'''What happens when an object is created (p_created=True) or edited?'''
|
||||
# Manage references
|
||||
self._appy_manageRefs(created)
|
||||
if self.wrapperClass:
|
||||
# Get the wrapper first
|
||||
appyWrapper = self._appy_getWrapper(force=True)
|
||||
# Call the custom "onEdit" if available
|
||||
try:
|
||||
appyWrapper.onEdit(created)
|
||||
except AttributeError, ae:
|
||||
pass
|
||||
# Manage "add" permissions
|
||||
self._appy_managePermissions()
|
||||
# Re/unindex object
|
||||
if self._appy_meta_type == 'tool': self.unindexObject()
|
||||
else: self.reindexObject()
|
||||
|
||||
def _appy_getDisplayList(self, values, labels, domain):
|
||||
'''Creates a DisplayList given a list of p_values and corresponding
|
||||
i18n p_labels.'''
|
||||
res = []
|
||||
i = -1
|
||||
for v in values:
|
||||
i += 1
|
||||
res.append( (v, self.utranslate(labels[i], domain=domain)))
|
||||
return self.getProductConfig().DisplayList(tuple(res))
|
||||
|
||||
nullValues = (None, '', ' ')
|
||||
numbersMap = {'Integer': 'int', 'Float': 'float'}
|
||||
validatorTypes = (types.FunctionType, type(String.EMAIL))
|
||||
def _appy_validateField(self, fieldName, value, label, specificType):
|
||||
'''Checks whether the p_value entered in field p_fieldName is
|
||||
correct.'''
|
||||
appyType = self.getAppyType(fieldName)
|
||||
msgId = None
|
||||
if (specificType == 'Ref') and appyType['link']:
|
||||
# We only check "link" Refs because in edit views, "add" Refs are
|
||||
# not visible. So if we check "add" Refs, on an "edit" view we will
|
||||
# believe that that there is no referred object even if there is.
|
||||
# If the field is a reference, appy must ensure itself that
|
||||
# multiplicities are enforced.
|
||||
fieldValue = self.REQUEST.get('appy_ref_%s' % fieldName, '')
|
||||
if not fieldValue:
|
||||
nbOfRefs = 0
|
||||
elif isinstance(fieldValue, basestring):
|
||||
nbOfRefs = 1
|
||||
else:
|
||||
nbOfRefs = len(fieldValue)
|
||||
minRef = appyType['multiplicity'][0]
|
||||
maxRef = appyType['multiplicity'][1]
|
||||
if maxRef == None:
|
||||
maxRef = sys.maxint
|
||||
if nbOfRefs < minRef:
|
||||
msgId = 'min_ref_violated'
|
||||
elif nbOfRefs > maxRef:
|
||||
msgId = 'max_ref_violated'
|
||||
elif specificType in self.numbersMap: # Float, Integer
|
||||
pyType = self.numbersMap[specificType]
|
||||
# Validate only if input value is there.
|
||||
# By the way, we also convert the value.
|
||||
if value not in self.nullValues:
|
||||
try:
|
||||
exec 'value = %s(value)' % pyType
|
||||
except ValueError:
|
||||
msgId = 'bad_%s' % pyType
|
||||
else:
|
||||
value = None
|
||||
# Apply the custom validator if it exists
|
||||
validator = appyType['validator']
|
||||
if not msgId and (type(validator) in self.validatorTypes):
|
||||
obj = self._appy_getWrapper(force=True)
|
||||
if type(validator) == self.validatorTypes[0]:
|
||||
# It is a custom function. Execute it.
|
||||
try:
|
||||
validValue = validator(obj, value)
|
||||
if isinstance(validValue, basestring) and validValue:
|
||||
# Validation failed; and p_validValue contains an error
|
||||
# message.
|
||||
return validValue
|
||||
else:
|
||||
if not validValue:
|
||||
msgId = label
|
||||
except Exception, e:
|
||||
return str(e)
|
||||
except:
|
||||
msgId = label
|
||||
elif type(validator) == self.validatorTypes[1]:
|
||||
# It is a regular expression
|
||||
if (value not in self.nullValues) and \
|
||||
not validator.match(value):
|
||||
# If the regular expression is among the default ones, we
|
||||
# generate a specific error message.
|
||||
if validator == String.EMAIL:
|
||||
msgId = 'bad_email'
|
||||
elif validator == String.URL:
|
||||
msgId = 'bad_url'
|
||||
elif validator == String.ALPHANUMERIC:
|
||||
msgId = 'bad_alphanumeric'
|
||||
else:
|
||||
msgId = label
|
||||
res = msgId
|
||||
if msgId:
|
||||
res = self.utranslate(msgId, domain=self.i18nDomain)
|
||||
return res
|
||||
|
||||
def _appy_validateAllFields(self, REQUEST, errors):
|
||||
'''This method is called when individual validation of all fields
|
||||
succeed (when editing or creating an object). Then, this method
|
||||
performs inter-field validation. This way, the user must first
|
||||
correct individual fields before being confronted to potential
|
||||
inter-fields validation errors.'''
|
||||
obj = self._appy_getWrapper()
|
||||
appyRequest = getAppyRequest(REQUEST, obj)
|
||||
try:
|
||||
appyErrors = ValidationErrors()
|
||||
obj.validate(appyRequest, appyErrors)
|
||||
# This custom "validate" method may have added fields in the given
|
||||
# ValidationErrors instance. Now we must fill the Zope "errors" dict
|
||||
# based on it. For every error message that is not a string,
|
||||
# we replace it with the standard validation error for the
|
||||
# corresponding field.
|
||||
for key, value in appyErrors.__dict__.iteritems():
|
||||
resValue = value
|
||||
if not isinstance(resValue, basestring):
|
||||
msgId = '%s_valid' % self.getLabelPrefix(key)
|
||||
resValue = self.utranslate(msgId, domain=self.i18nDomain)
|
||||
errors[key] = resValue
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _appy_getPortalType(self, request):
|
||||
'''Guess the portal_type of p_self from info about p_self and
|
||||
p_request.'''
|
||||
res = None
|
||||
# If the object is being created, self.portal_type is not correctly
|
||||
# initialized yet.
|
||||
if request.has_key('__factory__info__'):
|
||||
factoryInfo = request['__factory__info__']
|
||||
if factoryInfo.has_key('stack'):
|
||||
res = factoryInfo['stack'][0]
|
||||
if not res:
|
||||
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_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.
|
||||
if created:
|
||||
session = self.REQUEST.SESSION
|
||||
initiatorUid = session.get('initiator', None)
|
||||
initiator = None
|
||||
if initiatorUid:
|
||||
initiatorRes = self.uid_catalog.searchResults(UID=initiatorUid)
|
||||
if initiatorRes:
|
||||
initiator = initiatorRes[0].getObject()
|
||||
if initiator:
|
||||
fieldName = session.get('initiatorField')
|
||||
initiator._appy_getWrapper(force=True).link(fieldName, self)
|
||||
# Re-initialise the session
|
||||
session['initiator'] = None
|
||||
|
||||
def _appy_manageRefsFromRequest(self):
|
||||
'''Appy manages itself some Ref fields (with link=True). So here we must
|
||||
update the Ref fields.'''
|
||||
fieldsInRequest = [] # Fields present in the request
|
||||
for requestKey in self.REQUEST.keys():
|
||||
if requestKey.startswith('appy_ref_'):
|
||||
fieldName = requestKey[9:]
|
||||
fieldsInRequest.append(fieldName)
|
||||
fieldValue = self.REQUEST[requestKey]
|
||||
sortedRefField = getattr(self, '_appy_%s' % fieldName)
|
||||
del sortedRefField[:]
|
||||
if isinstance(fieldValue, basestring):
|
||||
fieldValue = [fieldValue]
|
||||
refObjects = []
|
||||
for uid in fieldValue:
|
||||
obj = self.uid_catalog(UID=uid)[0].getObject()
|
||||
refObjects.append(obj)
|
||||
sortedRefField.append(uid)
|
||||
exec 'self.set%s%s(refObjects)' % (fieldName[0].upper(),
|
||||
fieldName[1:])
|
||||
# Manage Ref fields that are not present in the request
|
||||
currentFieldset = self.REQUEST.get('fieldset', 'default')
|
||||
for field in self.schema.fields():
|
||||
if (field.type == 'reference') and \
|
||||
(field.schemata == currentFieldset) and \
|
||||
(field.getName() not in fieldsInRequest):
|
||||
# If this field is visible, it was not present in the request:
|
||||
# it means that we must remove any Ref from it.
|
||||
fieldName = field.getName()
|
||||
appyType = self.getAppyType(fieldName)
|
||||
fieldDescr = FieldDescr(field, appyType, None)
|
||||
if self.showField(fieldDescr, isEdit=True):
|
||||
exec 'self.set%s%s([])' % (fieldName[0].upper(),
|
||||
fieldName[1:])
|
||||
# ------------------------------------------------------------------------------
|
Loading…
Add table
Add a link
Reference in a new issue