Bugfix in new.py; added new user management.

This commit is contained in:
Gaetan Delannay 2010-09-02 16:16:08 +02:00
parent fa974239f3
commit eb52c1bb7d
30 changed files with 842 additions and 397 deletions

View file

@ -5,7 +5,7 @@
# ------------------------------------------------------------------------------
import os, os.path, sys, shutil
from optparse import OptionParser
from appy.shared.utils import cleanFolder
from appy.shared.utils import cleanFolder, copyFolder
# ------------------------------------------------------------------------------
class NewError(Exception): pass
@ -103,7 +103,7 @@ class NewScript:
os.system(cmd)
else:
# Copy thre product into the instance
shutil.copytree(folderName, destFolder)
copyFolder(folderName, destFolder)
uglyChunks = ('pkg_resources', '.declare_namespace(')
def findPythonPackageInEgg(self, currentFolder):
@ -199,7 +199,7 @@ class NewScript:
# A Zope product. Copy its content in Products.
innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder))
destFolder = j(productsFolder, os.path.basename(innerFolder))
shutil.copytree(innerFolder, destFolder)
copyFolder(innerFolder, destFolder)
else:
# A standard Python package. Copy its content in lib/python.
# Go into the subFolder that is not EGG-INFO.
@ -218,7 +218,7 @@ class NewScript:
# libFolder.
innerFolder = self.getSubFolder(eggFolder)
destFolder = j(productsFolder,os.path.basename(innerFolder))
shutil.copytree(innerFolder, destFolder)
copyFolder(innerFolder, destFolder)
else:
packageFolder = self.findPythonPackageInEgg(eggFolder)
# Create the destination folder(s) in the instance,
@ -252,7 +252,7 @@ class NewScript:
else:
destFolder = libFolder
destFolder = j(destFolder, os.path.basename(packageFolder))
shutil.copytree(packageFolder, destFolder)
copyFolder(packageFolder, destFolder)
self.patchPlone(productsFolder, libFolder)
def manageArgs(self, args):

View file

@ -5,8 +5,8 @@ from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
FileWrapper
from appy.shared.data import countries
FileWrapper, getClassName
from appy.shared.data import languages
# Default Appy permissions -----------------------------------------------------
r, w, d = ('read', 'write', 'delete')
@ -14,7 +14,8 @@ digit = re.compile('[0-9]')
alpha = re.compile('[a-zA-Z0-9]')
letter = re.compile('[a-zA-Z]')
nullValues = (None, '', ' ')
validatorTypes = (types.FunctionType, type(re.compile('')))
validatorTypes = (types.FunctionType, types.UnboundMethodType,
type(re.compile('')))
emptyTuple = ()
# Descriptor classes used for refining descriptions of elements in types
@ -383,14 +384,7 @@ class Type:
self.name = name
# Determine ids of i18n labels for this field
if not klass: prefix = appName
elif klass.__module__.endswith('.appyWrappers'):
prefix = appName + klass.__name__
elif Tool in klass.__bases__:
prefix = appName + 'Tool'
elif Flavour in klass.__bases__:
prefix = appName + 'Flavour'
else:
prefix = klass.__module__.replace('.', '_') + '_' + klass.__name__
else: prefix = getClassName(klass, appName)
self.labelId = '%s_%s' % (prefix, name)
self.descrId = self.labelId + '_descr'
self.helpId = self.labelId + '_help'
@ -583,7 +577,7 @@ class Type:
value = self.getStorableValue(value)
if self.validator and (type(self.validator) in validatorTypes):
obj = obj.appy()
if type(self.validator) == validatorTypes[0]:
if type(self.validator) != validatorTypes[-1]:
# It is a custom function. Execute it.
try:
validValue = self.validator(obj, value)
@ -598,7 +592,7 @@ class Type:
return str(e)
except:
return obj.translate('%s_valid' % self.labelId)
elif type(self.validator) == validatorTypes[1]:
else:
# It is a regular expression
if not self.validator.match(value):
# If the regular expression is among the default ones, we
@ -739,7 +733,7 @@ class String(Type):
# Maximum size is 34 chars
if (len(v) < 8) or (len(v) > 34): return False
# 2 first chars must be a valid country code
if not countries.exists(v[:2].lower()): return False
if not languages.exists(v[:2].lower()): return False
# 2 next chars are a control code whose value must be between 0 and 96.
try:
code = int(v[2:4])
@ -768,7 +762,7 @@ class String(Type):
for c in value[:4]:
if not letter.match(c): return False
# 2 next chars must be a valid country code
if not countries.exists(value[4:6].lower()): return False
if not languages.exists(value[4:6].lower()): return False
# Last chars represent some location within a country (a city, a
# province...). They can only be letters or figures.
for c in value[6:]:
@ -1356,24 +1350,71 @@ class Pod(Type):
self.validable = False
# Workflow-specific types ------------------------------------------------------
class Role:
'''Represents a role.'''
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous',
'Authenticated')
ploneLocalRoles = ('Owner',)
ploneUngrantableRoles = ('Anonymous', 'Authenticated')
def __init__(self, name, local=False, grantable=True):
self.name = name
self.local = local # True if it can be used as local role only.
# It is a standard Plone role or an application-specific one?
self.plone = name in self.ploneRoles
if self.plone and (name in self.ploneLocalRoles):
self.local = True
self.grantable = grantable
if self.plone and (name in self.ploneUngrantableRoles):
self.grantable = False
# An ungrantable role is one that is, like the Anonymous or
# Authenticated roles, automatically attributed to a user.
class State:
def __init__(self, permissions, initial=False, phase='main', show=True):
self.permissions = permissions #~{s_permissionName:[s_roleName]}~ This
# dict gives, for every permission managed by a workflow, the list of
# roles for which the permission is granted in this state.
# Standard permissions are 'read', 'write' and 'delete'.
self.usedRoles = {}
# The following dict ~{s_permissionName:[s_roleName|Role_role]}~
# gives, for every permission managed by a workflow, the list of roles
# for which the permission is granted in this state. Standard
# permissions are 'read', 'write' and 'delete'.
self.permissions = permissions
self.initial = initial
self.phase = phase
self.show = show
def getUsedRoles(self):
res = set()
for roleValue in self.permissions.itervalues():
if isinstance(roleValue, basestring):
res.add(roleValue)
elif roleValue:
for role in roleValue:
res.add(role)
return list(res)
# Standardize the way roles are expressed within self.permissions
self.standardizeRoles()
def getRole(self, role):
'''p_role can be the name of a role or a Role instance. If it is the
name of a role, this method returns self.usedRoles[role] if it
exists, or creates a Role instance, puts it in self.usedRoles and
returns it else. If it is a Role instance, the method stores it in
self.usedRoles if it not in it yet and returns it.'''
if isinstance(role, basestring):
if role in self.usedRoles:
return self.usedRoles[role]
else:
theRole = Role(role)
self.usedRoles[role] = theRole
return theRole
else:
if role.name not in self.usedRoles:
self.usedRoles[role.name] = role
return role
def standardizeRoles(self):
'''This method converts, within self.permissions, every role to a
Role instance. Every used role is stored in self.usedRoles.'''
for permission, roles in self.permissions.items():
if isinstance(roles, basestring) or isinstance(roles, Role):
self.permissions[permission] = [self.getRole(roles)]
elif roles:
rolesList = []
for role in roles:
rolesList.append(self.getRole(role))
self.permissions[permission] = rolesList
def getUsedRoles(self): return self.usedRoles.values()
def getTransitions(self, transitions, selfIsFromState=True):
'''Among p_transitions, returns those whose fromState is p_self (if
p_selfIsFromState is True) or those whose toState is p_self (if
@ -1383,6 +1424,7 @@ class State:
if self in t.getStates(selfIsFromState):
res.append(t)
return res
def getPermissions(self):
'''If you get the permissions mapping through self.permissions, dict
values may be of different types (a list of roles, a single role or
@ -1407,6 +1449,9 @@ class Transition:
# transition at several places in the state-transition diagram. It may
# be useful for "undo" transitions, for example.
self.condition = condition
if isinstance(condition, basestring):
# The condition specifies the name of a role.
self.condition = Role(condition)
self.action = action
self.notify = notify # If not None, it is a method telling who must be
# notified by email after the transition has been executed.
@ -1414,14 +1459,14 @@ class Transition:
# the transition. It will only be possible by code.
def getUsedRoles(self):
'''If self.condition is specifies a role.'''
'''self.condition can specify a role.'''
res = []
if isinstance(self.condition, basestring):
res = [self.condition]
if isinstance(self.condition, Role):
res.append(self.condition)
return res
def isSingle(self):
'''If this transitions is only define between 2 states, returns True.
'''If this transition is only defined between 2 states, returns True.
Else, returns False.'''
return isinstance(self.states[0], State)
@ -1517,13 +1562,16 @@ class Selection:
return value
# ------------------------------------------------------------------------------
class Tool:
class Model: pass
class Tool(Model):
'''If you want so define a custom tool class, she must inherit from this
class.'''
class Flavour:
class Flavour(Model):
'''A flavour represents a given group of configuration options. If you want
to define a custom flavour class, she must inherit from this class.'''
def __init__(self, name): self.name = name
class User(Model):
'''If you want to extend or modify the User class, subclass me.'''
# ------------------------------------------------------------------------------
class Config:
@ -1544,6 +1592,11 @@ class Config:
# For every language code that you specify in this list, appy.gen will
# produce and maintain translation files.
self.languages = ['en']
# If languageSelector is True, on every page, a language selector will
# allow to switch between languages defined in self.languages. Else,
# the browser-defined language will be used for choosing the language
# of returned pages.
self.languageSelector = False
# People having one of these roles will be able to create instances
# of classes defined in your application.
self.defaultCreators = ['Manager', 'Owner']

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------
import os, os.path, sys, parser, symbol, token, types
from appy.gen import Type, State, Config, Tool, Flavour
from appy.gen import Type, State, Config, Tool, Flavour, User
from appy.gen.descriptors import *
from appy.gen.utils import produceNiceMessage
import appy.pod, appy.pod.renderer
@ -133,7 +133,8 @@ class Generator:
# Default descriptor classes
self.descriptorClasses = {
'class': ClassDescriptor, 'tool': ClassDescriptor,
'flavour': ClassDescriptor, 'workflow': WorkflowDescriptor}
'flavour': ClassDescriptor, 'user': ClassDescriptor,
'workflow': WorkflowDescriptor}
# The following dict contains a series of replacements that need to be
# applied to file templates to generate files.
self.repls = {'applicationName': self.applicationName,
@ -143,6 +144,7 @@ class Generator:
self.classes = []
self.tool = None
self.flavour = None
self.user = None
self.workflows = []
self.initialize()
self.config = Config.getDefault()
@ -235,6 +237,12 @@ class Generator:
self.flavour = klass(moduleElem, attrs, self)
else:
self.flavour.update(moduleElem, attrs)
elif issubclass(moduleElem, User):
if not self.user:
klass = self.descriptorClasses['user']
self.user = klass(moduleElem, attrs, self)
else:
self.user.update(moduleElem, attrs)
else:
descriptorClass = self.descriptorClasses['class']
descriptor = descriptorClass(moduleElem,attrs, self)

View file

@ -185,6 +185,6 @@ class Table(LayoutElement):
# ------------------------------------------------------------------------------
defaultPageLayouts = {
'view': Table('m;-s|-n!-w;-b|'), 'edit': Table('m;-s|-n!-w;-b|')}
'view': Table('m;-s|-n!-w|-b|'), 'edit': Table('m;-w|-b|')}
defaultFieldLayouts = {'view': 'l;f!', 'edit': 'lrv;f!'}
# ------------------------------------------------------------------------------

View file

@ -12,9 +12,9 @@ import appy.gen
import appy.gen.descriptors
from appy.gen.po import PoMessage
from appy.gen import Date, String, State, Transition, Type, Search, \
Selection, Import
Selection, Import, Role
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
sequenceTypes
sequenceTypes, getClassName
TABS = 4 # Number of blanks in a Python indentation.
# ------------------------------------------------------------------------------
@ -110,13 +110,7 @@ class FieldDescriptor:
# Update the list of referers
self.generator.addReferer(self, relationship)
# Add the widget label for the back reference
refClassName = ClassDescriptor.getClassName(self.appyType.klass)
if issubclass(self.appyType.klass, ModelClass):
refClassName = self.applicationName + self.appyType.klass.__name__
elif issubclass(self.appyType.klass, appy.gen.Tool):
refClassName = '%sTool' % self.applicationName
elif issubclass(self.appyType.klass, appy.gen.Flavour):
refClassName = '%sFlavour' % self.applicationName
refClassName = getClassName(self.appyType.klass, self.applicationName)
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
poMsg.produceNiceDefault()
@ -240,7 +234,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
# for child classes of this class as well, but at this time we don't
# know yet every sub-class. So we store field definitions here; the
# Generator will propagate them later.
self.name = self.getClassName(klass)
self.name = getClassName(self.klass, generator.applicationName)
self.predefined = False
self.customized = False
@ -276,11 +270,6 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
# Currently, we generate Archetypes fields for Refs only.
self.schema += '\n' + fieldDef
@staticmethod
def getClassName(klass):
'''Generates the name of the corresponding Archetypes class.'''
return klass.__module__.replace('.', '_') + '_' + klass.__name__
def isAbstract(self):
'''Is self.klass abstract?'''
res = False
@ -322,7 +311,14 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
'''Gets the specific creators defined for this class.'''
res = []
if self.klass.__dict__.has_key('creators') and self.klass.creators:
res += list(self.klass.creators)
for creator in self.klass.creators:
if isinstance(creator, Role):
if creator.local:
raise 'Local role "%s" cannot be used as a creator.' % \
creator.name
res.append(creator)
else:
res.append(Role(creator))
return res
def getCreateMean(self, type='Import'):
@ -379,7 +375,6 @@ class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sTool' % generator.applicationName
self.modelClass = self.klass
self.predefined = True
self.customized = False
@ -405,7 +400,6 @@ class FlavourClassDescriptor(ClassDescriptor):
for the generated application.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sFlavour' % generator.applicationName
self.attributesByClass = klass._appy_classes
self.modelClass = self.klass
self.predefined = True
@ -431,13 +425,37 @@ class PodTemplateClassDescriptor(ClassDescriptor):
'''Represents a POD template.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sPodTemplate' % generator.applicationName
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()): return ['PodTemplate']
def isRoot(self): return False
class UserClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to the User
for the generated application.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()):
res = ['User']
if self.customized:
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
return res
def update(self, klass, attributes):
'''This method is called by the generator when he finds a custom user
definition. We must then add the custom user elements in this
default User descriptor.'''
self.orderedAttributes += attributes
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
'''Represents a workflow.'''
# How to map Appy permissions to Plone permissions ?
@ -497,11 +515,11 @@ class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
permissionsMapping = {}
for permission, roles in state.getPermissions().iteritems():
for plonePerm in self.getPlonePermissions(permission):
permissionsMapping[plonePerm] = roles
permissionsMapping[plonePerm] = [r.name for r in roles]
# Add 'Review portal content' to anyone; this is not a security
# problem because we limit the triggering of every transition
# individually.
allRoles = self.generator.getAllUsedRoles()
allRoles = [r.name for r in self.generator.getAllUsedRoles()]
if 'Manager' not in allRoles: allRoles.append('Manager')
permissionsMapping['Review portal content'] = allRoles
res[stateName] = (tNames, permissionsMapping)

View file

@ -7,10 +7,12 @@ import appy.gen
from appy.gen import *
from appy.gen.po import PoMessage, PoFile, PoParser
from appy.gen.generator import Generator as AbstractGenerator
from model import ModelClass, PodTemplate, Flavour, Tool
from appy.gen.utils import getClassName
from model import ModelClass, PodTemplate, User, Flavour, Tool
from descriptors import FieldDescriptor, ClassDescriptor, \
WorkflowDescriptor, ToolClassDescriptor, \
FlavourClassDescriptor, PodTemplateClassDescriptor
FlavourClassDescriptor, PodTemplateClassDescriptor, \
UserClassDescriptor
# Common methods that need to be defined on every Archetype class --------------
COMMON_METHODS = '''
@ -36,12 +38,14 @@ class Generator(AbstractGenerator):
self.tool = ToolClassDescriptor(Tool, self)
self.flavour = FlavourClassDescriptor(Flavour, self)
self.podTemplate = PodTemplateClassDescriptor(PodTemplate, self)
self.user = UserClassDescriptor(User, self)
# i18n labels to generate
self.labels = [] # i18n labels
self.toolName = '%sTool' % self.applicationName
self.flavourName = '%sFlavour' % self.applicationName
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
self.podTemplateName = '%sPodTemplate' % self.applicationName
self.userName = '%sUser' % self.applicationName
self.portletName = '%s_portlet' % self.applicationName.lower()
self.queryName = '%s_query' % self.applicationName.lower()
self.skinsFolder = 'skins/%s' % self.applicationName
@ -54,7 +58,7 @@ class Generator(AbstractGenerator):
{'toolName': self.toolName, 'flavourName': self.flavourName,
'portletName': self.portletName, 'queryName': self.queryName,
'toolInstanceName': self.toolInstanceName,
'podTemplateName': self.podTemplateName,
'podTemplateName': self.podTemplateName, 'userName': self.userName,
'commonMethods': commonMethods})
self.referers = {}
@ -149,18 +153,19 @@ class Generator(AbstractGenerator):
msg('image_required', '', msg.IMAGE_REQUIRED),
]
# Create a label for every role added by this application
for role in self.getAllUsedRoles(appOnly=True):
self.labels.append(msg('role_%s' % role,'', role, niceDefault=True))
for role in self.getAllUsedRoles():
self.labels.append(msg('role_%s' % role.name,'', role.name,
niceDefault=True))
# Create basic files (config.py, Install.py, etc)
self.generateTool()
self.generateConfig()
self.generateInit()
self.generateInstall()
self.generateWorkflows()
self.generateWrappers()
self.generateTests()
if self.config.frontPage:
self.generateFrontPage()
self.copyFile('Install.py', self.repls, destFolder='Extensions')
self.copyFile('configure.zcml', self.repls)
self.copyFile('import_steps.xml', self.repls,
destFolder='profiles/default')
@ -227,41 +232,49 @@ class Generator(AbstractGenerator):
if not poFile.generated:
poFile.generate()
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous')
def getAllUsedRoles(self, appOnly=False):
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
'''Produces a list of all the roles used within all workflows and
classes defined in this application. If p_appOnly is True, it
returns only roles which are specific to this application (ie it
removes predefined Plone roles like Member, Manager, etc.'''
res = []
classes defined in this application.
If p_plone is True, it keeps only Plone-standard roles; if p_plone
is False, it keeps only roles which are specific to this application;
if p_plone is None it has no effect (so it keeps both roles).
If p_local is True, it keeps only local roles (ie, roles that can
only be granted locally); if p_local is False, it keeps only "global"
roles; if p_local is None it has no effect (so it keeps both roles).
If p_grantable is True, it keeps only roles that the admin can
grant; if p_grantable is False, if keeps only ungrantable roles (ie
those that are implicitly granted by the system like role
"Authenticated"); if p_grantable is None it keeps both roles.'''
allRoles = {} # ~{s_roleName:Role_role}~
# Gather roles from workflow states and transitions
for wfDescr in self.workflows:
# Browse states and transitions
for attr in dir(wfDescr.klass):
attrValue = getattr(wfDescr.klass, attr)
if isinstance(attrValue, State) or \
isinstance(attrValue, Transition):
res += attrValue.getUsedRoles()
for role in attrValue.getUsedRoles():
if role.name not in allRoles:
allRoles[role.name] = role
# Gather roles from "creators" attributes from every class
for cDescr in self.getClasses(include='all'):
res += cDescr.getCreators()
res = list(set(res))
if appOnly:
for ploneRole in self.ploneRoles:
if ploneRole in res:
res.remove(ploneRole)
for role in cDescr.getCreators():
if role.name not in allRoles:
allRoles[role.name] = role
res = allRoles.values()
# Filter the result according to parameters
for p in ('plone', 'local', 'grantable'):
if eval(p) != None:
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.'''
k = fieldDescr.appyType.klass
if issubclass(k, ModelClass):
refClassName = self.applicationName + k.__name__
elif issubclass(k, appy.gen.Tool):
refClassName = '%sTool' % self.applicationName
elif issubclass(k, appy.gen.Flavour):
refClassName = '%sFlavour' % self.applicationName
else:
refClassName = ClassDescriptor.getClassName(k)
refClassName = getClassName(k, self.applicationName)
if not self.referers.has_key(refClassName):
self.referers[refClassName] = []
self.referers[refClassName].append( (fieldDescr, relationship))
@ -277,6 +290,59 @@ class Generator(AbstractGenerator):
return res
def generateConfig(self):
repls = self.repls.copy()
# Compute imports
imports = ['import %s' % self.applicationName]
classDescrs = self.getClasses(include='custom')
for classDescr in (classDescrs + self.workflows):
theImport = 'import %s' % classDescr.klass.__module__
if theImport not in imports:
imports.append(theImport)
repls['imports'] = '\n'.join(imports)
# Compute default add roles
repls['defaultAddRoles'] = ','.join(
['"%s"' % r for r in self.config.defaultCreators])
# Compute list of add permissions
addPermissions = ''
for classDescr in self.getClasses(include='allButTool'):
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
self.applicationName, classDescr.name)
repls['addPermissions'] = addPermissions
# Compute root classes
rootClasses = ''
for classDescr in self.classes:
if classDescr.isRoot():
rootClasses += "'%s'," % classDescr.name
repls['rootClasses'] = rootClasses
# Compute list of class definitions
appClasses = []
for classDescr in self.classes:
k = classDescr.klass
appClasses.append('%s.%s' % (k.__module__, k.__name__))
repls['appClasses'] = "[%s]" % ','.join(appClasses)
# Compute lists of class names
allClassNames = '"%s",' % self.flavourName
allClassNames += '"%s",' % self.podTemplateName
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
allClassNames += appClassNames
repls['allClassNames'] = allClassNames
repls['appClassNames'] = appClassNames
# Compute classes whose instances must not be catalogued.
catalogMap = ''
blackClasses = [self.toolName, self.flavourName, self.podTemplateName]
for blackClass in blackClasses:
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
catalogMap += "catalogMap['%s']['black'] = " \
"['portal_catalog']\n" % blackClass
repls['catalogMap'] = catalogMap
# Compute workflows
workflows = ''
for classDescr in self.getClasses(include='all'):
if hasattr(classDescr.klass, 'workflow'):
wfName = WorkflowDescriptor.getWorkflowName(
classDescr.klass.workflow)
workflows += '\n "%s":"%s",' % (classDescr.name, wfName)
repls['workflows'] = workflows
# Compute workflow instances initialisation
wfInit = ''
for workflowDescr in self.workflows:
@ -295,24 +361,7 @@ class Generator(AbstractGenerator):
for stateName in workflowDescr.getStateNames(ordered=True):
wfInit += 'wf._states.append("%s")\n' % stateName
wfInit += 'workflowInstances[%s] = wf\n' % className
# Compute imports
imports = ['import %s' % self.applicationName]
classDescrs = self.getClasses(include='custom')
for classDescr in (classDescrs + self.workflows):
theImport = 'import %s' % classDescr.klass.__module__
if theImport not in imports:
imports.append(theImport)
# Compute root classes
rootClasses = ''
for classDescr in self.classes:
if classDescr.isRoot():
rootClasses += "'%s'," % classDescr.name
# Compute list of add permissions
addPermissions = ''
for classDescr in self.classes:
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
self.applicationName, classDescr.name)
repls = self.repls.copy()
repls['workflowInstancesInit'] = wfInit
# Compute the list of ordered attributes (foward and backward, inherited
# included) for every Appy class.
attributes = []
@ -348,17 +397,22 @@ class Generator(AbstractGenerator):
aDict += '"%s":attributes["%s"][%d],' % \
(attrNames[i], classDescr.name, i)
attributesDict.append('"%s":{%s}' % (classDescr.name, aDict))
# Compute list of used roles for registering them if needed
repls['roles'] = ','.join(['"%s"' % r for r in \
self.getAllUsedRoles(appOnly=True)])
repls['rootClasses'] = rootClasses
repls['workflowInstancesInit'] = wfInit
repls['imports'] = '\n'.join(imports)
repls['attributes'] = ',\n '.join(attributes)
repls['attributesDict'] = ',\n '.join(attributesDict)
repls['defaultAddRoles'] = ','.join(
['"%s"' % r for r in self.config.defaultCreators])
repls['addPermissions'] = addPermissions
# Compute list of used roles for registering them if needed
specificRoles = self.getAllUsedRoles(plone=False)
repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles])
globalRoles = self.getAllUsedRoles(plone=False, local=False)
repls['gRoles'] = ','.join(['"%s"' % r.name for r in globalRoles])
grantableRoles = self.getAllUsedRoles(local=False, grantable=True)
repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles])
# Generate configuration options
repls['showPortlet'] = self.config.showPortlet
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
repls['languageSelector'] = self.config.languageSelector
repls['minimalistPlone'] = self.config.minimalistPlone
repls['appFrontPage'] = bool(self.config.frontPage)
self.copyFile('config.py', repls)
def generateInit(self):
@ -376,50 +430,6 @@ class Generator(AbstractGenerator):
repls['totalNumberOfTests'] = self.totalNumberOfTests
self.copyFile('__init__.py', repls)
def generateInstall(self):
# Compute lists of class names
allClassNames = '"%s",' % self.flavourName
allClassNames += '"%s",' % self.podTemplateName
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
allClassNames += appClassNames
# Compute imports
imports = []
for classDescr in self.classes:
theImport = 'import %s' % classDescr.klass.__module__
if theImport not in imports:
imports.append(theImport)
# Compute list of application classes
appClasses = []
for classDescr in self.classes:
k = classDescr.klass
appClasses.append('%s.%s' % (k.__module__, k.__name__))
# Compute classes whose instances must not be catalogued.
catalogMap = ''
blackClasses = [self.toolName, self.flavourName, self.podTemplateName]
for blackClass in blackClasses:
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
catalogMap += "catalogMap['%s']['black'] = " \
"['portal_catalog']\n" % blackClass
# Compute workflows
workflows = ''
for classDescr in self.getClasses(include='all'):
if hasattr(classDescr.klass, 'workflow'):
wfName = WorkflowDescriptor.getWorkflowName(
classDescr.klass.workflow)
workflows += '\n "%s":"%s",' % (classDescr.name, wfName)
# Generate the resulting file.
repls = self.repls.copy()
repls['allClassNames'] = allClassNames
repls['appClassNames'] = appClassNames
repls['catalogMap'] = catalogMap
repls['imports'] = '\n'.join(imports)
repls['appClasses'] = "[%s]" % ','.join(appClasses)
repls['minimalistPlone'] = self.config.minimalistPlone
repls['showPortlet'] = self.config.showPortlet
repls['appFrontPage'] = bool(self.config.frontPage)
repls['workflows'] = workflows
self.copyFile('Install.py', repls, destFolder='Extensions')
def generateWorkflows(self):
'''Generates the file that contains one function by workflow.
Those functions are called by Plone for registering the workflows.'''
@ -468,14 +478,18 @@ class Generator(AbstractGenerator):
'''Returns the descriptors for all the classes in the generated
gen-application. If p_include is "all", it includes the descriptors
for the config-related classes (tool, flavour, etc); if
p_include is "custom", it includes descriptors for the
config-related classes for which the user has created a sub-class.'''
p_include is "allButTool", it includes the same descriptors, the
tool excepted; if p_include is "custom", it includes descriptors
for the config-related classes for which the user has created a
sub-class.'''
if not include: return self.classes
else:
res = self.classes[:]
configClasses = [self.tool, self.flavour, self.podTemplate]
configClasses = [self.tool,self.flavour,self.podTemplate,self.user]
if include == 'all':
res += configClasses
elif include == 'allButTool':
res += configClasses[1:]
elif include == 'custom':
res += [c for c in configClasses if c.customized]
return res
@ -564,6 +578,7 @@ class Generator(AbstractGenerator):
repls['toolBody'] = Tool._appy_getBody()
repls['flavourBody'] = Flavour._appy_getBody()
repls['podTemplateBody'] = PodTemplate._appy_getBody()
repls['userBody'] = User._appy_getBody()
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
def generateTests(self):
@ -581,7 +596,8 @@ class Generator(AbstractGenerator):
# We need a front page, but no specific one has been given.
# So we will create a basic one that will simply display
# some translated text.
self.labels.append(msg('front_page_text', '', msg.FRONT_PAGE_TEXT))
self.labels.append(PoMessage('front_page_text', '',
PoMessage.FRONT_PAGE_TEXT))
repls['pageContent'] = '<span tal:replace="structure python: ' \
'tool.translateWithMapping(\'front_page_text\')"/>'
else:
@ -605,6 +621,9 @@ class Generator(AbstractGenerator):
Tool.flavours.klass = Flavour
if self.flavour.customized:
Tool.flavours.klass = self.flavour.klass
Tool.users.klass = User
if self.user.customized:
Tool.users.klass = self.user.klass
self.tool.generateSchema()
repls['fields'] = self.tool.schema
repls['methods'] = self.tool.methods
@ -659,6 +678,16 @@ class Generator(AbstractGenerator):
repls['wrapperClass'] = '%s_Wrapper' % self.podTemplate.name
self.copyFile('PodTemplate.py', repls,
destName='%s.py' % self.podTemplateName)
# Generate the User class
self.user.generateSchema()
self.labels += [ Msg(self.userName, '', Msg.USER),
Msg('%s_edit_descr' % self.userName, '', ' ')]
repls = self.repls.copy()
repls['fields'] = self.user.schema
repls['methods'] = self.user.methods
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
self.copyFile('UserTemplate.py', repls,
destName='%s.py' % self.userName)
def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application, for
@ -681,7 +710,7 @@ class Generator(AbstractGenerator):
implements = [baseClass]
for baseClass in classDescr.klass.__bases__:
if self.determineAppyType(baseClass) == 'class':
bcName = ClassDescriptor.getClassName(baseClass)
bcName = getClassName(baseClass)
parents.remove('ClassMixin')
parents.append(bcName)
implements.append(bcName)

View file

@ -18,30 +18,27 @@ class ZCTextIndexInfo:
class PloneInstaller:
'''This Plone installer runs every time the generated Plone product is
installed or uninstalled (in the Plone configuration interface).'''
def __init__(self, reinstall, productName, ploneSite, minimalistPlone,
appClasses, appClassNames, allClassNames, catalogMap, applicationRoles,
defaultAddRoles, workflows, appFrontPage, showPortlet, ploneStuff):
def __init__(self, reinstall, ploneSite, config):
# p_cfg is the configuration module of the Plone product.
self.reinstall = reinstall # Is it a fresh install or a re-install?
self.productName = productName
self.ploneSite = ploneSite
self.minimalistPlone = minimalistPlone # If True, lots of basic Plone
# stuff will be hidden.
self.appClasses = appClasses # The list of classes declared in the
# gen-application.
self.appClassNames = appClassNames # Names of those classes
self.allClassNames = allClassNames # Includes Flavour and PodTemplate
self.catalogMap = catalogMap # Indicates classes to be indexed or not
self.applicationRoles = applicationRoles # Roles defined in the app
self.defaultAddRoles = defaultAddRoles # The default roles that can add
# content
self.workflows = workflows # Dict whose keys are class names and whose
# values are workflow names (=the workflow
# used by the content type)
self.appFrontPage = appFrontPage # Does this app define a site-wide
# front page?
self.showPortlet = showPortlet # Must we show the application portlet?
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
self.attributes = ploneStuff['GLOBALS']['attributes']
self.config = cfg = config
# Unwrap some useful variables from config
self.productName = cfg.PROJECTNAME
self.minimalistPlone = cfg.minimalistPlone
self.appClasses = cfg.appClasses
self.appClassNames = cfg.appClassNames
self.allClassNames = cfg.allClassNames
self.catalogMap = cfg.catalogMap
self.applicationRoles = cfg.applicationRoles # Roles defined in the app
self.defaultAddRoles = cfg.defaultAddRoles
self.workflows = cfg.workflows
self.appFrontPage = cfg.appFrontPage
self.showPortlet = cfg.showPortlet
self.languages = cfg.languages
self.languageSelector = cfg.languageSelector
self.attributes = cfg.attributes
# A buffer for logging purposes
self.toLog = StringIO()
self.typeAliases = {'sharing': '', 'gethtml': '',
'(Default)': 'skynView', 'edit': 'skyn/edit',
@ -166,7 +163,7 @@ class PloneInstaller:
site.manage_delObjects(['skyn'])
# This way, if Appy has moved from one place to the other, the
# directory view will always refer to the correct place.
addDirView = self.ploneStuff['manage_addDirectoryView']
addDirView = self.config.manage_addDirectoryView
addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn')
def installTypes(self):
@ -174,11 +171,9 @@ class PloneInstaller:
gen-classes.'''
site = self.ploneSite
# Do Plone-based type registration
classes = self.ploneStuff['listTypes'](self.productName)
self.ploneStuff['installTypes'](site, self.toLog, classes,
self.productName)
self.ploneStuff['install_subskin'](site, self.toLog,
self.ploneStuff['GLOBALS'])
classes = self.config.listTypes(self.productName)
self.config.installTypes(site, self.toLog, classes, self.productName)
self.config.install_subskin(site, self.toLog, self.config.__dict__)
# Set appy view/edit pages for every created type
for className in self.allClassNames + ['%sTool' % self.productName]:
# I did not put the app tool in self.allClassNames because it
@ -204,7 +199,7 @@ class PloneInstaller:
factoryTool.manage_setPortalFactoryTypes(listOfTypeIds=factoryTypes)
# Configure CatalogMultiplex: tell what types will be catalogued or not.
atTool = getattr(site, self.ploneStuff['ARCHETYPETOOLNAME'])
atTool = getattr(site, self.config.ARCHETYPETOOLNAME)
for meta_type in self.catalogMap:
submap = self.catalogMap[meta_type]
current_catalogs = Set(
@ -294,7 +289,7 @@ class PloneInstaller:
try:
self.ploneSite.manage_addProduct[
self.productName].manage_addTool(self.toolName)
except self.ploneStuff['BadRequest']:
except self.config.BadRequest:
# If an instance with the same name already exists, this error will
# be unelegantly raised by Zope.
pass
@ -357,7 +352,7 @@ class PloneInstaller:
groups if needed.'''
site = self.ploneSite
data = list(site.__ac_roles__)
for role in self.applicationRoles:
for role in self.config.applicationRoles:
if not role in data:
data.append(role)
# Add to portal_role_manager
@ -373,11 +368,13 @@ class PloneInstaller:
pass
except AttributeError:
pass
# Create a specific group and grant him this role
# If it is a global role, create a specific group and grant him
# this role
if role not in self.config.applicationGlobalRoles: continue
group = '%s_group' % role
if not site.portal_groups.getGroupById(group):
site.portal_groups.addGroup(group, title=group)
site.portal_groups.setRolesForGroup(group, [role])
if site.portal_groups.getGroupById(group): continue # Already there
site.portal_groups.addGroup(group, title=group)
site.portal_groups.setRolesForGroup(group, [role])
site.__ac_roles__ = tuple(data)
def installWorkflows(self):
@ -386,7 +383,7 @@ class PloneInstaller:
for contentType, workflowName in self.workflows.iteritems():
# Register the workflow if needed
if workflowName not in wfTool.listWorkflows():
wfMethod = self.ploneStuff['ExternalMethod']('temp', 'temp',
wfMethod = self.config.ExternalMethod('temp', 'temp',
self.productName + '.workflows', 'create_%s' % workflowName)
workflow = wfMethod(self, workflowName)
wfTool._setObject(workflowName, workflow)
@ -401,18 +398,14 @@ class PloneInstaller:
cssName = self.productName + '.css'
cssTitle = self.productName + ' CSS styles'
cssInfo = {'id': cssName, 'title': cssTitle}
portalCss = self.ploneSite.portal_css
try:
portalCss = self.ploneSite.portal_css
try:
portalCss.unregisterResource(cssInfo['id'])
except:
pass
defaults = {'id': '', 'media': 'all', 'enabled': True}
defaults.update(cssInfo)
portalCss.registerStylesheet(**defaults)
portalCss.unregisterResource(cssInfo['id'])
except:
# No portal_css registry
pass
defaults = {'id': '', 'media': 'all', 'enabled': True}
defaults.update(cssInfo)
portalCss.registerStylesheet(**defaults)
def managePortlets(self):
'''Shows or hides the application-specific portlet and configures other
@ -456,6 +449,22 @@ class PloneInstaller:
if indexInfo:
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
def manageLanguages(self):
'''Manages the languages supported by the application.'''
if self.languageSelector:
# We must install the PloneLanguageTool if not done yet
qi = self.ploneSite.portal_quickinstaller
if not qi.isProductInstalled('PloneLanguageTool'):
qi.installProduct('PloneLanguageTool')
languageTool = self.ploneSite.portal_languages
defLanguage = self.languages[0]
languageTool.manage_setLanguageSettings(defaultLanguage=defLanguage,
supportedLanguages=self.languages, setContentN=None,
setCookieN=True, setRequestN=True, setPathN=True,
setForcelanguageUrls=True, setAllowContentLanguageFallback=None,
setUseCombinedLanguageCodes=None, displayFlags=False,
startNeutral=False)
def finalizeInstallation(self):
'''Performs some final installation steps.'''
site = self.ploneSite
@ -493,6 +502,7 @@ class PloneInstaller:
self.installStyleSheet()
self.managePortlets()
self.manageIndexes()
self.manageLanguages()
self.finalizeInstallation()
self.log("Installation of %s done." % self.productName)
@ -545,17 +555,16 @@ def traverseWrapper(self, path, response=None, validated_hook=None):
class ZopeInstaller:
'''This Zope installer runs every time Zope starts and encounters this
generated Zope product.'''
def __init__(self, zopeContext, productName, toolClass,
defaultAddContentPermission, addContentPermissions,
logger, ploneStuff, classes):
def __init__(self, zopeContext, toolClass, config, classes):
self.zopeContext = zopeContext
self.productName = productName
self.toolClass = toolClass
self.defaultAddContentPermission = defaultAddContentPermission
self.addContentPermissions = addContentPermissions
self.logger = logger
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
self.config = cfg = config
self.classes = classes
# Unwrap some useful config variables
self.productName = cfg.PROJECTNAME
self.logger = cfg.logger
self.defaultAddContentPermission = cfg.DEFAULT_ADD_CONTENT_PERMISSION
self.addContentPermissions = cfg.ADD_CONTENT_PERMISSIONS
def completeAppyTypes(self):
'''We complete here the initialisation process of every Appy type of
@ -574,23 +583,23 @@ class ZopeInstaller:
def installApplication(self):
'''Performs some application-wide installation steps.'''
register = self.ploneStuff['DirectoryView'].registerDirectory
register('skins', self.ploneStuff['product_globals'])
register = self.config.DirectoryView.registerDirectory
register('skins', self.config.__dict__)
# Register the appy skin folder among DirectoryView'able folders
register('skin', appy.getPath() + '/gen/plone25')
def installTool(self):
'''Installs the tool.'''
self.ploneStuff['ToolInit'](self.productName + ' Tools',
self.config.ToolInit(self.productName + ' Tools',
tools = [self.toolClass], icon='tool.gif').initialize(
self.zopeContext)
def installTypes(self):
'''Installs and configures the types defined in the application.'''
contentTypes, constructors, ftis = self.ploneStuff['process_types'](
self.ploneStuff['listTypes'](self.productName), self.productName)
contentTypes, constructors, ftis = self.config.process_types(
self.config.listTypes(self.productName), self.productName)
self.ploneStuff['cmfutils'].ContentInit(self.productName + ' Content',
self.config.cmfutils.ContentInit(self.productName + ' Content',
content_types = contentTypes,
permission = self.defaultAddContentPermission,
extra_constructors = constructors, fti = ftis).initialize(
@ -611,14 +620,14 @@ class ZopeInstaller:
global originalTraverse
if not originalTraverse:
# User tracking is not enabled yet. Do it now.
BaseRequest = self.ploneStuff['BaseRequest']
BaseRequest = self.config.BaseRequest
originalTraverse = BaseRequest.traverse
BaseRequest.traverse = traverseWrapper
def finalizeInstallation(self):
'''Performs some final installation steps.'''
# Apply customization policy if any
cp = self.ploneStuff['CustomizationPolicy']
cp = self.config.CustomizationPolicy
if cp and hasattr(cp, 'register'): cp.register(context)
def install(self):

View file

@ -18,33 +18,10 @@ POD_ERROR = 'An error occurred while generating the document. Please ' \
# ------------------------------------------------------------------------------
class FlavourMixin(AbstractMixin):
_appy_meta_type = 'Flavour'
def getPortalType(self, metaTypeOrAppyType):
def getPortalType(self, metaTypeOrAppyClass):
'''Returns the name of the portal_type that is based on
p_metaTypeOrAppyType in this flavour.'''
res = metaTypeOrAppyType
isPredefined = False
isAppy = False
appName = self.getProductConfig().PROJECTNAME
if not isinstance(res, basestring):
res = ClassDescriptor.getClassName(res)
isAppy = True
if res.find('Extensions_appyWrappers') != -1:
isPredefined = True
elems = res.split('_')
res = '%s%s' % (elems[1], elems[4])
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Tool):
# This is the custom tool
isPredefined = True
res = '%sTool' % appName
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Flavour):
# This is the custom Flavour
isPredefined = True
res = '%sFlavour' % appName
if not isPredefined:
number = self.appy().number
if number != 1:
res = '%s_%d' % (res, number)
return res
return self.getParentNode().getPortalType(metaTypeOrAppyClass)
def registerPortalTypes(self):
'''Registers, into portal_types, the portal types which are specific

View file

@ -1,8 +1,9 @@
# ------------------------------------------------------------------------------
import re, os, os.path, Cookie
from appy.shared.utils import getOsTempFolder
import appy.gen
from appy.gen import Type, Search, Selection
from appy.gen.utils import SomeObjects, sequenceTypes
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.wrappers import AbstractWrapper
@ -13,6 +14,17 @@ jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------
class ToolMixin(AbstractMixin):
_appy_meta_type = 'Tool'
def getPortalType(self, metaTypeOrAppyClass):
'''Returns the name of the portal_type that is based on
p_metaTypeOrAppyType in this flavour.'''
appName = self.getProductConfig().PROJECTNAME
if not isinstance(metaTypeOrAppyClass, basestring):
res = getClassName(metaTypeOrAppyClass, appName)
if res.find('Extensions_appyWrappers') != -1:
elems = res.split('_')
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):

View file

@ -0,0 +1,7 @@
# ------------------------------------------------------------------------------
from appy.gen.plone25.mixins import AbstractMixin
# ------------------------------------------------------------------------------
class UserMixin(AbstractMixin):
_appy_meta_type = 'UserMixin'
# ------------------------------------------------------------------------------

View file

@ -8,7 +8,7 @@
# ------------------------------------------------------------------------------
import os, os.path, types, mimetypes
import appy.gen
from appy.gen import Type, String, Selection
from appy.gen import Type, String, Selection, Role
from appy.gen.utils import *
from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.plone25.descriptors import ClassDescriptor
@ -338,14 +338,30 @@ class AbstractMixin:
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)
i += 1
# 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]
@ -413,18 +429,6 @@ class AbstractMixin:
res = sortedObjectsUids.index(obj.UID())
return res
def getAppyRefPortalType(self, fieldName):
'''Gets the portal type of objects linked to me through Ref field named
p_fieldName.'''
appyType = self.getAppyType(fieldName)
tool = self.getTool()
if self._appy_meta_type == 'Flavour':
flavour = self.appy()
else:
portalTypeName = self._appy_getPortalType(self.REQUEST)
flavour = tool.getFlavour(portalTypeName)
return self._appy_getAtType(appyType.klass, flavour)
def getAppyType(self, name, asDict=False, className=None):
'''Returns the Appy type named p_name. If no p_className is defined, the
field is supposed to belong to self's class.'''
@ -696,10 +700,10 @@ class AbstractMixin:
# Get the corresponding Appy transition
transition = workflow._transitionsMapping[transitionName]
user = self.portal_membership.getAuthenticatedMember()
if isinstance(transition.condition, basestring):
if isinstance(transition.condition, Role):
# It is a role. Transition may be triggered if the user has this
# role.
res = user.has_role(transition.condition, self)
res = user.has_role(transition.condition.name, self)
elif type(transition.condition) == types.FunctionType:
res = transition.condition(workflow, self.appy())
elif type(transition.condition) in (tuple, list):
@ -843,28 +847,6 @@ class AbstractMixin:
rq.appyWrappers[uid] = wrapper
return wrapper
def _appy_getAtType(self, appyClass, flavour=None):
'''Gets the name of the Archetypes class that corresponds to
p_appyClass (which is a Python class coming from the user
application). If p_flavour is specified, the method returns the name
of the specific Archetypes class in this flavour (ie suffixed with
the flavour number).'''
res = ClassDescriptor.getClassName(appyClass)
appName = self.getProductConfig().PROJECTNAME
if res.find('Extensions_appyWrappers') != -1:
# This is not a content type defined Maybe I am a tool or flavour
res = appName + appyClass.__name__
elif issubclass(appyClass, appy.gen.Tool):
# This is the custom tool
res = '%sTool' % appName
elif issubclass(appyClass, appy.gen.Flavour):
# This is the custom Flavour
res = '%sFlavour' % appName
else:
if flavour and flavour.number != 1:
res += '_%d' % flavour.number
return res
def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False,
noListIfSingleObj=False):
'''This method returns the list of objects linked to this one
@ -927,19 +909,14 @@ class AbstractMixin:
if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: continue
# Indeed, no possibility to create objects with such Ref
refContentTypeName = self.getAppyRefPortalType(appyType.name)
refContentType = getattr(self.portal_types, refContentTypeName)
refMetaType = refContentType.content_meta_type
if refMetaType not in addPermissions: continue
# Indeed, there is no specific "add" permission is defined for tool
# and flavour, for example.
appyClass = refContentType.wrapperClass.__bases__[-1]
refType = self.getTool().getPortalType(appyType.klass)
if refType not in addPermissions: continue
# Get roles that may add this content type
creators = getattr(appyClass, 'creators', None)
creators = getattr(appyType.klass, 'creators', None)
if not creators:
creators = self.getProductConfig().defaultAddRoles
# Add those creators to the list of creators for this meta_type
addPermission = addPermissions[refMetaType]
addPermission = addPermissions[refType]
if addPermission in allCreators:
allCreators[addPermission] = allCreators[\
addPermission].union(creators)

View file

@ -7,7 +7,7 @@
# ------------------------------------------------------------------------------
import copy, types
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection, Group
from appy.gen import *
# ------------------------------------------------------------------------------
class ModelClass:
@ -70,6 +70,25 @@ class ModelClass:
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
return res
class User(ModelClass):
# All methods defined below are fake. Real versions are in the wrapper.
title = String(show=False)
gm = {'group': 'main', 'multiplicity': (1,1)}
name = String(**gm)
firstName = String(**gm)
def showLogin(self): pass
def validateLogin(self): pass
login = String(show=showLogin, validator=validateLogin, **gm)
def showPassword(self): pass
def validatePassword(self): pass
password1 = String(format=String.PASSWORD, show=showPassword,
validator=validatePassword, **gm)
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
gm['multiplicity'] = (0, None)
roles = String(validator=Selection('getGrantableRoles'), **gm)
_appy_attributes = ['title', 'name', 'firstName', 'login',
'password1', 'password2', 'roles']
class PodTemplate(ModelClass):
description = String(format=String.TEXT)
podTemplate = File(multiplicity=(1,1))
@ -290,12 +309,18 @@ class Tool(ModelClass):
# First arg is None because we don't know yet if it will link
# to the predefined Flavour class or a custom class defined
# in the application.
def validPythonWithUno(self, value): pass
users = Ref(None, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool'), page='users',
shownInfo=('login', 'title', 'roles'), showHeaders=True)
# First arg is None because we don't know yet if it will link to the
# predefined User class or a custom class defined in the application.
def validPythonWithUno(self, value): pass # Real method in the wrapper
unoEnabledPython = String(group="connectionToOpenOffice",
validator=validPythonWithUno)
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30)
listBoxesMaximumWidth = Integer(default=100)
_appy_attributes = ['flavours', 'unoEnabledPython', 'openOfficePort',
'numberOfResultsPerPage', 'listBoxesMaximumWidth']
_appy_attributes = ['flavours', 'users', 'unoEnabledPython',
'openOfficePort', 'numberOfResultsPerPage',
'listBoxesMaximumWidth']
# ------------------------------------------------------------------------------

View file

@ -174,7 +174,7 @@
tal:content="structure python: tool.translate('%s_page_%s' % (contextObj.meta_type, aPage))">
</a>
</td>
<td>
<td align="right">
<img tal:define="nav request/nav|nothing;
nav python: test(nav, '&nav=%s' % nav, '')"
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"

View file

@ -104,7 +104,7 @@
objs refObjects/objects;
totalNumber refObjects/totalNumber;
batchSize refObjects/batchSize;
folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
folder python: contextObj.isPrincipiaFolderish and contextObj or contextObj.getParentNode();
flavour python:tool.getFlavour(contextObj);
linkedPortalType python:flavour.getPortalType(appyType['klass']);
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
@ -205,7 +205,7 @@
<tal:comment replace="nothing">Object title, shown here if not specified somewhere
else in appyType.shownInfo.</tal:comment>
<td tal:condition="python: 'title' not in appyType['shownInfo']"><metal:showObjectTitle
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>&nbsp;
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
</td>
<tal:comment replace="nothing">Additional fields that must be shown</tal:comment>
<td tal:repeat="widget widgets">
@ -217,7 +217,7 @@
innerRef python:True"
condition="python: widget['name'] != 'title'">
<metal:showField use-macro="portal/skyn/widgets/show/macros/field" />
</tal:showOtherField>&nbsp;
</tal:showOtherField>
</td>
<tal:comment replace="nothing">Actions</tal:comment>
<td align="right">
@ -248,7 +248,7 @@
requestValue python: request.get(rname, []);
inRequest python: request.has_key(rname);
allObjects python: contextObj.getSelectableAppyRefs(name);
refUids python: [o.UID() for o in here.getAppyRefs(name)['objects']];
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
<select tal:attributes="name rname;

View file

@ -26,14 +26,13 @@
isOneLine python: fmt in (0,3)">
<tal:choice condition="isSelect">
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True);
multiValued python: (widget['multiplicity'][1] != 1) and True or False"
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True)"
tal:attributes="name name;
id name;
multiple python: multiValued and 'multiple' or '';
multiple python: isMultiple and 'multiple' or '';
onchange python: isMaster and ('javascript:updateSlaves(getMasterValue(this), \'%s\')' % widget['id']) or '';
class widget/master_css;
size python: multiValued and widget['height'] or 1">
size python: isMultiple and widget['height'] or 1">
<option tal:repeat="possibleValue possibleValues"
tal:attributes="value python: possibleValue[0];
selected python:contextObj.fieldValueSelected(name, possibleValue[0], rawValue)"
@ -42,7 +41,8 @@
</tal:choice>
<tal:line condition="python: isOneLine and not isSelect">
<input tal:attributes="id name; name name; size widget/width;
value python: test(inRequest, requestValue, value)" type="text"/>
value python: test(inRequest, requestValue, value);
type python: (widget['format'] == 3) and 'password' or 'text'"/>
</tal:line>
<tal:textarea condition="python: fmt == 1">
<textarea tal:attributes="id name; name name;
@ -66,8 +66,14 @@
</metal:edit>
<tal:comment replace="nothing">Cell macro for a String.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/string/macros/view"/>
<metal:cell define-macro="cell"
tal:define="multipleValues python: value and isMultiple">
<tal:multiple condition="multipleValues"
content="python: ', '.join(value)">
</tal:multiple>
<tal:notMultiple condition="not: multipleValues">
<metal:call use-macro="portal/skyn/widgets/string/macros/view"/>
</tal:notMultiple>
</metal:cell>
<tal:comment replace="nothing">Search macro for a String.</tal:comment>

View file

@ -20,7 +20,6 @@ class <!flavourName!>(OrderedBaseFolder, FlavourMixin):
allowed_content_types = []
filter_content_types = 0
global_allow = 1
#content_icon = '<!flavourName!>.gif'
immediate_view = 'skyn/view'
default_view = 'skyn/view'
suppl_views = ()

View file

@ -1,38 +1,15 @@
<!codeHeader!>
from zExceptions import BadRequest
from Products.CMFCore.DirectoryView import manage_addDirectoryView
from Products.ExternalMethod.ExternalMethod import ExternalMethod
from Products.Archetypes.Extensions.utils import installTypes
from Products.Archetypes.Extensions.utils import install_subskin
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
from Products.Archetypes.atapi import listTypes
from Products.<!applicationName!>.config import applicationRoles,defaultAddRoles
from Products.<!applicationName!>.config import product_globals as GLOBALS
import appy.gen
from appy.gen.plone25.installer import PloneInstaller
<!imports!>
catalogMap = {}
<!catalogMap!>
appClasses = <!appClasses!>
appClassNames = [<!appClassNames!>]
allClassNames = [<!allClassNames!>]
workflows = {<!workflows!>}
showPortlet = <!showPortlet!>
import Products.<!applicationName!>.config as config
# ------------------------------------------------------------------------------
def install(self, reinstall=False):
'''Installation of product "<!applicationName!>"'''
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self,
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
catalogMap, applicationRoles, defaultAddRoles, workflows,
<!appFrontPage!>, showPortlet, globals())
return ploneInstaller.install()
'''Installation procedure.'''
return PloneInstaller(reinstall, self, config).install()
# ------------------------------------------------------------------------------
def uninstall(self, reinstall=False):
'''Uninstallation of product "<!applicationName!>"'''
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self,
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
catalogMap, applicationRoles, defaultAddRoles, workflows,
<!appFrontPage!>, showPortlet, globals())
return ploneInstaller.uninstall()
'''Uninstallation procedure.'''
return PloneInstaller(reinstall, self, config).uninstall()
# ------------------------------------------------------------------------------

View file

@ -14,19 +14,17 @@ class <!applicationName!>PodTemplate(BaseContent, PodTemplateMixin):
'''POD template.'''
security = ClassSecurityInfo()
__implements__ = (getattr(BaseContent,'__implements__',()),)
archetype_name = '<!applicationName!>PodTemplate'
meta_type = '<!applicationName!>PodTemplate'
portal_type = '<!applicationName!>PodTemplate'
allowed_content_types = []
filter_content_types = 0
global_allow = 1
#content_icon = '<!applicationName!>PodTemplate.gif'
immediate_view = 'skyn/view'
default_view = 'skyn/view'
suppl_views = ()
typeDescription = "<!applicationName!>PodTemplate"
typeDescMsgId = '<!applicationName!>_edit_descr'
typeDescMsgId = '<!applicationName!>PodTemplate_edit_descr'
wrapperClass = <!wrapperClass!>
schema = fullSchema
for elem in dir(PodTemplateMixin):

View file

@ -170,21 +170,13 @@ th {
/* overflow: visible; IE produces ugly results with this */
}
.listing {
margin: 0em 0em;
}
.listing { margin: 0em 0em; }
.listing td, .stx table td {
padding-right: 0.1em;
padding-left: 0.3em;
padding-top: 0.3em;
padding-bottom: 0em;
padding : 0.1em 0.3em 0.1em 0.3em;
border-top : 1px solid #8CACBB;
}
.vertical td {
padding-left: 0.3em;
}
.listing th, .stx table th { padding: 0.25em 0.3em; }
.vertical td { padding-left: 0.3em; }
.innerAppyTable {
border-width: 0px;

View file

@ -0,0 +1,34 @@
<!codeHeader!>
from AccessControl import ClassSecurityInfo
from Products.Archetypes.atapi import *
import Products.<!applicationName!>.config
from appy.gen.plone25.mixins.UserMixin import UserMixin
from Extensions.appyWrappers import <!wrapperClass!>
schema = Schema((<!fields!>
),)
fullSchema = BaseSchema.copy() + schema.copy()
class <!applicationName!>User(BaseContent, UserMixin):
'''Configuration flavour class for <!applicationName!>.'''
security = ClassSecurityInfo()
__implements__ = (getattr(BaseContent,'__implements__',()),)
archetype_name = '<!applicationName!>User'
meta_type = '<!applicationName!>User'
portal_type = '<!applicationName!>User'
allowed_content_types = []
filter_content_types = 0
global_allow = 1
immediate_view = 'skyn/view'
default_view = 'skyn/view'
suppl_views = ()
typeDescription = "<!applicationName!>User"
typeDescMsgId = '<!applicationName!>User_edit_descr'
i18nDomain = '<!applicationName!>'
schema = fullSchema
wrapperClass = <!wrapperClass!>
for elem in dir(UserMixin):
if not elem.startswith('__'): security.declarePublic(elem)
<!commonMethods!>
<!methods!>
registerType(<!applicationName!>User, '<!applicationName!>')

View file

@ -25,20 +25,8 @@ def countTest():
numberOfExecutedTests += 1
# ------------------------------------------------------------------------------
from config import *
from ZPublisher.HTTPRequest import BaseRequest
import logging
try:
import CustomizationPolicy
except ImportError:
CustomizationPolicy = None
from Products.CMFCore import utils as cmfutils
from Products.CMFCore import DirectoryView
from Products.CMFPlone.utils import ToolInit
from Products.Archetypes.atapi import *
from Products.Archetypes import listTypes
import config
from appy.gen.plone25.installer import ZopeInstaller
logger = logging.getLogger(PROJECTNAME)
# Zope-level installation of the generated product. ----------------------------
def initialize(context):
@ -46,8 +34,6 @@ def initialize(context):
# I need to do those imports here; else, types and add permissions will not
# be registered.
classes = [<!classes!>]
ZopeInstaller(context, PROJECTNAME,
<!applicationName!>Tool.<!applicationName!>Tool,
DEFAULT_ADD_CONTENT_PERMISSION, ADD_CONTENT_PERMISSIONS,
logger, globals(), classes).install()
ZopeInstaller(context, <!applicationName!>Tool.<!applicationName!>Tool,
config, classes).install()
# ------------------------------------------------------------------------------

View file

@ -4,6 +4,7 @@ from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
<!imports!>
@ -11,6 +12,9 @@ from AccessControl import ClassSecurityInfo
class PodTemplate(PodTemplateWrapper):
'''This class represents a POD template for this application.'''
<!podTemplateBody!>
class User(UserWrapper):
'''This class represents a user.'''
<!userBody!>
class Flavour(FlavourWrapper):
'''This class represents the Appy class used for defining a flavour.'''
folder=True

View file

@ -10,10 +10,26 @@ import Extensions.appyWrappers as wraps
# every Archetype instance has a method "getProductConfig" that returns this
# module.
from persistent.list import PersistentList
from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest
try:
import CustomizationPolicy
except ImportError:
CustomizationPolicy = None
from OFS.Image import File
from DateTime import DateTime
from Products.CMFCore import utils as cmfutils
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.PloneBatch import Batch
from Products.CMFPlone.utils import ToolInit
from Products.CMFCore import DirectoryView
from Products.CMFCore.DirectoryView import manage_addDirectoryView
from Products.ExternalMethod.ExternalMethod import ExternalMethod
from Products.Archetypes.Extensions.utils import installTypes
from Products.Archetypes.Extensions.utils import install_subskin
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
from Products.Archetypes import listTypes, process_types
import appy.gen
import logging
logger = logging.getLogger('<!applicationName!>')
@ -25,10 +41,19 @@ DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
ADD_CONTENT_PERMISSIONS = {
<!addPermissions!>}
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
product_globals = globals()
applicationRoles = [<!roles!>]
rootClasses = [<!rootClasses!>]
# Applications classes, in various formats and flavours
rootClasses = [<!rootClasses!>]
appClasses = <!appClasses!>
appClassNames = [<!appClassNames!>]
allClassNames = [<!allClassNames!>]
# List of classes that must be hidden from the catalog
catalogMap = {}
<!catalogMap!>
# Dict whose keys are class names and whose values are workflow names (=the
# workflow used by the content type)
workflows = {<!workflows!>}
# In the following dict, we keep one instance for every Appy workflow defined
# in the application. Those prototypical instances will be used for executing
# user-defined actions and transitions. For each instance, we add a special
@ -43,4 +68,16 @@ attributes = {<!attributes!>}
# In the followinf dict, we store, for every Appy class, a dict of appy types
# keyed by their names.
attributesDict = {<!attributesDict!>}
# Application roles
applicationRoles = [<!roles!>]
applicationGlobalRoles = [<!gRoles!>]
grantableRoles = [<!grRoles!>]
# Configuration options
showPortlet = <!showPortlet!>
languages = [<!languages!>]
languageSelector = <!languageSelector!>
minimalistPlone = <!minimalistPlone!>
appFrontPage = <!appFrontPage!>
# ------------------------------------------------------------------------------

View file

@ -0,0 +1,93 @@
# ------------------------------------------------------------------------------
from appy.gen.plone25.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
class UserWrapper(AbstractWrapper):
def showLogin(self):
'''When must we show the login field?'''
if self.o.isTemporary(): return 'edit'
return 'view'
def validateLogin(self, login):
'''Is this p_login valid?'''
# The login can't be the id of the whole site or "admin"
if (login == self.o.portal_url.getPortalObject().getId()) or \
(login == 'admin'):
return self.translate(u'This username is reserved. Please choose ' \
'a different name.', domain='plone')
# Check that the login does not already exist and check some
# Plone-specific rules.
pr = self.o.portal_registration
if not pr.isMemberIdAllowed(login):
return self.translate(u'The login name you selected is already ' \
'in use or is not valid. Please choose another.', domain='plone')
return True
def validatePassword(self, password):
'''Is this p_password valid?'''
# Password must be at least 5 chars length
if len(password) < 5:
return self.translate(u'Passwords must contain at least 5 letters.',
domain='plone')
return True
def showPassword(self):
'''When must we show the 2 fields for entering a password ?'''
if self.o.isTemporary(): return 'edit'
return False
def getGrantableRoles(self):
'''Returns the list of roles that the admin can grant to a user.'''
res = []
for role in self.o.getProductConfig().grantableRoles:
res.append( (role, self.translate('role_%s' % role)) )
return res
def validate(self, new, errors):
'''Inter-field validation.'''
page = self.request.get('page', 'main')
if page == 'main':
if hasattr(new, 'password1') and (new.password1 != new.password2):
msg = self.translate(u'Passwords do not match.', domain='plone')
errors.password1 = msg
errors.password2 = msg
def onEdit(self, created):
self.title = self.firstName + ' ' + self.name
pm = self.o.portal_membership
if created:
# Create the corresponding Plone user
pm.addMember(self.login, self.password1, ('Member',), None)
# Remove our own password copies
self.password1 = self.password2 = ''
# Perform updates on the corresponding Plone user
ploneUser = self.o.portal_membership.getMemberById(self.login)
ploneUser.setMemberProperties({'fullname': self.title})
# Change group membership according to self.roles. Indeed, instead of
# granting roles directly to the user, we will add the user to a
# Appy-created group having this role.
userRoles = self.roles
userGroups = ploneUser.getGroups()
for role in self.o.getProductConfig().grantableRoles:
# Retrieve the group corresponding to this role
groupName = '%s_group' % role
if role == 'Manager': groupName = 'Administrators'
elif role == 'Reviewer': groupName = 'Reviewers'
group = self.o.portal_groups.getGroupById(groupName)
# Add or remove the user from this group according to its role(s).
if role in userRoles:
# Add the user if not already present in the group
if groupName not in userGroups:
group.addMember(self.login)
else:
# Remove the user if it was in the corresponding group
if groupName in userGroups:
group.removeMember(self.login)
# Call the custom user "onEdit" method if it exists
# XXX This code does not work.
if len(self.__class__.__bases__) > 1:
customUser = self.__class__.__bases__[-1]
# There is a custom user class
if customUser.__dict__.has_key('onEdit'):
customUser.__dict__['onEdit'](self, created)
# ------------------------------------------------------------------------------

View file

@ -151,13 +151,13 @@ class AbstractWrapper:
isField = isinstance(fieldNameOrClass, basestring)
# Determine the portal type of the object to create
if isField:
fieldName = fieldNameOrClass
idPrefix = fieldName
portalType = self.o.getAppyRefPortalType(fieldName)
fieldName = idPrefix = fieldNameOrClass
appyType = self.o.getAppyType(fieldName)
portalType = self.tool.o.getPortalType(appyType.klass)
else:
theClass = fieldNameOrClass
idPrefix = theClass.__name__
portalType = self.o._appy_getAtType(theClass, self.flavour.o)
klass = fieldNameOrClass
idPrefix = klass.__name__
portalType = self.tool.o.getPortalType(klass)
# Determine object id
if kwargs.has_key('id'):
objId = kwargs['id']

View file

@ -40,6 +40,7 @@ class PoMessage:
'comment every time a transition is triggered'
MSG_showAllStatesInPhaseFor = 'Show all states in phase'
POD_TEMPLATE = 'POD template'
USER = 'User'
POD_ASKACTION = 'Trigger related action'
DEFAULT_VALID_ERROR = 'Please fill or correct this.'
REF_NO = 'No object.'

View file

@ -308,4 +308,21 @@ class FileWrapper:
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
return
return filePath
# ------------------------------------------------------------------------------
def getClassName(klass, appName=None):
'''Generates, from appy-class p_klass, the name of the corresponding
Archetypes class. For some classes, name p_appName is required: it is
part of the class name.'''
moduleName = klass.__module__
if (moduleName == 'appy.gen.plone25.model') or \
moduleName.endswith('.appyWrappers'):
# This is a model (generation time or run time)
res = appName + klass.__name__
elif klass.__bases__ and (klass.__bases__[-1].__module__ == 'appy.gen'):
# This is a customized class (inherits from appy.gen.Tool, User,...)
res = appName + klass.__bases__[-1].__name__
else: # This is a standard class
res = klass.__module__.replace('.', '_') + '_' + klass.__name__
return res
# ------------------------------------------------------------------------------

View file

@ -5,20 +5,178 @@
# ------------------------------------------------------------------------------
import os, os.path
# List of names of language in their own language ------------------------------
# It was copied from Plone 2.5.5 (PloneLanguageTool), don't know any "authentic
# source" for that.
nativeNames = {
'aa' : 'магIарул мацI',
'ab' : 'бызшәа',
'af' : 'Afrikaans',
'am' : 'አማርኛ',
'ar' : 'العربية',
'as' : 'অসমিয়া',
'ay' : 'Aymara',
'az' : 'Azəri Türkçəsi',
'ba' : 'Bashkir',
'be' : 'Беларускі',
'bg' : 'Български',
'bh' : 'Bihari',
'bi' : 'Bislama',
'bn' : 'বাংলা',
'bo' : 'བོད་སྐད་',
'bs' : 'Bosanski',
'br' : 'Brezhoneg',
'ca' : 'Català',
'ch' : 'Chamoru',
'co' : 'Corsu',
'cs' : 'Čeština',
'cy' : 'Cymraeg',
'da' : 'Dansk',
'de' : 'Deutsch',
'dz' : 'རྫོང་ཁ',
'el' : 'Ελληνικά',
'en' : 'English',
'eo' : 'Esperanto',
'es' : 'Español',
'et' : 'Eesti',
'eu' : 'Euskara',
'fa' : 'فارسی',
'fi' : 'Suomi',
'fj' : 'Fiji',
'fo' : 'Føroyska',
'fr' : 'Français',
'fy' : 'Frysk',
'ga' : 'Gaeilge',
'gd' : 'Gàidhlig',
'gl' : 'Galego',
'gn' : 'Guarani',
'gu' : 'ગુજરાતી',
'gv' : 'Gaelg',
'ha' : 'هَوُس',
'he' : 'עברית',
'hi' : 'हिंदी',
'hr' : 'Hrvatski',
'hu' : 'Magyar',
'hy' : 'Հայերէն',
'ia' : 'Interlingua',
'id' : 'Bahasa Indonesia',
'ie' : 'Interlingue',
'ik' : 'Inupiak',
'is' : 'Íslenska',
'it' : 'Italiano',
'iu' : 'ᐃᓄᒃᑎᑐᑦ',
'ja' : '日本語',
'jbo': 'lojban',
'jw' : 'Basa Jawi',
'ka' : 'ქართული',
'kk' : 'ﻗﺎﺯﺍﻗﺸﺎ',
'kl' : 'Greenlandic',
'km' : 'ខ្មែរ',
'kn' : 'ಕನ್ನಡ',
'ko' : '한국어',
'ks' : 'काऽशुर',
'ku' : 'Kurdí',
'kw' : 'Kernewek',
'ky' : 'Кыргыз',
'la' : 'Latin',
'lb' : 'Lëtzebuergesch',
'li' : 'Limburgs',
'ln' : 'Lingala',
'lo' : 'ພາສາລາວ',
'lt' : 'Lietuviskai',
'lv' : 'Latviešu',
'mg' : 'Malagasy',
'mi' : 'Maori',
'mk' : 'Македонски',
'ml' : 'മലയാളം',
'mn' : 'Монгол',
'mo' : 'Moldavian',
'mr' : 'मराठी',
'ms' : 'Bahasa Melayu',
'mt' : 'Malti',
'my' : 'Burmese',
'na' : 'Nauru',
'ne' : 'नेपाली',
'nl' : 'Nederlands',
'no' : 'Norsk',
'nn' : 'Nynorsk',
'oc' : 'Languedoc',
'om' : 'Oromo',
'or' : 'ଓଡ଼ିଆ',
'pa' : 'ਪੰਜਾਬੀ',
'pl' : 'Polski',
'ps' : 'پښتو',
'pt' : 'Português',
'qu' : 'Quechua',
'rm' : 'Rumantsch',
'rn' : 'Kirundi',
'ro' : 'Română',
'ru' : 'Русский',
'rw' : 'Kiyarwanda',
'sa' : 'संस्कृत',
'sd' : 'Sindhi',
'se' : 'Northern Sámi',
'sg' : 'Sangho',
'sh' : 'Serbo-Croatian',
'si' : 'Singhalese',
'sk' : 'Slovenčina',
'sl' : 'Slovenščina',
'sm' : 'Samoan',
'sn' : 'Shona',
'so' : 'Somali',
'sq' : 'Shqip',
'sr' : 'српски',
'ss' : 'Siswati',
'st' : 'Sesotho',
'su' : 'Sudanese',
'sv' : 'Svenska',
'sw' : 'Kiswahili',
'ta' : 'தமிழ',
'te' : 'తెలుగు',
'tg' : 'Тоҷики',
'th' : 'ไทย',
'ti' : 'ትግርኛ',
'tk' : 'түркmенче',
'tl' : 'Tagalog',
'tn' : 'Setswana',
'to' : 'Lea faka-Tonga',
'tr' : 'Türkçe',
'ts' : 'Tsonga',
'tt' : 'татарча',
'tw' : 'Twi',
'ug' : 'Uigur',
'uk' : 'Українська',
'ur' : 'اردو',
'uz' : 'Ўзбекча',
'vi' : 'Tiếng Việt',
'vo' : 'Volapük',
'wa' : 'Walon',
'wo' : 'Wolof',
'xh' : 'isiXhosa',
'yi' : 'ײִדיש',
'yo' : 'Yorùbá',
'za' : 'Zhuang',
'zh' : '中文',
'zu' : 'isiZulu'
}
# ------------------------------------------------------------------------------
class Countries:
'''This class gives access to the country codes as standardized by
class Languages:
'''This class gives access to the language codes as standardized by
ISO-639. The file has been downloaded in July 2009 from
http://www.loc.gov/standards/iso639-2/ascii_8bits.html (UTF-8 version)'''
def __init__(self):
self.fileName = os.path.dirname(__file__) + '/CountryCodesIso639.2.txt'
self.fileName = os.path.dirname(__file__) + '/LanguageCodesIso639.2.txt'
self.languageCodes = []
# Names of languages in English
self.languageNames = []
# Names of languages in their language. It is not part of ISO 639.2 and
# is taken from dict languageNames above.
self.nativeNames = []
self.parseFile()
def parseFile(self):
'''Parses the language codes and names in the ISO file and puts them in
self.languageCodes and self.languageNames.'''
self.languageCodes, self.languageNames and self.nativeNames.'''
f = file(self.fileName)
for line in f:
if line.strip():
@ -27,11 +185,16 @@ class Countries:
# I take only those that have a 2-chars ISO-639-1 code.
self.languageCodes.append(lineElems[2])
self.languageNames.append(lineElems[3])
if lineElems[2] in nativeNames:
self.nativeNames.append(nativeNames[lineElems[2]])
else:
# Put the english name nevertheless.
self.nativeNames.append(lineElems[3])
f.close()
def exists(self, countryCode):
'''Is p_countryCode a valid 2-digits country code?'''
return countryCode in self.languageCodes
def exists(self, code):
'''Is p_code a valid 2-digits language/country code?'''
return code in self.languageCodes
def __repr__(self):
i = -1
@ -41,7 +204,7 @@ class Countries:
res += 'Language: ' + languageCode + ' - ' + self.languageNames[i]
res += '\n'
return res
countries = Countries() # As this is international, I instantiate it.
languages = Languages() # As this is international, I instantiate it.
# ------------------------------------------------------------------------------
class BelgianCities:

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------
import os, os.path, sys, traceback, unicodedata
import os, os.path, sys, traceback, unicodedata, shutil
# ------------------------------------------------------------------------------
class FolderDeleter:
@ -47,6 +47,29 @@ def cleanFolder(folder, exts=extsToClean, verbose=False):
if verbose: print 'Removing %s...' % fileToRemove
os.remove(fileToRemove)
def copyFolder(source, dest, cleanDest=False):
'''Copies the content of folder p_source to folder p_dest. p_dest is
created, with intermediary subfolders if required. If p_cleanDest is
True, it removes completely p_dest if it existed.'''
dest = os.path.abspath(dest)
# Delete the dest folder if required
if os.path.exists(dest) and cleanDest:
FolderDeleter.delete(dest)
# Create the dest folder if it does not exist
if not os.path.exists(dest):
os.makedirs(dest)
# Copy the content of p_source to p_dest.
for name in os.listdir(source):
sourceName = os.path.join(source, name)
destName = os.path.join(dest, name)
if os.path.isfile(sourceName):
# Copy a single file
shutil.copy(sourceName, destName)
elif os.path.isdir(sourceName):
# Copy a subfolder (recursively)
copyFolder(sourceName, destName)
# ------------------------------------------------------------------------------
class Traceback:
'''Dumps the last traceback into a string.'''