Eradicated Flavour and PodTemplate classes (for the latter, use Pod fields instead); Added a code analyser; Groups can now be slaves in master/slaves relationships; Refs have more params (show a confirmation popup before adding an object, add an object without creation form); Code for Refs has been refactored to comply with the new way to organize Types; Added a WebDAV client library.
This commit is contained in:
parent
9f4db88bdf
commit
990e16c6e7
47 changed files with 1006 additions and 1297 deletions
|
@ -1,7 +0,0 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ClassMixin(AbstractMixin):
|
||||
_appy_meta_type = 'Class'
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,263 +0,0 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
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 ClassDescriptor
|
||||
|
||||
# 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, metaTypeOrAppyClass):
|
||||
'''Returns the name of the portal_type that is based on
|
||||
p_metaTypeOrAppyType in this flavour.'''
|
||||
return self.getParentNode().getPortalType(metaTypeOrAppyClass)
|
||||
|
||||
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.labelId)
|
||||
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, name):
|
||||
'''Gets on this flavour attribute named p_attrName. Useful because we
|
||||
can't use getattr directly in Zope Page Templates.'''
|
||||
return getattr(self.appy(), name, None)
|
||||
|
||||
def _appy_getAllFields(self, contentType):
|
||||
'''Returns the (translated) names of fields of p_contentType.'''
|
||||
res = []
|
||||
for appyType in self.getProductConfig().attributes[contentType]:
|
||||
if appyType.name != 'title': # Will be included by default.
|
||||
label = '%s_%s' % (contentType, appyType.name)
|
||||
res.append((appyType.name, 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).'''
|
||||
res = []
|
||||
for appyType in self.getProductConfig().attributes[contentType]:
|
||||
if appyType.indexed:
|
||||
res.append((appyType.name, self.translate(appyType.labelId)))
|
||||
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 = []
|
||||
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
|
||||
for name in fieldNames:
|
||||
appyType = self.getAppyType(name, asDict=True,className=contentType)
|
||||
res.append(appyType)
|
||||
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]
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,7 +0,0 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class PodTemplateMixin(AbstractMixin):
|
||||
_appy_meta_type = 'PodTemplate'
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,22 +1,28 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import re, os, os.path, Cookie
|
||||
import re, os, os.path, time, Cookie, StringIO, types
|
||||
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, Search, Selection
|
||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||
from appy.gen.plone25.mixins import BaseMixin
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||
|
||||
# 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.'
|
||||
jsMessages = ('no_elem_selected', 'delete_confirm')
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ToolMixin(AbstractMixin):
|
||||
class ToolMixin(BaseMixin):
|
||||
_appy_meta_type = 'Tool'
|
||||
def getPortalType(self, metaTypeOrAppyClass):
|
||||
'''Returns the name of the portal_type that is based on
|
||||
p_metaTypeOrAppyType in this flavour.'''
|
||||
p_metaTypeOrAppyType.'''
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if not isinstance(metaTypeOrAppyClass, basestring):
|
||||
res = getClassName(metaTypeOrAppyClass, appName)
|
||||
|
@ -25,52 +31,100 @@ class ToolMixin(AbstractMixin):
|
|||
res = '%s%s' % (elems[1], elems[4])
|
||||
return res
|
||||
|
||||
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
|
||||
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.getAppyClass(ploneObj.meta_type)
|
||||
appyTool = self.appy()
|
||||
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()
|
||||
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
|
||||
n = appyTool.getAttributeName('formats', appyClass, fieldName)
|
||||
res['formats'] = getattr(appyTool, n)
|
||||
n = appyTool.getAttributeName('podTemplate', appyClass, fieldName)
|
||||
res['template'] = getattr(appyTool, n)
|
||||
appyType = ploneObj.getAppyType(fieldName)
|
||||
res['title'] = self.translate(appyType.labelId)
|
||||
res['context'] = appyType.context
|
||||
res['action'] = appyType.action
|
||||
return res
|
||||
|
||||
def getFlavoursInfo(self):
|
||||
'''Returns information about flavours.'''
|
||||
res = []
|
||||
def generateDocument(self):
|
||||
'''Generates the document from field-related info. UID of object that
|
||||
is the template target is given in the request.'''
|
||||
rq = self.REQUEST
|
||||
appyTool = self.appy()
|
||||
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})
|
||||
# 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.
|
||||
specificPodContext = None
|
||||
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, 'user': currentUser, 'self': appyObj,
|
||||
'now': self.getProductConfig().DateTime(),
|
||||
'projectFolder': appyTool.getDiskFolder(),
|
||||
}
|
||||
if specificPodContext:
|
||||
podContext.update(specificPodContext)
|
||||
rendererParams = {'template': StringIO.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, name):
|
||||
'''Gets attribute named p_attrName. Useful because we can't use getattr
|
||||
directly in Zope Page Templates.'''
|
||||
return getattr(self.appy(), name, None)
|
||||
|
||||
def getAppName(self):
|
||||
'''Returns the name of this application.'''
|
||||
return self.getProductConfig().PROJECTNAME
|
||||
|
@ -86,6 +140,58 @@ class ToolMixin(AbstractMixin):
|
|||
'''Returns the list of root classes for this application.'''
|
||||
return self.getProductConfig().rootClasses
|
||||
|
||||
def _appy_getAllFields(self, contentType):
|
||||
'''Returns the (translated) names of fields of p_contentType.'''
|
||||
res = []
|
||||
for appyType in self.getProductConfig().attributes[contentType]:
|
||||
if appyType.name != 'title': # Will be included by default.
|
||||
label = '%s_%s' % (contentType, appyType.name)
|
||||
res.append((appyType.name, 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).'''
|
||||
res = []
|
||||
for appyType in self.getProductConfig().attributes[contentType]:
|
||||
if appyType.indexed:
|
||||
res.append((appyType.name, self.translate(appyType.labelId)))
|
||||
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 as being effectively
|
||||
used in the search screen.'''
|
||||
res = []
|
||||
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
|
||||
for name in fieldNames:
|
||||
appyType = self.getAppyType(name, asDict=True,className=contentType)
|
||||
res.append(appyType)
|
||||
return res
|
||||
|
||||
def getImportElements(self, contentType):
|
||||
'''Returns the list of elements that can be imported from p_path for
|
||||
p_contentType.'''
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
importParams = self.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]
|
||||
|
||||
def showPortlet(self, context):
|
||||
if self.portal_membership.isAnonymousUser(): return False
|
||||
if context.id == 'skyn': context = context.getParentNode()
|
||||
|
@ -106,15 +212,13 @@ class ToolMixin(AbstractMixin):
|
|||
res = res.appy()
|
||||
return res
|
||||
|
||||
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
||||
startNumber=0, search=None, remember=False,
|
||||
brainsOnly=False, maxResults=None, noSecurity=False,
|
||||
sortBy=None, sortOrder='asc',
|
||||
filterKey=None, filterValue=None):
|
||||
def executeQuery(self, contentType, searchName=None, startNumber=0,
|
||||
search=None, remember=False, brainsOnly=False,
|
||||
maxResults=None, noSecurity=False, sortBy=None,
|
||||
sortOrder='asc', filterKey=None, filterValue=None):
|
||||
'''Executes a query on a given p_contentType (or several, separated
|
||||
with commas) in Plone's portal_catalog. Portal types are from the
|
||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||
corresponds to:
|
||||
with commas) in Plone's portal_catalog. If p_searchName is specified,
|
||||
it corresponds to:
|
||||
1) a search defined on p_contentType: additional search criteria
|
||||
will be added to the query, or;
|
||||
2) "_advanced": in this case, additional search criteria will also
|
||||
|
@ -150,11 +254,7 @@ class ToolMixin(AbstractMixin):
|
|||
p_filterValue.'''
|
||||
# Is there one or several content types ?
|
||||
if contentType.find(',') != -1:
|
||||
# Several content types are specified
|
||||
portalTypes = contentType.split(',')
|
||||
if flavourNumber != 1:
|
||||
portalTypes = ['%s_%d' % (pt, flavourNumber) \
|
||||
for pt in portalTypes]
|
||||
else:
|
||||
portalTypes = contentType
|
||||
params = {'portal_type': portalTypes}
|
||||
|
@ -164,8 +264,7 @@ class ToolMixin(AbstractMixin):
|
|||
# In this case, contentType must contain a single content type.
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
if searchName != '_advanced':
|
||||
search = ClassDescriptor.getSearch(
|
||||
appyClass, searchName)
|
||||
search = ClassDescriptor.getSearch(appyClass, searchName)
|
||||
else:
|
||||
fields = self.REQUEST.SESSION['searchCriteria']
|
||||
search = Search('customSearch', **fields)
|
||||
|
@ -220,22 +319,17 @@ class ToolMixin(AbstractMixin):
|
|||
for obj in res.objects:
|
||||
i += 1
|
||||
uids[startNumber+i] = obj.UID()
|
||||
s['search_%s_%s' % (flavourNumber, searchName)] = uids
|
||||
s['search_%s' % searchName] = uids
|
||||
return res.__dict__
|
||||
|
||||
def getResultColumnsNames(self, contentType):
|
||||
contentTypes = contentType.strip(',').split(',')
|
||||
resSet = None # Temporary set for computing intersections.
|
||||
res = [] # Final, sorted result.
|
||||
flavour = None
|
||||
fieldNames = None
|
||||
appyTool = self.appy()
|
||||
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)
|
||||
fieldNames = getattr(appyTool, 'resultColumnsFor%s' % cType)
|
||||
if not resSet:
|
||||
resSet = set(fieldNames)
|
||||
else:
|
||||
|
@ -483,9 +577,9 @@ class ToolMixin(AbstractMixin):
|
|||
attrValue = oper.join(attrValue)
|
||||
criteria[attrName[2:]] = attrValue
|
||||
rq.SESSION['searchCriteria'] = criteria
|
||||
# Goto the screen that displays search results
|
||||
backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \
|
||||
(os.path.dirname(rq['URL']), rq['type_name'], rq['flavourNumber'])
|
||||
# Go to the screen that displays search results
|
||||
backUrl = '%s/query?type_name=%s&&search=_advanced' % \
|
||||
(os.path.dirname(rq['URL']), rq['type_name'])
|
||||
return self.goto(backUrl)
|
||||
|
||||
def getJavascriptMessages(self):
|
||||
|
@ -535,13 +629,12 @@ class ToolMixin(AbstractMixin):
|
|||
if cookieValue: return cookieValue.value
|
||||
return default
|
||||
|
||||
def getQueryUrl(self, contentType, flavourNumber, searchName,
|
||||
startNumber=None):
|
||||
def getQueryUrl(self, contentType, searchName, startNumber=None):
|
||||
'''This method creates the URL that allows to perform a (non-Ajax)
|
||||
request for getting queried objects from a search named p_searchName
|
||||
on p_contentType from flavour numbered p_flavourNumber.'''
|
||||
on p_contentType.'''
|
||||
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
|
||||
baseParams= 'type_name=%s&flavourNumber=%s' %(contentType,flavourNumber)
|
||||
baseParams = 'type_name=%s' % contentType
|
||||
# Manage start number
|
||||
rq = self.REQUEST
|
||||
if startNumber != None:
|
||||
|
@ -609,11 +702,10 @@ class ToolMixin(AbstractMixin):
|
|||
if (nextIndex < lastIndex): lastNeeded = True
|
||||
# Get the list of available UIDs surrounding the current object
|
||||
if t == 'ref': # Manage navigation from a reference
|
||||
# In the case of a reference, we retrieve ALL surrounding objects.
|
||||
masterObj = self.getObject(d1)
|
||||
batchSize = masterObj.getAppyType(fieldName).maxPerPage
|
||||
uids = getattr(masterObj, '_appy_%s' % fieldName)
|
||||
# In the case of a reference, we retrieve ALL surrounding objects.
|
||||
|
||||
# Display the reference widget at the page where the current object
|
||||
# lies.
|
||||
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)
|
||||
|
@ -622,13 +714,12 @@ class ToolMixin(AbstractMixin):
|
|||
res['sourceUrl'] = masterObj.getUrl(**{startNumberKey:startNumber,
|
||||
'page':pageName, 'nav':''})
|
||||
else: # Manage navigation from a search
|
||||
contentType, flavourNumber = d1.split(':')
|
||||
flavourNumber = int(flavourNumber)
|
||||
contentType = d1
|
||||
searchName = keySuffix = d2
|
||||
batchSize = self.appy().numberOfResultsPerPage
|
||||
if not searchName: keySuffix = contentType
|
||||
s = self.REQUEST.SESSION
|
||||
searchKey = 'search_%s_%s' % (flavourNumber, keySuffix)
|
||||
searchKey = 'search_%s' % keySuffix
|
||||
if s.has_key(searchKey): uids = s[searchKey]
|
||||
else: uids = {}
|
||||
# In the case of a search, we retrieve only a part of all
|
||||
|
@ -640,9 +731,8 @@ class ToolMixin(AbstractMixin):
|
|||
# this one.
|
||||
newStartNumber = (res['currentNumber']-1) - (batchSize / 2)
|
||||
if newStartNumber < 0: newStartNumber = 0
|
||||
self.executeQuery(contentType, flavourNumber,
|
||||
searchName=searchName, startNumber=newStartNumber,
|
||||
remember=True)
|
||||
self.executeQuery(contentType, searchName=searchName,
|
||||
startNumber=newStartNumber, remember=True)
|
||||
uids = s[searchKey]
|
||||
# For the moment, for first and last, we get them only if we have
|
||||
# them in session.
|
||||
|
@ -650,9 +740,9 @@ class ToolMixin(AbstractMixin):
|
|||
if not uids.has_key(lastIndex): lastNeeded = False
|
||||
# Compute URL of source object
|
||||
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
||||
res['totalNumber'], batchSize)
|
||||
res['sourceUrl'] = self.getQueryUrl(contentType, flavourNumber,
|
||||
searchName, startNumber=startNumber)
|
||||
res['totalNumber'], batchSize)
|
||||
res['sourceUrl'] = self.getQueryUrl(contentType, searchName,
|
||||
startNumber=startNumber)
|
||||
# Compute URLs
|
||||
for urlType in ('previous', 'next', 'first', 'last'):
|
||||
exec 'needIt = %sNeeded' % urlType
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class UserMixin(AbstractMixin):
|
||||
_appy_meta_type = 'UserMixin'
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,9 +1,6 @@
|
|||
'''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.'''
|
||||
- mixins/BaseMixin is mixed in with Standard Archetypes classes;
|
||||
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, types, mimetypes
|
||||
|
@ -15,10 +12,10 @@ from appy.gen.plone25.descriptors import ClassDescriptor
|
|||
from appy.gen.plone25.utils import updateRolesForPermission
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
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.'''
|
||||
class BaseMixin:
|
||||
'''Every Archetype class generated by appy.gen inherits from this class or
|
||||
a subclass of it.'''
|
||||
_appy_meta_type = 'Class'
|
||||
|
||||
def createOrUpdate(self, created, values):
|
||||
'''This method creates (if p_created is True) or updates an object.
|
||||
|
@ -52,17 +49,21 @@ class AbstractMixin:
|
|||
# Keep in history potential changes on historized fields
|
||||
self.historizeData(previousData)
|
||||
|
||||
# Manage references
|
||||
obj._appy_manageRefs(created)
|
||||
# Manage potential link with an initiator object
|
||||
if created and rq.get('nav', None):
|
||||
# Get the initiator
|
||||
splitted = rq['nav'].split('.')
|
||||
if splitted[0] == 'search': return # Not an initiator but a search.
|
||||
initiator = self.uid_catalog(UID=splitted[1])[0].getObject()
|
||||
fieldName = splitted[2].split(':')[0]
|
||||
initiator.appy().link(fieldName, obj)
|
||||
|
||||
# Call the custom "onEdit" if available
|
||||
if obj.wrapperClass:
|
||||
# Get the wrapper first
|
||||
appyObject = obj.appy()
|
||||
# Call the custom "onEdit" if available
|
||||
if hasattr(appyObject, 'onEdit'):
|
||||
appyObject.onEdit(created)
|
||||
# Manage "add" permissions
|
||||
if hasattr(appyObject, 'onEdit'): appyObject.onEdit(created)
|
||||
# Manage "add" permissions and reindex the object
|
||||
obj._appy_managePermissions()
|
||||
# Reindex object
|
||||
obj.reindexObject()
|
||||
return obj
|
||||
|
||||
|
@ -95,6 +96,12 @@ class AbstractMixin:
|
|||
(baseUrl, typeName, objId)
|
||||
return self.goto(self.getUrl(editUrl, **urlParams))
|
||||
|
||||
def onCreateWithoutForm(self):
|
||||
'''This method is called when a user wants to create a object from a
|
||||
reference field, automatically (without displaying a form).'''
|
||||
rq = self.REQUEST
|
||||
self.appy().create(rq['fieldName'])
|
||||
|
||||
def intraFieldValidation(self, errors, values):
|
||||
'''This method performs field-specific validation for every field from
|
||||
the page that is being created or edited. For every field whose
|
||||
|
@ -120,7 +127,7 @@ class AbstractMixin:
|
|||
obj = self.appy()
|
||||
if not hasattr(obj, 'validate'): return
|
||||
obj.validate(values, errors)
|
||||
# This custom "validate" method may have added fields in the given
|
||||
# Those custom validation methods may have added fields in the given
|
||||
# p_errors object. Within this object, for every error message that is
|
||||
# not a string, we replace it with the standard validation error for the
|
||||
# corresponding field.
|
||||
|
@ -241,21 +248,19 @@ class AbstractMixin:
|
|||
res = {}
|
||||
for appyType in self.getAllAppyTypes():
|
||||
if appyType.historized:
|
||||
res[appyType.name] = (getattr(self, appyType.name),
|
||||
appyType.labelId)
|
||||
res[appyType.name] = appyType.getValue(self)
|
||||
return res
|
||||
|
||||
def addDataChange(self, changes, labels=False):
|
||||
def addDataChange(self, changes):
|
||||
'''This method allows to add "manually" a data change into the objet's
|
||||
history. Indeed, data changes are "automatically" recorded only when
|
||||
a HTTP form is uploaded, not if, in the code, a setter is called on
|
||||
a field. The method is also called by the method historizeData below,
|
||||
that performs "automatic" recording when a HTTP form is uploaded.'''
|
||||
# Add to the p_changes dict the field labels if they are not present
|
||||
if not labels:
|
||||
for fieldName in changes.iterkeys():
|
||||
appyType = self.getAppyType(fieldName)
|
||||
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
||||
# Add to the p_changes dict the field labels
|
||||
for fieldName in changes.iterkeys():
|
||||
appyType = self.getAppyType(fieldName)
|
||||
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
||||
# Create the event to record in the history
|
||||
DateTime = self.getProductConfig().DateTime
|
||||
state = self.portal_workflow.getInfoFor(self, 'review_state')
|
||||
|
@ -273,14 +278,18 @@ class AbstractMixin:
|
|||
historized fields, while p_self already contains the (potentially)
|
||||
modified values.'''
|
||||
# Remove from previousData all values that were not changed
|
||||
for fieldName in previousData.keys():
|
||||
prev = previousData[fieldName][0]
|
||||
curr = getattr(self, fieldName)
|
||||
for field in previousData.keys():
|
||||
prev = previousData[field]
|
||||
appyType = self.getAppyType(field)
|
||||
curr = appyType.getValue(self)
|
||||
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
||||
((prev == '') and (curr == None)):
|
||||
del previousData[fieldName]
|
||||
del previousData[field]
|
||||
if (appyType.type == 'Ref') and (field in previousData):
|
||||
titles = [r.title for r in previousData[field]]
|
||||
previousData[field] = ','.join(titles)
|
||||
if previousData:
|
||||
self.addDataChange(previousData, labels=True)
|
||||
self.addDataChange(previousData)
|
||||
|
||||
def goto(self, url, addParams=False):
|
||||
'''Brings the user to some p_url after an action has been executed.'''
|
||||
|
@ -308,80 +317,14 @@ class AbstractMixin:
|
|||
field named p_name.'''
|
||||
return self.getAppyType(name).getFormattedValue(self, value)
|
||||
|
||||
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
||||
noListIfSingleObj=False, startNumber=None):
|
||||
'''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.
|
||||
If p_startNumber is None, this method returns all referred objects.
|
||||
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 = 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
|
||||
# are not updated when objects are deleted. So we do it now. TODO: do
|
||||
# such cleaning on object deletion?
|
||||
toDelete = []
|
||||
for uid in sortedUids:
|
||||
if uid not in refUids:
|
||||
toDelete.append(uid)
|
||||
for uid in toDelete:
|
||||
sortedUids.remove(uid)
|
||||
# Prepare the result
|
||||
res = SomeObjects()
|
||||
res.totalNumber = res.batchSize = len(sortedUids)
|
||||
if batchNeeded:
|
||||
res.batchSize = appyType.maxPerPage
|
||||
if startNumber != None:
|
||||
res.startNumber = startNumber
|
||||
# Get the needed referred objects
|
||||
i = res.startNumber
|
||||
# Is it possible and more efficient to perform a single query in
|
||||
# uid_catalog and get the result in the order of specified uids?
|
||||
toUnlink = []
|
||||
while i < (res.startNumber + res.batchSize):
|
||||
if i >= res.totalNumber: break
|
||||
refUid = sortedUids[i]
|
||||
refObject = self.uid_catalog(UID=refUid)[0].getObject()
|
||||
i += 1
|
||||
tool = self.getTool()
|
||||
if refObject.meta_type != tool.getPortalType(appyType.klass):
|
||||
toUnlink.append(refObject)
|
||||
continue
|
||||
if not ploneObjects:
|
||||
refObject = refObject.appy()
|
||||
res.objects.append(refObject)
|
||||
# Unlink dummy linked objects
|
||||
if toUnlink:
|
||||
suffix = '%s%s' % (fieldName[0].upper(), fieldName[1:])
|
||||
exec 'linkedObjects = self.get%s()' % suffix
|
||||
for dummyObject in toUnlink:
|
||||
linkedObjects.remove(dummyObject)
|
||||
self.getProductConfig().logger.warn('DB error: Ref %s.%s ' \
|
||||
'contains a %s instance "%s". It was removed.' % \
|
||||
(self.meta_type, fieldName, dummyObject.meta_type,
|
||||
dummyObject.getId()))
|
||||
exec 'self.set%s(linkedObjects)' % suffix
|
||||
if res.objects and noListIfSingleObj:
|
||||
if appyType.multiplicity[1] == 1:
|
||||
res.objects = res.objects[0]
|
||||
return res
|
||||
|
||||
def getAppyRefs(self, name, startNumber=None):
|
||||
'''Gets the objects linked to me through Ref field named p_name.
|
||||
If p_startNumber is None, this method returns all referred objects.
|
||||
If p_startNumber is a number, this method will return x objects,
|
||||
starting at p_startNumber, x being appyType.maxPerPage.'''
|
||||
If p_startNumber is a number, this method will return
|
||||
appyType.maxPerPage objects, starting at p_startNumber.'''
|
||||
appyType = self.getAppyType(name)
|
||||
if not appyType.isBack:
|
||||
return self._appy_getRefs(name, ploneObjects=True,
|
||||
startNumber=startNumber).__dict__
|
||||
else:
|
||||
# Note that pagination is not yet implemented for backward refs.
|
||||
return SomeObjects(self.getBRefs(appyType.relationship)).__dict__
|
||||
return appyType.getValue(self, type='zobjects', someObjects=True,
|
||||
startNumber=startNumber).__dict__
|
||||
|
||||
def getSelectableAppyRefs(self, name):
|
||||
'''p_name is the name of a Ref field. This method returns the list of
|
||||
|
@ -780,10 +723,6 @@ class AbstractMixin:
|
|||
self.reindexObject()
|
||||
return self.goto(urlBack)
|
||||
|
||||
def getFlavour(self):
|
||||
'''Returns the flavour corresponding to this object.'''
|
||||
return self.getTool().getFlavour(self.portal_type)
|
||||
|
||||
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
||||
'''When displaying a selection box (ie a String with a validator being a
|
||||
list), must the _vocabValue appear as selected?'''
|
||||
|
@ -853,32 +792,6 @@ class AbstractMixin:
|
|||
rq.appyWrappers[uid] = wrapper
|
||||
return wrapper
|
||||
|
||||
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.'''
|
||||
# Preamble: must I return a list or a single element?
|
||||
maxOne = False
|
||||
if noListIfSingleObj:
|
||||
# I must get the referred appyType to know its maximum multiplicity.
|
||||
appyType = self.getAppyType(fieldName)
|
||||
if appyType.multiplicity[1] == 1:
|
||||
maxOne = True
|
||||
# Get the referred objects through the Archetypes relationship.
|
||||
objs = self.getBRefs(relName)
|
||||
if maxOne:
|
||||
res = None
|
||||
if objs:
|
||||
res = objs[0]
|
||||
if res and not ploneObjects:
|
||||
res = res.appy()
|
||||
else:
|
||||
res = objs
|
||||
if not ploneObjects:
|
||||
res = [o.appy() for o in objs]
|
||||
return res
|
||||
|
||||
def _appy_showState(self, workflow, stateShow):
|
||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
||||
if callable(stateShow):
|
||||
|
@ -955,57 +868,6 @@ class AbstractMixin:
|
|||
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_manageRefsFromRequest()
|
||||
rq = self.REQUEST
|
||||
# If the creation was initiated by another object, update the ref.
|
||||
if created and rq.get('nav', None):
|
||||
# Get the initiator
|
||||
splitted = rq['nav'].split('.')
|
||||
if splitted[0] == 'search': return # Not an initiator but a search.
|
||||
initiator = self.uid_catalog.searchResults(
|
||||
UID=splitted[1])[0].getObject()
|
||||
fieldName = splitted[2].split(':')[1]
|
||||
initiator.appy().link(fieldName, self)
|
||||
|
||||
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:]
|
||||
# Security check
|
||||
if not self.getAppyType(fieldName).isShowable(self, 'edit'):
|
||||
continue
|
||||
fieldsInRequest.append(fieldName)
|
||||
fieldValue = self.REQUEST[requestKey]
|
||||
sortedRefField = self._appy_getSortedField(fieldName)
|
||||
del sortedRefField[:]
|
||||
if not fieldValue: fieldValue = []
|
||||
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
|
||||
currentPage = self.REQUEST.get('page', 'main')
|
||||
for appyType in self.getAllAppyTypes():
|
||||
if (appyType.type == 'Ref') and not appyType.isBack and \
|
||||
(appyType.page == currentPage) and \
|
||||
(appyType.name 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.
|
||||
if appyType.isShowable(self, 'edit'):
|
||||
exec 'self.set%s%s([])' % (appyType.name[0].upper(),
|
||||
appyType.name[1:])
|
||||
|
||||
getUrlDefaults = {'page':True, 'nav':True}
|
||||
def getUrl(self, base=None, mode='view', **kwargs):
|
||||
'''Returns a Appy URL.
|
||||
|
@ -1039,12 +901,14 @@ class AbstractMixin:
|
|||
params = ''
|
||||
return '%s%s' % (base, params)
|
||||
|
||||
def translate(self, label, mapping={}, domain=None, default=None):
|
||||
def translate(self, label, mapping={}, domain=None, default=None,
|
||||
language=None):
|
||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||
cfg = self.getProductConfig()
|
||||
if not domain: domain = cfg.PROJECTNAME
|
||||
return self.translation_service.utranslate(
|
||||
domain, label, mapping, self, default=default)
|
||||
return self.Control_Panel.TranslationService.utranslate(
|
||||
domain, label, mapping, self, default=default,
|
||||
target_language=language)
|
||||
|
||||
def getPageLayout(self, layoutType):
|
||||
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue