appy.gen: reworked management of Ref fields, that do not use Archetypes and reference catalog anymore. appy.gen: added a mechanism for migrating from one Appy version to another, automatically, when reinstalling an Appy application.

This commit is contained in:
Gaetan Delannay 2011-09-26 21:19:34 +02:00
parent eceb9175fd
commit 93619dafe1
13 changed files with 209 additions and 311 deletions

View file

@ -5,6 +5,7 @@ import sys, os.path
from optparse import OptionParser
from appy.gen.generator import GeneratorError
from appy.shared.utils import LinesCounter
import appy.version
# ------------------------------------------------------------------------------
ERROR_CODE = 1
@ -103,6 +104,7 @@ class GeneratorScript:
(options, args) = optParser.parse_args()
try:
self.manageArgs(optParser, options, args)
print 'Appy version:', appy.version.verbose
print 'Generating %s product in %s...' % (args[1], args[2])
self.generateProduct(options, *args)
# Give the user some statistics about its code

View file

@ -528,7 +528,6 @@ class Type:
# We must initialise the corresponding back reference
self.back.klass = klass
self.back.init(self.back.attribute, self.klass, appName)
self.back.relationship = '%s_%s_rel' % (prefix, name)
def reload(self, klass, obj):
'''In debug mode, we want to reload layouts without restarting Zope.
@ -1735,18 +1734,16 @@ class Ref(Type):
if (layoutType == 'edit') and self.add: return False
if self.isBack:
if layoutType == 'edit': return False
else:
return obj.getBRefs(self.relationship)
else: return getattr(obj, self.name, None)
return res
def getValue(self, obj, type='objects', noListIfSingleObj=False,
startNumber=None, someObjects=False):
'''Returns the objects linked to p_obj through Ref field "self".
'''Returns the objects linked to p_obj through this Ref field.
- If p_type is "objects", it returns the Appy wrappers;
- If p_type is "zobjects", it returns the Zope objects;
- If p_type is "uids", it returns UIDs of objects (= strings).
* If p_startNumber is None, it returns all referred objects.
* If p_startNumber is a number, it returns self.maxPerPage objects,
starting at p_startNumber.
@ -1756,23 +1753,7 @@ class Ref(Type):
If p_someObjects is True, it returns an instance of SomeObjects
instead of returning a list of references.'''
if self.isBack:
getRefs = obj.reference_catalog.getBackReferences
uids = [r.sourceUID for r in getRefs(obj, self.relationship)]
else:
uids = obj._appy_getSortedField(self.name)
batchNeeded = startNumber != None
exec 'refUids = obj.getRaw%s%s()' % (self.name[0].upper(),
self.name[1:])
# There may be too much UIDs in sortedField 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 uids:
if uid not in refUids:
toDelete.append(uid)
for uid in toDelete:
uids.remove(uid)
uids = getattr(obj, self.name, [])
if not uids:
# Maybe is there a default value?
defValue = Type.getValue(self, obj)
@ -1783,8 +1764,8 @@ class Ref(Type):
uids = [o.o.UID() for o in defValue]
else:
uids = [defValue.o.UID()]
# Prepare the result: an instance of SomeObjects, that, in this case,
# represent a subset of all referred objects
# Prepare the result: an instance of SomeObjects, that will be unwrapped
# if not required.
res = SomeObjects()
res.totalNumber = res.batchSize = len(uids)
batchNeeded = startNumber != None
@ -1792,10 +1773,8 @@ class Ref(Type):
res.batchSize = self.maxPerPage
if startNumber != None:
res.startNumber = startNumber
# Get the needed referred objects
# Get the objects given their uids
i = res.startNumber
# Is it possible and more efficient to perform a single query in
# portal_catalog and get the result in the order of specified uids?
while i < (res.startNumber + res.batchSize):
if i >= res.totalNumber: break
# Retrieve every reference in the correct format according to p_type
@ -1852,22 +1831,23 @@ class Ref(Type):
* a Appy object;
* a list of Appy or Zope objects.
'''
# Standardize the way p_value is expressed
refs = value
if not refs: refs = []
if type(refs) not in sequenceTypes: refs = [refs]
for i in range(len(refs)):
if isinstance(refs[i], basestring):
# Get the Zope object from the UID
refs[i] = obj.portal_catalog(UID=refs[i])[0].getObject()
# Standardize p_value into a list of uids
uids = value
if not uids: uids = []
if type(uids) not in sequenceTypes: uids = [uids]
for i in range(len(uids)):
if not isinstance(uids[i], basestring):
# Get the UID from the Zope or Appy object
uids[i] = uids[i].o.UID()
# Update the list of referred uids.
refs = getattr(obj, self.name, None)
if refs == None:
refs = obj.getProductConfig().PersistentList(uids)
setattr(obj, self.name, refs)
else:
refs[i] = refs[i].o # Now we are sure to have a Zope object.
# Update the field storing on p_obj the ordered list of UIDs
sortedRefs = obj._appy_getSortedField(self.name)
del sortedRefs[:]
for ref in refs: sortedRefs.append(ref.UID())
# Update the Archetypes Ref field.
exec 'obj.set%s%s(refs)' % (self.name[0].upper(), self.name[1:])
# Empty the list and fill it with uids
del refs[:]
for uid in uids: refs.append(uid)
def clone(self, forTool=True):
'''Produces a clone of myself.'''

View file

@ -7,7 +7,6 @@
# ------------------------------------------------------------------------------
import types, copy
from model import ModelClass, toolFieldPrefixes
from utils import stringify
import appy.gen
import appy.gen.descriptors
from appy.gen.po import PoMessage
@ -95,14 +94,8 @@ class FieldDescriptor:
def walkRef(self):
'''How to generate a Ref?'''
relationship = '%s_%s_rel' % (self.classDescr.name, self.fieldName)
self.fieldType = 'ReferenceField'
self.widgetType = 'ReferenceWidget'
self.fieldParams['relationship'] = relationship
if self.appyType.isMultiValued():
self.fieldParams['multiValued'] = True
# Update the list of referers
self.generator.addReferer(self, relationship)
self.generator.addReferer(self)
# Add the widget label for the back reference
refClassName = getClassName(self.appyType.klass, self.applicationName)
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
@ -185,35 +178,12 @@ class FieldDescriptor:
def generate(self):
'''Generates the i18n labels for this type.'''
self.walkAppyType()
if self.appyType.type != 'Ref': return
res = ''
s = stringify
spaces = TABS
# Generate field name
res += ' '*spaces + self.fieldType + '(\n'
# Generate field parameters
spaces += TABS
for fParamName, fParamValue in self.fieldParams.iteritems():
res += ' '*spaces + fParamName + '=' + s(fParamValue) + ',\n'
# Generate widget
res += ' '*spaces + 'widget=%s(\n' % self.widgetType
spaces += TABS
for wParamName, wParamValue in self.widgetParams.iteritems():
res += ' '*spaces + wParamName + '=' + s(wParamValue) + ',\n'
# End of widget definition
spaces -= TABS
res += ' '*spaces + ')\n'
# End of field definition
spaces -= TABS
res += ' '*spaces + '),\n'
return res
class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
'''Represents an Archetypes-compliant class.'''
def __init__(self, klass, orderedAttributes, generator):
appy.gen.descriptors.ClassDescriptor.__init__(self, klass,
orderedAttributes, generator)
self.schema = '' # The archetypes schema will be generated here
self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will
@ -247,11 +217,10 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
return (parentWrapper, parentClass)
def generateSchema(self, configClass=False):
'''Generates the corresponding Archetypes schema in self.schema. If we
are generating a schema for a class that is in the configuration
(tool, user, etc) we must avoid having attributes that rely on
the configuration (ie attributes that are optional, with
editDefault=True, etc).'''
'''Generates i18n and other related stuff for this class. If this class
is in the configuration (tool, user, etc) we must avoid having
attributes that rely on the configuration (ie attributes that are
optional, with editDefault=True, etc).'''
for attrName in self.orderedAttributes:
try:
attrValue = getattr(self.klass, attrName)
@ -262,11 +231,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
attrValue = copy.copy(attrValue)
attrValue.optional = False
attrValue.editDefault = False
field = FieldDescriptor(attrName, attrValue, self)
fieldDef = field.generate()
if fieldDef:
# Currently, we generate Archetypes fields for Refs only.
self.schema += '\n' + fieldDef
FieldDescriptor(attrName, attrValue, self).generate()
def isAbstract(self):
'''Is self.klass abstract?'''

View file

@ -286,14 +286,13 @@ class Generator(AbstractGenerator):
res = [r for r in res if eval('r.%s == %s' % (p, p))]
return res
def addReferer(self, fieldDescr, relationship):
'''p_fieldDescr is a Ref type definition. We will create in config.py a
dict that lists all back references, by type.'''
def addReferer(self, fieldDescr):
'''p_fieldDescr is a Ref type definition.'''
k = fieldDescr.appyType.klass
refClassName = getClassName(k, self.applicationName)
if not self.referers.has_key(refClassName):
self.referers[refClassName] = []
self.referers[refClassName].append( (fieldDescr, relationship))
self.referers[refClassName].append(fieldDescr)
def getAppyTypePath(self, name, appyType, klass, isBack=False):
'''Gets the path to the p_appyType when a direct reference to an
@ -363,7 +362,7 @@ class Generator(AbstractGenerator):
if not titleFound: names.insert(0, 'title')
# Any backward attributes to append?
if classDescr.name in self.referers:
for field, rel in self.referers[classDescr.name]:
for field in self.referers[classDescr.name]:
names.append(field.appyType.back.attribute)
qNames = ['"%s"' % name for name in names]
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
@ -536,10 +535,10 @@ class Generator(AbstractGenerator):
self.labels += [ Msg(klass.name, '', klassType),
Msg('%s_plural' % klass.name,'', klass.name+'s')]
repls = self.repls.copy()
repls.update({'fields': klass.schema, 'methods': klass.methods,
'genClassName': klass.name, 'imports': '','baseMixin':'BaseMixin',
'baseSchema': 'BaseSchema', 'global_allow': 1,
'parents': 'BaseMixin, BaseContent', 'static': '',
repls.update({'methods': klass.methods, 'genClassName': klass.name,
'imports': '','baseMixin':'BaseMixin', 'baseSchema': 'BaseSchema',
'global_allow': 1, 'parents': 'BaseMixin, BaseContent',
'static': '',
'classDoc': 'User class for %s' % self.applicationName,
'implements': "(getattr(BaseContent,'__implements__',()),)",
'register': "registerType(%s, '%s')" % (klass.name,
@ -567,7 +566,7 @@ class Generator(AbstractGenerator):
# Generate the Tool class
repls = self.repls.copy()
repls.update({'fields': self.tool.schema, 'methods': self.tool.methods,
repls.update({'methods': self.tool.methods,
'genClassName': self.tool.name, 'imports':'', 'baseMixin':'ToolMixin',
'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0,
'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder',
@ -584,7 +583,7 @@ class Generator(AbstractGenerator):
def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application, for
generating the corresponding Archetype class and schema.'''
generating the corresponding Archetype class.'''
k = classDescr.klass
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
if not classDescr.isAbstract():
@ -625,9 +624,9 @@ class Generator(AbstractGenerator):
'className': classDescr.klass.__name__, 'global_allow': 1,
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
'classDoc': classDoc, 'applicationName': self.applicationName,
'fields': classDescr.schema, 'methods': classDescr.methods,
'implements': implements, 'baseSchema': baseSchema, 'static': '',
'register': register, 'toolInstanceName': self.toolInstanceName})
'methods': classDescr.methods, 'implements': implements,
'baseSchema': baseSchema, 'static': '', 'register': register,
'toolInstanceName': self.toolInstanceName})
fileName = '%s.py' % classDescr.name
# Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
@ -651,7 +650,7 @@ class Generator(AbstractGenerator):
poMsg.produceNiceDefault()
if poMsg not in self.labels:
self.labels.append(poMsg)
# Generate the resulting Archetypes class and schema.
# Generate the resulting Archetypes class.
self.copyFile('Class.py', repls, destName=fileName)
def generateWorkflow(self, wfDescr):

View file

@ -6,12 +6,14 @@ import os, os.path, time
from StringIO import StringIO
from sets import Set
import appy
import appy.version
from appy.gen import Type, Ref, String
from appy.gen.po import PoParser
from appy.gen.utils import produceNiceMessage
from appy.gen.plone25.utils import updateRolesForPermission
from appy.gen.utils import produceNiceMessage, updateRolesForPermission
from appy.shared.data import languages
from migrator import Migrator
# ------------------------------------------------------------------------------
class ZCTextIndexInfo:
'''Silly class used for storing information about a ZCTextIndex.'''
lexicon_id = "plone_lexicon"
@ -246,7 +248,6 @@ class PloneInstaller:
self.tool.createOrUpdate(False, None)
else:
self.tool.createOrUpdate(True, None)
self.updatePodTemplates()
def installTranslations(self):
'''Creates or updates the translation objects within the tool.'''
@ -304,20 +305,6 @@ class PloneInstaller:
site.portal_groups.setRolesForGroup(group, [role])
site.__ac_roles__ = tuple(data)
def installStyleSheet(self):
'''Registers In Plone the stylesheet linked to this application.'''
cssName = self.productName + '.css'
cssTitle = self.productName + ' CSS styles'
cssInfo = {'id': cssName, 'title': cssTitle}
portalCss = self.ploneSite.portal_css
try:
portalCss.unregisterResource(cssInfo['id'])
except:
pass
defaults = {'id': '', 'media': 'all', 'enabled': True}
defaults.update(cssInfo)
portalCss.registerStylesheet(**defaults)
def manageIndexes(self):
'''For every indexed field, this method installs and updates the
corresponding index if it does not exist yet.'''
@ -350,54 +337,34 @@ class PloneInstaller:
site = self.ploneSite
# Do not allow an anonymous user to register himself as new user
site.manage_permission('Add portal member', ('Manager',), acquire=0)
# Call custom installer if any
if hasattr(self.appyTool, 'install'):
self.tool.executeAppyAction('install', reindex=False)
# Replace Plone front-page with an application-specific page if needed
if self.appFrontPage:
frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName)
# Store the used Appy version (used for detecting new versions)
self.appyTool.appyVersion = appy.version.short
self.info('Appy version is %s.' % self.appyTool.appyVersion)
# Call custom installer if any
if hasattr(self.appyTool, 'install'):
self.tool.executeAppyAction('install', reindex=False)
def info(self, msg): return self.appyTool.log(msg)
def install(self):
# Begin with a migration if required.
self.installTool()
if self.reinstall: Migrator(self).run()
self.installRootFolder()
self.installTypes()
self.installTool()
self.manageLanguages()
self.manageIndexes()
self.updatePodTemplates()
self.installTranslations()
self.installRolesAndGroups()
self.installStyleSheet()
self.manageIndexes()
self.manageLanguages()
self.finalizeInstallation()
self.appyTool.log("Installation done.")
def uninstallTool(self):
site = self.ploneSite
# Unmention tool in the search form
portalProperties = getattr(site, 'portal_properties', None)
if portalProperties is not None:
siteProperties = getattr(portalProperties, 'site_properties', None)
if siteProperties is not None and \
siteProperties.hasProperty('types_not_searched'):
current = list(siteProperties.getProperty('types_not_searched'))
if self.toolName in current:
current.remove(self.toolName)
siteProperties.manage_changeProperties(
**{'types_not_searched' : current})
# Unmention tool in the navigation
if portalProperties is not None:
nvProps = getattr(portalProperties, 'navtree_properties', None)
if nvProps is not None and nvProps.hasProperty('idsNotToList'):
current = list(nvProps.getProperty('idsNotToList'))
if self.toolInstanceName in current:
current.remove(self.toolInstanceName)
nvProps.manage_changeProperties(**{'idsNotToList': current})
def uninstall(self):
self.uninstallTool()
return 'Done.'
def uninstall(self): return 'Done.'
# Stuff for tracking user activity ---------------------------------------------
loggedUsers = {}

63
gen/plone25/migrator.py Normal file
View file

@ -0,0 +1,63 @@
# ------------------------------------------------------------------------------
import time
# ------------------------------------------------------------------------------
class Migrator:
'''This class is responsible for performing migrations, when, on
installation, we've detected a new Appy version.'''
def __init__(self, installer):
self.installer = installer
def migrateTo_0_7_1(self):
'''Appy 0.7.1 has its own management of Ref fields and does not use
Archetypes references and the reference catalog anymore. So we must
update data structures that store Ref info on instances.'''
ins = self.installer
ins.info('Migrating to Appy 0.7.1...')
allClassNames = [ins.tool.__class__.__name__] + ins.config.allClassNames
for className in allClassNames:
i = -1
updated = 0
ins.info('Analysing class "%s"...' % className)
refFields = None
for obj in ins.tool.executeQuery(className,\
noSecurity=True)['objects']:
i += 1
if i == 0:
# Get the Ref fields for objects of this class
refFields = [f for f in obj.getAllAppyTypes() \
if (f.type == 'Ref') and not f.isBack]
if refFields:
refNames = ', '.join([rf.name for rf in refFields])
ins.info(' Ref fields found: %s' % refNames)
else:
ins.info(' No Ref field found.')
break
isUpdated = False
for field in refFields:
# Attr for storing UIDs of referred objects has moved
# from _appy_[fieldName] to [fieldName].
refs = getattr(obj, '_appy_%s' % field.name)
if refs:
isUpdated = True
setattr(obj, field.name, refs)
exec 'del obj._appy_%s' % field.name
# Set the back references
for refObject in field.getValue(obj):
refObject.link(field.back.name, obj, back=True)
if isUpdated: updated += 1
if updated:
ins.info(' %d/%d object(s) updated.' % (updated, i+1))
def run(self):
i = self.installer
installedVersion = i.appyTool.appyVersion
startTime = time.time()
migrationRequired = False
if not installedVersion or (installedVersion <= '0.7.0'):
migrationRequired = True
self.migrateTo_0_7_1()
stopTime = time.time()
if migrationRequired:
i.info('Migration done in %d minute(s).'% ((stopTime-startTime)/60))
# ------------------------------------------------------------------------------

View file

@ -270,7 +270,7 @@ class ToolMixin(BaseMixin):
# corresponding filter widget on the screen.
if refObject:
refField = refObject.getAppyType(refField)
params['UID'] = refObject._appy_getSortedField(refField.name).data
params['UID'] = getattr(refObject, refField.name).data
# Determine what method to call on the portal catalog
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
else: catalogMethod = 'searchResults'
@ -694,7 +694,7 @@ class ToolMixin(BaseMixin):
# 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)
uids = getattr(masterObj, fieldName)
# Display the reference widget at the page where the current object
# lies.
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)

View file

@ -11,7 +11,6 @@ from appy.gen.utils import *
from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.descriptors import WorkflowDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor
from appy.gen.plone25.utils import updateRolesForPermission,checkTransitionGuard
# ------------------------------------------------------------------------------
class BaseMixin:
@ -81,6 +80,11 @@ class BaseMixin:
# Call a custom "onDelete" if it exists
appyObj = self.appy()
if hasattr(appyObj, 'onDelete'): appyObj.onDelete()
# Any people referencing me must forget me now
for field in self.getAllAppyTypes():
if field.type != 'Ref': continue
for obj in field.getValue(self):
obj.unlink(field.back.name, self, back=True)
# Delete the object
self.getParentNode().manage_delObjects([self.id])
@ -430,7 +434,6 @@ class BaseMixin:
appyType = self.getAppyType(name)
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]):
return appyType.getValue(self)
return None
def getFormattedFieldValue(self, name, value):
'''Gets a nice, string representation of p_value which is a value from
@ -442,8 +445,8 @@ class BaseMixin:
If p_startNumber is None, this method returns all referred objects.
If p_startNumber is a number, this method will return
appyType.maxPerPage objects, starting at p_startNumber.'''
appyType = self.getAppyType(name)
return appyType.getValue(self, type='zobjects', someObjects=True,
field = self.getAppyType(name)
return field.getValue(self, type='zobjects', someObjects=True,
startNumber=startNumber).__dict__
def getSelectableAppyRefs(self, name):
@ -494,9 +497,9 @@ class BaseMixin:
def getAppyRefIndex(self, fieldName, obj):
'''Gets the position of p_obj within Ref field named p_fieldName.'''
sortedObjectsUids = self._appy_getSortedField(fieldName)
res = sortedObjectsUids.index(obj.UID())
return res
refs = getattr(self, fieldName, None)
if not refs: raise IndexError()
return refs.index(obj.UID())
def isDebug(self):
'''Are we in debug mode ?'''
@ -782,14 +785,14 @@ class BaseMixin:
'''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.'''
sortedObjectsUids = self._appy_getSortedField(fieldName)
oldIndex = sortedObjectsUids.index(objectUid)
sortedObjectsUids.remove(objectUid)
refs = getattr(self, fieldName, None)
oldIndex = refs.index(objectUid)
refs.remove(objectUid)
if isDelta:
newIndex = oldIndex + newIndex
else:
pass # To implement later on
sortedObjectsUids.insert(newIndex, objectUid)
refs.insert(newIndex, objectUid)
def onChangeRefOrder(self):
'''This method is called when the user wants to change order of an
@ -1074,7 +1077,7 @@ class BaseMixin:
for appyType in self.getAllAppyTypes():
if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: continue
# Indeed, no possibility to create objects with such Ref
# Indeed, no possibility to create objects with such Refs
refType = self.getTool().getPortalType(appyType.klass)
if refType not in addPermissions: continue
# Get roles that may add this content type
@ -1111,16 +1114,6 @@ class BaseMixin:
res = self.portal_type
return res
def _appy_getSortedField(self, fieldName):
'''Gets, for reference field p_fieldName, the Appy persistent list
that contains the sorted list of referred object UIDs. If this list
does not exist, it is created.'''
sortedFieldName = '_appy_%s' % fieldName
if not hasattr(self.aq_base, sortedFieldName):
pList = self.getProductConfig().PersistentList
exec 'self.%s = pList()' % sortedFieldName
return getattr(self, sortedFieldName)
getUrlDefaults = {'page':True, 'nav':True}
def getUrl(self, base=None, mode='view', **kwargs):
'''Returns a Appy URL.

View file

@ -168,7 +168,7 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
defaultToolFields = ('users', 'translations', 'enableNotifications',
'unoEnabledPython', 'openOfficePort',
'numberOfResultsPerPage', 'listBoxesMaximumWidth',
'refreshSecurity')
'appyVersion', 'refreshSecurity')
class Tool(ModelClass):
# In a ModelClass we need to declare attributes in the following list.
@ -181,6 +181,7 @@ class Tool(ModelClass):
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30)
listBoxesMaximumWidth = Integer(default=100)
appyVersion = String(show=False, layouts='f')
def refreshSecurity(self): pass # Real method in the wrapper
refreshSecurity = Action(action=refreshSecurity, confirm=True)
# Ref(User) will maybe be transformed into Ref(CustomUserClass).

View file

@ -8,11 +8,6 @@ from appy.gen.plone25.mixins import BaseMixin
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
from Extensions.appyWrappers import <!genClassName!>_Wrapper
<!imports!>
schema = Schema((<!fields!>
),)
fullSchema = <!baseSchema!>.copy() + schema.copy()
class <!genClassName!>(<!parents!>):
'''<!classDoc!>'''
security = ClassSecurityInfo()
@ -30,7 +25,7 @@ class <!genClassName!>(<!parents!>):
typeDescMsgId = '<!genClassName!>'
i18nDomain = '<!applicationName!>'
wrapperClass = <!genClassName!>_Wrapper
schema = fullSchema
schema = <!baseSchema!>.copy()
for elem in dir(<!baseMixin!>):
if not elem.startswith('__'): security.declarePublic(elem)
<!static!>

View file

@ -1,84 +0,0 @@
# ------------------------------------------------------------------------------
def stringify(value):
'''Transforms p_value such that it can be dumped as a string into a
generated file.'''
if isinstance(value, tuple) or isinstance(value, list):
res = '('
for v in value:
res += '%s,' % stringify(v)
res += ')'
elif value.__class__.__name__ == 'DateTime':
res = 'DateTime("%s")' % value.strftime('%Y/%m/%d %H:%M')
else:
res = str(value)
if isinstance(value, basestring):
if value.startswith('python:'):
res = value[7:]
else:
res = "'%s'" % value.replace("'", "\\'")
res = res.replace('\n', '\\n')
return res
# ------------------------------------------------------------------------------
def updateRolesForPermission(permission, roles, obj):
'''Adds roles from list p_roles to the list of roles that are granted
p_permission on p_obj.'''
from AccessControl.Permission import Permission
# Find existing roles that were granted p_permission on p_obj
existingRoles = ()
for p in obj.ac_inherited_permissions(1):
name, value = p[:2]
if name == permission:
perm = Permission(name, value, obj)
existingRoles = perm.getRoles()
allRoles = set(existingRoles).union(roles)
obj.manage_permission(permission, tuple(allRoles), acquire=0)
# ------------------------------------------------------------------------------
def checkTransitionGuard(guard, sm, wf_def, ob):
'''This method is similar to DCWorkflow.Guard.check, but allows to retrieve
the truth value as a appy.gen.No instance, not simply "1" or "0".'''
from Products.DCWorkflow.Expression import StateChangeInfo,createExprContext
u_roles = None
if wf_def.manager_bypass:
# Possibly bypass.
u_roles = sm.getUser().getRolesInContext(ob)
if 'Manager' in u_roles:
return 1
if guard.permissions:
for p in guard.permissions:
if _checkPermission(p, ob):
break
else:
return 0
if guard.roles:
# Require at least one of the given roles.
if u_roles is None:
u_roles = sm.getUser().getRolesInContext(ob)
for role in guard.roles:
if role in u_roles:
break
else:
return 0
if guard.groups:
# Require at least one of the specified groups.
u = sm.getUser()
b = aq_base( u )
if hasattr( b, 'getGroupsInContext' ):
u_groups = u.getGroupsInContext( ob )
elif hasattr( b, 'getGroups' ):
u_groups = u.getGroups()
else:
u_groups = ()
for group in guard.groups:
if group in u_groups:
break
else:
return 0
expr = guard.expr
if expr is not None:
econtext = createExprContext(StateChangeInfo(ob, wf_def))
res = expr(econtext)
return res
return 1
# ------------------------------------------------------------------------------

View file

@ -86,65 +86,67 @@ class AbstractWrapper(object):
def getField(self, name): return self.o.getAppyType(name)
def link(self, fieldName, obj):
def link(self, fieldName, obj, back=False):
'''This method links p_obj (which can be a list of objects) to this one
through reference field p_fieldName.'''
postfix = 'et%s%s' % (fieldName[0].upper(), fieldName[1:])
# Update the Archetypes reference field
exec 'objs = self.o.g%s()' % postfix
if not objs:
objs = []
elif type(objs) not in sequenceTypes:
objs = [objs]
# Add p_obj to the existing objects
through reference field p_fieldName. As this method is recursive and
can be called to update the corresponding back reference, param
p_back is there, but, you, as Appy developer, should never set it
to True.'''
# p_objs can be a list of objects
if type(obj) in sequenceTypes:
for o in obj: objs.append(o.o)
else:
objs.append(obj.o)
exec 'self.o.s%s(objs)' % postfix
# Update the ordered list of references
sorted = self.o._appy_getSortedField(fieldName)
if type(obj) in sequenceTypes:
for o in obj: sorted.append(o.o.UID())
else:
sorted.append(obj.o.UID())
for o in obj: self.link(fieldName, o, back=back)
return
# Gets the list of referred objects (=list of uids), or create it.
selfO = self.o
refs = getattr(selfO, fieldName, None)
if refs == None:
refs = selfO.getProductConfig().PersistentList()
setattr(selfO, fieldName, refs)
# Insert p_obj into it.
uid = obj.o.UID()
if uid not in refs:
refs.append(uid)
# Update the back reference
if not back:
backName = selfO.getAppyType(fieldName).back.attribute
obj.appy().link(backName, self, back=True)
def unlink(self, fieldName, obj):
def unlink(self, fieldName, obj, back=False):
'''This method unlinks p_obj (which can be a list of objects) from this
one through reference field p_fieldName.'''
postfix = 'et%s%s' % (fieldName[0].upper(), fieldName[1:])
# Update the Archetypes reference field.
exec 'objs = self.o.g%s()' % postfix
if not objs: return
if type(objs) not in sequenceTypes: objs = [objs]
# Remove p_obj from existing objects
one through reference field p_fieldName. As this method is recursive
and can be called to update the corresponding back reference, param
p_back is there, but, you, as Appy developer, should never set it
to True.'''
# p_objs can be a list of objects
if type(obj) in sequenceTypes:
for o in obj:
if o.o in objs: objs.remove(o.o)
else:
if obj.o in objs: objs.remove(obj.o)
exec 'self.o.s%s(objs)' % postfix
# Update the ordered list of references
sorted = self.o._appy_getSortedField(fieldName)
if type(obj) in sequenceTypes:
for o in obj:
if o.o.UID() in sorted:
sorted.remove(o.o.UID())
else:
if obj.o.UID() in sorted:
sorted.remove(obj.o.UID())
for o in obj: self.unlink(fieldName, o, back=back)
return
# Get the list of referred objects
selfO = self.o
refs = getattr(selfO, fieldName, None)
if not refs: return
# Unlink the object
uid = obj.o.UID()
if uid in refs:
refs.remove(uid)
# Update the back reference
if not back:
backName = selfO.getAppyType(fieldName).back.attribute
obj.appy().unlink(backName, self, back=True)
def sort(self, fieldName, sortKey='title', reverse=False):
'''Sorts referred elements linked to p_self via p_fieldName according
to a given p_sortKey which must be an attribute set on referred
objects ("title", by default).'''
sortedUids = getattr(self.o, '_appy_%s' % fieldName)
c = self.o.portal_catalog
sortedUids.sort(lambda x,y: \
selfO = self.o
refs = getattr(selfO, fieldName, None)
if not refs: return
c = selfO.portal_catalog
refs.sort(lambda x,y: \
cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey),
getattr(c(UID=y)[0].getObject().appy(), sortKey)))
if reverse:
sortedUids.reverse()
refs.reverse()
def create(self, fieldNameOrClass, **kwargs):
'''If p_fieldNameOfClass is the name of a field, this method allows to

View file

@ -314,4 +314,19 @@ def getClassName(klass, appName=None):
else: # This is a standard class
res = klass.__module__.replace('.', '_') + '_' + klass.__name__
return res
# ------------------------------------------------------------------------------
def updateRolesForPermission(permission, roles, obj):
'''Adds roles from list p_roles to the list of roles that are granted
p_permission on p_obj.'''
from AccessControl.Permission import Permission
# Find existing roles that were granted p_permission on p_obj
existingRoles = ()
for p in obj.ac_inherited_permissions(1):
name, value = p[:2]
if name == permission:
perm = Permission(name, value, obj)
existingRoles = perm.getRoles()
allRoles = set(existingRoles).union(roles)
obj.manage_permission(permission, tuple(allRoles), acquire=0)
# ------------------------------------------------------------------------------