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

View file

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

View file

@ -7,7 +7,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import types, copy import types, copy
from model import ModelClass, toolFieldPrefixes from model import ModelClass, toolFieldPrefixes
from utils import stringify
import appy.gen import appy.gen
import appy.gen.descriptors import appy.gen.descriptors
from appy.gen.po import PoMessage from appy.gen.po import PoMessage
@ -95,14 +94,8 @@ class FieldDescriptor:
def walkRef(self): def walkRef(self):
'''How to generate a Ref?''' '''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 # Update the list of referers
self.generator.addReferer(self, relationship) self.generator.addReferer(self)
# Add the widget label for the back reference # Add the widget label for the back reference
refClassName = getClassName(self.appyType.klass, self.applicationName) refClassName = getClassName(self.appyType.klass, self.applicationName)
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute) backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
@ -185,35 +178,12 @@ class FieldDescriptor:
def generate(self): def generate(self):
'''Generates the i18n labels for this type.''' '''Generates the i18n labels for this type.'''
self.walkAppyType() 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): class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
'''Represents an Archetypes-compliant class.''' '''Represents an Archetypes-compliant class.'''
def __init__(self, klass, orderedAttributes, generator): def __init__(self, klass, orderedAttributes, generator):
appy.gen.descriptors.ClassDescriptor.__init__(self, klass, appy.gen.descriptors.ClassDescriptor.__init__(self, klass,
orderedAttributes, generator) orderedAttributes, generator)
self.schema = '' # The archetypes schema will be generated here
self.methods = '' # Needed method definitions will be generated here self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy # We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will # type. Indeed, after having parsed all application classes, we will
@ -247,11 +217,10 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
return (parentWrapper, parentClass) return (parentWrapper, parentClass)
def generateSchema(self, configClass=False): def generateSchema(self, configClass=False):
'''Generates the corresponding Archetypes schema in self.schema. If we '''Generates i18n and other related stuff for this class. If this class
are generating a schema for a class that is in the configuration is in the configuration (tool, user, etc) we must avoid having
(tool, user, etc) we must avoid having attributes that rely on attributes that rely on the configuration (ie attributes that are
the configuration (ie attributes that are optional, with optional, with editDefault=True, etc).'''
editDefault=True, etc).'''
for attrName in self.orderedAttributes: for attrName in self.orderedAttributes:
try: try:
attrValue = getattr(self.klass, attrName) attrValue = getattr(self.klass, attrName)
@ -262,11 +231,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
attrValue = copy.copy(attrValue) attrValue = copy.copy(attrValue)
attrValue.optional = False attrValue.optional = False
attrValue.editDefault = False attrValue.editDefault = False
field = FieldDescriptor(attrName, attrValue, self) FieldDescriptor(attrName, attrValue, self).generate()
fieldDef = field.generate()
if fieldDef:
# Currently, we generate Archetypes fields for Refs only.
self.schema += '\n' + fieldDef
def isAbstract(self): def isAbstract(self):
'''Is self.klass abstract?''' '''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))] res = [r for r in res if eval('r.%s == %s' % (p, p))]
return res return res
def addReferer(self, fieldDescr, relationship): def addReferer(self, fieldDescr):
'''p_fieldDescr is a Ref type definition. We will create in config.py a '''p_fieldDescr is a Ref type definition.'''
dict that lists all back references, by type.'''
k = fieldDescr.appyType.klass k = fieldDescr.appyType.klass
refClassName = getClassName(k, self.applicationName) refClassName = getClassName(k, self.applicationName)
if not self.referers.has_key(refClassName): if not self.referers.has_key(refClassName):
self.referers[refClassName] = [] self.referers[refClassName] = []
self.referers[refClassName].append( (fieldDescr, relationship)) self.referers[refClassName].append(fieldDescr)
def getAppyTypePath(self, name, appyType, klass, isBack=False): def getAppyTypePath(self, name, appyType, klass, isBack=False):
'''Gets the path to the p_appyType when a direct reference to an '''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') if not titleFound: names.insert(0, 'title')
# Any backward attributes to append? # Any backward attributes to append?
if classDescr.name in self.referers: 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) names.append(field.appyType.back.attribute)
qNames = ['"%s"' % name for name in names] qNames = ['"%s"' % name for name in names]
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames))) attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
@ -536,10 +535,10 @@ class Generator(AbstractGenerator):
self.labels += [ Msg(klass.name, '', klassType), self.labels += [ Msg(klass.name, '', klassType),
Msg('%s_plural' % klass.name,'', klass.name+'s')] Msg('%s_plural' % klass.name,'', klass.name+'s')]
repls = self.repls.copy() repls = self.repls.copy()
repls.update({'fields': klass.schema, 'methods': klass.methods, repls.update({'methods': klass.methods, 'genClassName': klass.name,
'genClassName': klass.name, 'imports': '','baseMixin':'BaseMixin', 'imports': '','baseMixin':'BaseMixin', 'baseSchema': 'BaseSchema',
'baseSchema': 'BaseSchema', 'global_allow': 1, 'global_allow': 1, 'parents': 'BaseMixin, BaseContent',
'parents': 'BaseMixin, BaseContent', 'static': '', 'static': '',
'classDoc': 'User class for %s' % self.applicationName, 'classDoc': 'User class for %s' % self.applicationName,
'implements': "(getattr(BaseContent,'__implements__',()),)", 'implements': "(getattr(BaseContent,'__implements__',()),)",
'register': "registerType(%s, '%s')" % (klass.name, 'register': "registerType(%s, '%s')" % (klass.name,
@ -567,7 +566,7 @@ class Generator(AbstractGenerator):
# Generate the Tool class # Generate the Tool class
repls = self.repls.copy() 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', 'genClassName': self.tool.name, 'imports':'', 'baseMixin':'ToolMixin',
'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0, 'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0,
'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder', 'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder',
@ -584,7 +583,7 @@ class Generator(AbstractGenerator):
def generateClass(self, classDescr): def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application, for '''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 k = classDescr.klass
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__) print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
if not classDescr.isAbstract(): if not classDescr.isAbstract():
@ -625,9 +624,9 @@ class Generator(AbstractGenerator):
'className': classDescr.klass.__name__, 'global_allow': 1, 'className': classDescr.klass.__name__, 'global_allow': 1,
'genClassName': classDescr.name, 'baseMixin':'BaseMixin', 'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
'classDoc': classDoc, 'applicationName': self.applicationName, 'classDoc': classDoc, 'applicationName': self.applicationName,
'fields': classDescr.schema, 'methods': classDescr.methods, 'methods': classDescr.methods, 'implements': implements,
'implements': implements, 'baseSchema': baseSchema, 'static': '', 'baseSchema': baseSchema, 'static': '', 'register': register,
'register': register, 'toolInstanceName': self.toolInstanceName}) 'toolInstanceName': self.toolInstanceName})
fileName = '%s.py' % classDescr.name fileName = '%s.py' % classDescr.name
# Create i18n labels (class name and plural form) # Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__) poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
@ -651,7 +650,7 @@ class Generator(AbstractGenerator):
poMsg.produceNiceDefault() poMsg.produceNiceDefault()
if poMsg not in self.labels: if poMsg not in self.labels:
self.labels.append(poMsg) self.labels.append(poMsg)
# Generate the resulting Archetypes class and schema. # Generate the resulting Archetypes class.
self.copyFile('Class.py', repls, destName=fileName) self.copyFile('Class.py', repls, destName=fileName)
def generateWorkflow(self, wfDescr): def generateWorkflow(self, wfDescr):

View file

@ -6,12 +6,14 @@ import os, os.path, time
from StringIO import StringIO from StringIO import StringIO
from sets import Set from sets import Set
import appy import appy
import appy.version
from appy.gen import Type, Ref, String from appy.gen import Type, Ref, String
from appy.gen.po import PoParser from appy.gen.po import PoParser
from appy.gen.utils import produceNiceMessage from appy.gen.utils import produceNiceMessage, updateRolesForPermission
from appy.gen.plone25.utils import updateRolesForPermission
from appy.shared.data import languages from appy.shared.data import languages
from migrator import Migrator
# ------------------------------------------------------------------------------
class ZCTextIndexInfo: class ZCTextIndexInfo:
'''Silly class used for storing information about a ZCTextIndex.''' '''Silly class used for storing information about a ZCTextIndex.'''
lexicon_id = "plone_lexicon" lexicon_id = "plone_lexicon"
@ -246,7 +248,6 @@ class PloneInstaller:
self.tool.createOrUpdate(False, None) self.tool.createOrUpdate(False, None)
else: else:
self.tool.createOrUpdate(True, None) self.tool.createOrUpdate(True, None)
self.updatePodTemplates()
def installTranslations(self): def installTranslations(self):
'''Creates or updates the translation objects within the tool.''' '''Creates or updates the translation objects within the tool.'''
@ -304,20 +305,6 @@ class PloneInstaller:
site.portal_groups.setRolesForGroup(group, [role]) site.portal_groups.setRolesForGroup(group, [role])
site.__ac_roles__ = tuple(data) 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): def manageIndexes(self):
'''For every indexed field, this method installs and updates the '''For every indexed field, this method installs and updates the
corresponding index if it does not exist yet.''' corresponding index if it does not exist yet.'''
@ -350,54 +337,34 @@ class PloneInstaller:
site = self.ploneSite site = self.ploneSite
# Do not allow an anonymous user to register himself as new user # Do not allow an anonymous user to register himself as new user
site.manage_permission('Add portal member', ('Manager',), acquire=0) 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 # Replace Plone front-page with an application-specific page if needed
if self.appFrontPage: if self.appFrontPage:
frontPageName = self.productName + 'FrontPage' frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName) 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 info(self, msg): return self.appyTool.log(msg)
def install(self): def install(self):
# Begin with a migration if required.
self.installTool()
if self.reinstall: Migrator(self).run()
self.installRootFolder() self.installRootFolder()
self.installTypes() self.installTypes()
self.installTool() self.manageLanguages()
self.manageIndexes()
self.updatePodTemplates()
self.installTranslations() self.installTranslations()
self.installRolesAndGroups() self.installRolesAndGroups()
self.installStyleSheet()
self.manageIndexes()
self.manageLanguages()
self.finalizeInstallation() self.finalizeInstallation()
self.appyTool.log("Installation done.") self.appyTool.log("Installation done.")
def uninstallTool(self): def uninstall(self): return 'Done.'
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.'
# Stuff for tracking user activity --------------------------------------------- # Stuff for tracking user activity ---------------------------------------------
loggedUsers = {} 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. # corresponding filter widget on the screen.
if refObject: if refObject:
refField = refObject.getAppyType(refField) 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 # Determine what method to call on the portal catalog
if noSecurity: catalogMethod = 'unrestrictedSearchResults' if noSecurity: catalogMethod = 'unrestrictedSearchResults'
else: catalogMethod = 'searchResults' else: catalogMethod = 'searchResults'
@ -694,7 +694,7 @@ class ToolMixin(BaseMixin):
# In the case of a reference, we retrieve ALL surrounding objects. # In the case of a reference, we retrieve ALL surrounding objects.
masterObj = self.getObject(d1) masterObj = self.getObject(d1)
batchSize = masterObj.getAppyType(fieldName).maxPerPage 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 # Display the reference widget at the page where the current object
# lies. # lies.
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName) 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.layout import Table, defaultPageLayouts
from appy.gen.descriptors import WorkflowDescriptor from appy.gen.descriptors import WorkflowDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor from appy.gen.plone25.descriptors import ClassDescriptor
from appy.gen.plone25.utils import updateRolesForPermission,checkTransitionGuard
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class BaseMixin: class BaseMixin:
@ -81,6 +80,11 @@ class BaseMixin:
# Call a custom "onDelete" if it exists # Call a custom "onDelete" if it exists
appyObj = self.appy() appyObj = self.appy()
if hasattr(appyObj, 'onDelete'): appyObj.onDelete() 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 # Delete the object
self.getParentNode().manage_delObjects([self.id]) self.getParentNode().manage_delObjects([self.id])
@ -430,7 +434,6 @@ class BaseMixin:
appyType = self.getAppyType(name) appyType = self.getAppyType(name)
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]): if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]):
return appyType.getValue(self) return appyType.getValue(self)
return None
def getFormattedFieldValue(self, name, value): def getFormattedFieldValue(self, name, value):
'''Gets a nice, string representation of p_value which is a value from '''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 None, this method returns all referred objects.
If p_startNumber is a number, this method will return If p_startNumber is a number, this method will return
appyType.maxPerPage objects, starting at p_startNumber.''' appyType.maxPerPage objects, starting at p_startNumber.'''
appyType = self.getAppyType(name) field = self.getAppyType(name)
return appyType.getValue(self, type='zobjects', someObjects=True, return field.getValue(self, type='zobjects', someObjects=True,
startNumber=startNumber).__dict__ startNumber=startNumber).__dict__
def getSelectableAppyRefs(self, name): def getSelectableAppyRefs(self, name):
@ -494,9 +497,9 @@ class BaseMixin:
def getAppyRefIndex(self, fieldName, obj): def getAppyRefIndex(self, fieldName, obj):
'''Gets the position of p_obj within Ref field named p_fieldName.''' '''Gets the position of p_obj within Ref field named p_fieldName.'''
sortedObjectsUids = self._appy_getSortedField(fieldName) refs = getattr(self, fieldName, None)
res = sortedObjectsUids.index(obj.UID()) if not refs: raise IndexError()
return res return refs.index(obj.UID())
def isDebug(self): def isDebug(self):
'''Are we in debug mode ?''' '''Are we in debug mode ?'''
@ -782,14 +785,14 @@ class BaseMixin:
'''This method changes the position of object with uid p_objectUid in '''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 reference field p_fieldName to p_newIndex i p_isDelta is False, or
to actualIndex+p_newIndex if p_isDelta is True.''' to actualIndex+p_newIndex if p_isDelta is True.'''
sortedObjectsUids = self._appy_getSortedField(fieldName) refs = getattr(self, fieldName, None)
oldIndex = sortedObjectsUids.index(objectUid) oldIndex = refs.index(objectUid)
sortedObjectsUids.remove(objectUid) refs.remove(objectUid)
if isDelta: if isDelta:
newIndex = oldIndex + newIndex newIndex = oldIndex + newIndex
else: else:
pass # To implement later on pass # To implement later on
sortedObjectsUids.insert(newIndex, objectUid) refs.insert(newIndex, objectUid)
def onChangeRefOrder(self): def onChangeRefOrder(self):
'''This method is called when the user wants to change order of an '''This method is called when the user wants to change order of an
@ -1074,7 +1077,7 @@ class BaseMixin:
for appyType in self.getAllAppyTypes(): for appyType in self.getAllAppyTypes():
if appyType.type != 'Ref': continue if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: 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) refType = self.getTool().getPortalType(appyType.klass)
if refType not in addPermissions: continue if refType not in addPermissions: continue
# Get roles that may add this content type # Get roles that may add this content type
@ -1111,16 +1114,6 @@ class BaseMixin:
res = self.portal_type res = self.portal_type
return res 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} getUrlDefaults = {'page':True, 'nav':True}
def getUrl(self, base=None, mode='view', **kwargs): def getUrl(self, base=None, mode='view', **kwargs):
'''Returns a Appy URL. '''Returns a Appy URL.

View file

@ -168,7 +168,7 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
defaultToolFields = ('users', 'translations', 'enableNotifications', defaultToolFields = ('users', 'translations', 'enableNotifications',
'unoEnabledPython', 'openOfficePort', 'unoEnabledPython', 'openOfficePort',
'numberOfResultsPerPage', 'listBoxesMaximumWidth', 'numberOfResultsPerPage', 'listBoxesMaximumWidth',
'refreshSecurity') 'appyVersion', 'refreshSecurity')
class Tool(ModelClass): class Tool(ModelClass):
# In a ModelClass we need to declare attributes in the following list. # 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") openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30) numberOfResultsPerPage = Integer(default=30)
listBoxesMaximumWidth = Integer(default=100) listBoxesMaximumWidth = Integer(default=100)
appyVersion = String(show=False, layouts='f')
def refreshSecurity(self): pass # Real method in the wrapper def refreshSecurity(self): pass # Real method in the wrapper
refreshSecurity = Action(action=refreshSecurity, confirm=True) refreshSecurity = Action(action=refreshSecurity, confirm=True)
# Ref(User) will maybe be transformed into Ref(CustomUserClass). # 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 appy.gen.plone25.mixins.ToolMixin import ToolMixin
from Extensions.appyWrappers import <!genClassName!>_Wrapper from Extensions.appyWrappers import <!genClassName!>_Wrapper
<!imports!> <!imports!>
schema = Schema((<!fields!>
),)
fullSchema = <!baseSchema!>.copy() + schema.copy()
class <!genClassName!>(<!parents!>): class <!genClassName!>(<!parents!>):
'''<!classDoc!>''' '''<!classDoc!>'''
security = ClassSecurityInfo() security = ClassSecurityInfo()
@ -30,7 +25,7 @@ class <!genClassName!>(<!parents!>):
typeDescMsgId = '<!genClassName!>' typeDescMsgId = '<!genClassName!>'
i18nDomain = '<!applicationName!>' i18nDomain = '<!applicationName!>'
wrapperClass = <!genClassName!>_Wrapper wrapperClass = <!genClassName!>_Wrapper
schema = fullSchema schema = <!baseSchema!>.copy()
for elem in dir(<!baseMixin!>): for elem in dir(<!baseMixin!>):
if not elem.startswith('__'): security.declarePublic(elem) if not elem.startswith('__'): security.declarePublic(elem)
<!static!> <!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 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 '''This method links p_obj (which can be a list of objects) to this one
through reference field p_fieldName.''' through reference field p_fieldName. As this method is recursive and
postfix = 'et%s%s' % (fieldName[0].upper(), fieldName[1:]) can be called to update the corresponding back reference, param
# Update the Archetypes reference field p_back is there, but, you, as Appy developer, should never set it
exec 'objs = self.o.g%s()' % postfix to True.'''
if not objs: # p_objs can be a list of objects
objs = []
elif type(objs) not in sequenceTypes:
objs = [objs]
# Add p_obj to the existing objects
if type(obj) in sequenceTypes: if type(obj) in sequenceTypes:
for o in obj: objs.append(o.o) for o in obj: self.link(fieldName, o, back=back)
else: return
objs.append(obj.o) # Gets the list of referred objects (=list of uids), or create it.
exec 'self.o.s%s(objs)' % postfix selfO = self.o
# Update the ordered list of references refs = getattr(selfO, fieldName, None)
sorted = self.o._appy_getSortedField(fieldName) if refs == None:
if type(obj) in sequenceTypes: refs = selfO.getProductConfig().PersistentList()
for o in obj: sorted.append(o.o.UID()) setattr(selfO, fieldName, refs)
else: # Insert p_obj into it.
sorted.append(obj.o.UID()) 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 '''This method unlinks p_obj (which can be a list of objects) from this
one through reference field p_fieldName.''' one through reference field p_fieldName. As this method is recursive
postfix = 'et%s%s' % (fieldName[0].upper(), fieldName[1:]) and can be called to update the corresponding back reference, param
# Update the Archetypes reference field. p_back is there, but, you, as Appy developer, should never set it
exec 'objs = self.o.g%s()' % postfix to True.'''
if not objs: return # p_objs can be a list of objects
if type(objs) not in sequenceTypes: objs = [objs]
# Remove p_obj from existing objects
if type(obj) in sequenceTypes: if type(obj) in sequenceTypes:
for o in obj: for o in obj: self.unlink(fieldName, o, back=back)
if o.o in objs: objs.remove(o.o) return
else: # Get the list of referred objects
if obj.o in objs: objs.remove(obj.o) selfO = self.o
exec 'self.o.s%s(objs)' % postfix refs = getattr(selfO, fieldName, None)
# Update the ordered list of references if not refs: return
sorted = self.o._appy_getSortedField(fieldName) # Unlink the object
if type(obj) in sequenceTypes: uid = obj.o.UID()
for o in obj: if uid in refs:
if o.o.UID() in sorted: refs.remove(uid)
sorted.remove(o.o.UID()) # Update the back reference
else: if not back:
if obj.o.UID() in sorted: backName = selfO.getAppyType(fieldName).back.attribute
sorted.remove(obj.o.UID()) obj.appy().unlink(backName, self, back=True)
def sort(self, fieldName, sortKey='title', reverse=False): def sort(self, fieldName, sortKey='title', reverse=False):
'''Sorts referred elements linked to p_self via p_fieldName according '''Sorts referred elements linked to p_self via p_fieldName according
to a given p_sortKey which must be an attribute set on referred to a given p_sortKey which must be an attribute set on referred
objects ("title", by default).''' objects ("title", by default).'''
sortedUids = getattr(self.o, '_appy_%s' % fieldName) selfO = self.o
c = self.o.portal_catalog refs = getattr(selfO, fieldName, None)
sortedUids.sort(lambda x,y: \ if not refs: return
c = selfO.portal_catalog
refs.sort(lambda x,y: \
cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey), cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey),
getattr(c(UID=y)[0].getObject().appy(), sortKey))) getattr(c(UID=y)[0].getObject().appy(), sortKey)))
if reverse: if reverse:
sortedUids.reverse() refs.reverse()
def create(self, fieldNameOrClass, **kwargs): def create(self, fieldNameOrClass, **kwargs):
'''If p_fieldNameOfClass is the name of a field, this method allows to '''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 else: # This is a standard class
res = klass.__module__.replace('.', '_') + '_' + klass.__name__ res = klass.__module__.replace('.', '_') + '_' + klass.__name__
return res 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)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------