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 import os, os.path, sys, shutil
from optparse import OptionParser from optparse import OptionParser
from appy.shared.utils import cleanFolder from appy.shared.utils import cleanFolder, copyFolder
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class NewError(Exception): pass class NewError(Exception): pass
@ -103,7 +103,7 @@ class NewScript:
os.system(cmd) os.system(cmd)
else: else:
# Copy thre product into the instance # Copy thre product into the instance
shutil.copytree(folderName, destFolder) copyFolder(folderName, destFolder)
uglyChunks = ('pkg_resources', '.declare_namespace(') uglyChunks = ('pkg_resources', '.declare_namespace(')
def findPythonPackageInEgg(self, currentFolder): def findPythonPackageInEgg(self, currentFolder):
@ -199,7 +199,7 @@ class NewScript:
# A Zope product. Copy its content in Products. # A Zope product. Copy its content in Products.
innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder)) innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder))
destFolder = j(productsFolder, os.path.basename(innerFolder)) destFolder = j(productsFolder, os.path.basename(innerFolder))
shutil.copytree(innerFolder, destFolder) copyFolder(innerFolder, destFolder)
else: else:
# A standard Python package. Copy its content in lib/python. # A standard Python package. Copy its content in lib/python.
# Go into the subFolder that is not EGG-INFO. # Go into the subFolder that is not EGG-INFO.
@ -218,7 +218,7 @@ class NewScript:
# libFolder. # libFolder.
innerFolder = self.getSubFolder(eggFolder) innerFolder = self.getSubFolder(eggFolder)
destFolder = j(productsFolder,os.path.basename(innerFolder)) destFolder = j(productsFolder,os.path.basename(innerFolder))
shutil.copytree(innerFolder, destFolder) copyFolder(innerFolder, destFolder)
else: else:
packageFolder = self.findPythonPackageInEgg(eggFolder) packageFolder = self.findPythonPackageInEgg(eggFolder)
# Create the destination folder(s) in the instance, # Create the destination folder(s) in the instance,
@ -252,7 +252,7 @@ class NewScript:
else: else:
destFolder = libFolder destFolder = libFolder
destFolder = j(destFolder, os.path.basename(packageFolder)) destFolder = j(destFolder, os.path.basename(packageFolder))
shutil.copytree(packageFolder, destFolder) copyFolder(packageFolder, destFolder)
self.patchPlone(productsFolder, libFolder) self.patchPlone(productsFolder, libFolder)
def manageArgs(self, args): 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.layout import defaultFieldLayouts
from appy.gen.po import PoMessage from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \ from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
FileWrapper FileWrapper, getClassName
from appy.shared.data import countries from appy.shared.data import languages
# Default Appy permissions ----------------------------------------------------- # Default Appy permissions -----------------------------------------------------
r, w, d = ('read', 'write', 'delete') r, w, d = ('read', 'write', 'delete')
@ -14,7 +14,8 @@ digit = re.compile('[0-9]')
alpha = re.compile('[a-zA-Z0-9]') alpha = re.compile('[a-zA-Z0-9]')
letter = re.compile('[a-zA-Z]') letter = re.compile('[a-zA-Z]')
nullValues = (None, '', ' ') nullValues = (None, '', ' ')
validatorTypes = (types.FunctionType, type(re.compile(''))) validatorTypes = (types.FunctionType, types.UnboundMethodType,
type(re.compile('')))
emptyTuple = () emptyTuple = ()
# Descriptor classes used for refining descriptions of elements in types # Descriptor classes used for refining descriptions of elements in types
@ -383,14 +384,7 @@ class Type:
self.name = name self.name = name
# Determine ids of i18n labels for this field # Determine ids of i18n labels for this field
if not klass: prefix = appName if not klass: prefix = appName
elif klass.__module__.endswith('.appyWrappers'): else: prefix = getClassName(klass, appName)
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__
self.labelId = '%s_%s' % (prefix, name) self.labelId = '%s_%s' % (prefix, name)
self.descrId = self.labelId + '_descr' self.descrId = self.labelId + '_descr'
self.helpId = self.labelId + '_help' self.helpId = self.labelId + '_help'
@ -583,7 +577,7 @@ class Type:
value = self.getStorableValue(value) value = self.getStorableValue(value)
if self.validator and (type(self.validator) in validatorTypes): if self.validator and (type(self.validator) in validatorTypes):
obj = obj.appy() obj = obj.appy()
if type(self.validator) == validatorTypes[0]: if type(self.validator) != validatorTypes[-1]:
# It is a custom function. Execute it. # It is a custom function. Execute it.
try: try:
validValue = self.validator(obj, value) validValue = self.validator(obj, value)
@ -598,7 +592,7 @@ class Type:
return str(e) return str(e)
except: except:
return obj.translate('%s_valid' % self.labelId) return obj.translate('%s_valid' % self.labelId)
elif type(self.validator) == validatorTypes[1]: else:
# It is a regular expression # It is a regular expression
if not self.validator.match(value): if not self.validator.match(value):
# If the regular expression is among the default ones, we # If the regular expression is among the default ones, we
@ -739,7 +733,7 @@ class String(Type):
# Maximum size is 34 chars # Maximum size is 34 chars
if (len(v) < 8) or (len(v) > 34): return False if (len(v) < 8) or (len(v) > 34): return False
# 2 first chars must be a valid country code # 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. # 2 next chars are a control code whose value must be between 0 and 96.
try: try:
code = int(v[2:4]) code = int(v[2:4])
@ -768,7 +762,7 @@ class String(Type):
for c in value[:4]: for c in value[:4]:
if not letter.match(c): return False if not letter.match(c): return False
# 2 next chars must be a valid country code # 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 # Last chars represent some location within a country (a city, a
# province...). They can only be letters or figures. # province...). They can only be letters or figures.
for c in value[6:]: for c in value[6:]:
@ -1356,24 +1350,71 @@ class Pod(Type):
self.validable = False self.validable = False
# Workflow-specific types ------------------------------------------------------ # 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: class State:
def __init__(self, permissions, initial=False, phase='main', show=True): def __init__(self, permissions, initial=False, phase='main', show=True):
self.permissions = permissions #~{s_permissionName:[s_roleName]}~ This self.usedRoles = {}
# dict gives, for every permission managed by a workflow, the list of # The following dict ~{s_permissionName:[s_roleName|Role_role]}~
# roles for which the permission is granted in this state. # gives, for every permission managed by a workflow, the list of roles
# Standard permissions are 'read', 'write' and 'delete'. # for which the permission is granted in this state. Standard
# permissions are 'read', 'write' and 'delete'.
self.permissions = permissions
self.initial = initial self.initial = initial
self.phase = phase self.phase = phase
self.show = show self.show = show
def getUsedRoles(self): # Standardize the way roles are expressed within self.permissions
res = set() self.standardizeRoles()
for roleValue in self.permissions.itervalues():
if isinstance(roleValue, basestring): def getRole(self, role):
res.add(roleValue) '''p_role can be the name of a role or a Role instance. If it is the
elif roleValue: name of a role, this method returns self.usedRoles[role] if it
for role in roleValue: exists, or creates a Role instance, puts it in self.usedRoles and
res.add(role) returns it else. If it is a Role instance, the method stores it in
return list(res) 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): def getTransitions(self, transitions, selfIsFromState=True):
'''Among p_transitions, returns those whose fromState is p_self (if '''Among p_transitions, returns those whose fromState is p_self (if
p_selfIsFromState is True) or those whose toState 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): if self in t.getStates(selfIsFromState):
res.append(t) res.append(t)
return res return res
def getPermissions(self): def getPermissions(self):
'''If you get the permissions mapping through self.permissions, dict '''If you get the permissions mapping through self.permissions, dict
values may be of different types (a list of roles, a single role or 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 # transition at several places in the state-transition diagram. It may
# be useful for "undo" transitions, for example. # be useful for "undo" transitions, for example.
self.condition = condition self.condition = condition
if isinstance(condition, basestring):
# The condition specifies the name of a role.
self.condition = Role(condition)
self.action = action self.action = action
self.notify = notify # If not None, it is a method telling who must be self.notify = notify # If not None, it is a method telling who must be
# notified by email after the transition has been executed. # notified by email after the transition has been executed.
@ -1414,14 +1459,14 @@ class Transition:
# the transition. It will only be possible by code. # the transition. It will only be possible by code.
def getUsedRoles(self): def getUsedRoles(self):
'''If self.condition is specifies a role.''' '''self.condition can specify a role.'''
res = [] res = []
if isinstance(self.condition, basestring): if isinstance(self.condition, Role):
res = [self.condition] res.append(self.condition)
return res return res
def isSingle(self): 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.''' Else, returns False.'''
return isinstance(self.states[0], State) return isinstance(self.states[0], State)
@ -1517,13 +1562,16 @@ class Selection:
return value return value
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Tool: class Model: pass
class Tool(Model):
'''If you want so define a custom tool class, she must inherit from this '''If you want so define a custom tool class, she must inherit from this
class.''' class.'''
class Flavour: class Flavour(Model):
'''A flavour represents a given group of configuration options. If you want '''A flavour represents a given group of configuration options. If you want
to define a custom flavour class, she must inherit from this class.''' to define a custom flavour class, she must inherit from this class.'''
def __init__(self, name): self.name = name def __init__(self, name): self.name = name
class User(Model):
'''If you want to extend or modify the User class, subclass me.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Config: class Config:
@ -1544,6 +1592,11 @@ class Config:
# For every language code that you specify in this list, appy.gen will # For every language code that you specify in this list, appy.gen will
# produce and maintain translation files. # produce and maintain translation files.
self.languages = ['en'] 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 # People having one of these roles will be able to create instances
# of classes defined in your application. # of classes defined in your application.
self.defaultCreators = ['Manager', 'Owner'] self.defaultCreators = ['Manager', 'Owner']

View file

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

View file

@ -185,6 +185,6 @@ class Table(LayoutElement):
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
defaultPageLayouts = { 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!'} defaultFieldLayouts = {'view': 'l;f!', 'edit': 'lrv;f!'}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -12,9 +12,9 @@ import appy.gen
import appy.gen.descriptors import appy.gen.descriptors
from appy.gen.po import PoMessage from appy.gen.po import PoMessage
from appy.gen import Date, String, State, Transition, Type, Search, \ from appy.gen import Date, String, State, Transition, Type, Search, \
Selection, Import Selection, Import, Role
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \ from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
sequenceTypes sequenceTypes, getClassName
TABS = 4 # Number of blanks in a Python indentation. TABS = 4 # Number of blanks in a Python indentation.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -110,13 +110,7 @@ class FieldDescriptor:
# Update the list of referers # Update the list of referers
self.generator.addReferer(self, relationship) self.generator.addReferer(self, relationship)
# Add the widget label for the back reference # Add the widget label for the back reference
refClassName = ClassDescriptor.getClassName(self.appyType.klass) refClassName = getClassName(self.appyType.klass, self.applicationName)
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
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute) backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute) poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
poMsg.produceNiceDefault() 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 # 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 # know yet every sub-class. So we store field definitions here; the
# Generator will propagate them later. # Generator will propagate them later.
self.name = self.getClassName(klass) self.name = getClassName(self.klass, generator.applicationName)
self.predefined = False self.predefined = False
self.customized = False self.customized = False
@ -276,11 +270,6 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
# Currently, we generate Archetypes fields for Refs only. # Currently, we generate Archetypes fields for Refs only.
self.schema += '\n' + fieldDef 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): def isAbstract(self):
'''Is self.klass abstract?''' '''Is self.klass abstract?'''
res = False res = False
@ -322,7 +311,14 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
'''Gets the specific creators defined for this class.''' '''Gets the specific creators defined for this class.'''
res = [] res = []
if self.klass.__dict__.has_key('creators') and self.klass.creators: 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 return res
def getCreateMean(self, type='Import'): def getCreateMean(self, type='Import'):
@ -379,7 +375,6 @@ class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.''' '''Represents the POD-specific fields that must be added to the tool.'''
def __init__(self, klass, generator): def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sTool' % generator.applicationName
self.modelClass = self.klass self.modelClass = self.klass
self.predefined = True self.predefined = True
self.customized = False self.customized = False
@ -405,7 +400,6 @@ class FlavourClassDescriptor(ClassDescriptor):
for the generated application.''' for the generated application.'''
def __init__(self, klass, generator): def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sFlavour' % generator.applicationName
self.attributesByClass = klass._appy_classes self.attributesByClass = klass._appy_classes
self.modelClass = self.klass self.modelClass = self.klass
self.predefined = True self.predefined = True
@ -431,13 +425,37 @@ class PodTemplateClassDescriptor(ClassDescriptor):
'''Represents a POD template.''' '''Represents a POD template.'''
def __init__(self, klass, generator): def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sPodTemplate' % generator.applicationName
self.modelClass = self.klass self.modelClass = self.klass
self.predefined = True self.predefined = True
self.customized = False self.customized = False
def getParents(self, allClasses=()): return ['PodTemplate'] def getParents(self, allClasses=()): return ['PodTemplate']
def isRoot(self): return False 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): class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
'''Represents a workflow.''' '''Represents a workflow.'''
# How to map Appy permissions to Plone permissions ? # How to map Appy permissions to Plone permissions ?
@ -497,11 +515,11 @@ class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
permissionsMapping = {} permissionsMapping = {}
for permission, roles in state.getPermissions().iteritems(): for permission, roles in state.getPermissions().iteritems():
for plonePerm in self.getPlonePermissions(permission): 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 # Add 'Review portal content' to anyone; this is not a security
# problem because we limit the triggering of every transition # problem because we limit the triggering of every transition
# individually. # individually.
allRoles = self.generator.getAllUsedRoles() allRoles = [r.name for r in self.generator.getAllUsedRoles()]
if 'Manager' not in allRoles: allRoles.append('Manager') if 'Manager' not in allRoles: allRoles.append('Manager')
permissionsMapping['Review portal content'] = allRoles permissionsMapping['Review portal content'] = allRoles
res[stateName] = (tNames, permissionsMapping) res[stateName] = (tNames, permissionsMapping)

View file

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

View file

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

View file

@ -18,33 +18,10 @@ POD_ERROR = 'An error occurred while generating the document. Please ' \
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class FlavourMixin(AbstractMixin): class FlavourMixin(AbstractMixin):
_appy_meta_type = 'Flavour' _appy_meta_type = 'Flavour'
def getPortalType(self, metaTypeOrAppyType): def getPortalType(self, metaTypeOrAppyClass):
'''Returns the name of the portal_type that is based on '''Returns the name of the portal_type that is based on
p_metaTypeOrAppyType in this flavour.''' p_metaTypeOrAppyType in this flavour.'''
res = metaTypeOrAppyType return self.getParentNode().getPortalType(metaTypeOrAppyClass)
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
def registerPortalTypes(self): def registerPortalTypes(self):
'''Registers, into portal_types, the portal types which are specific '''Registers, into portal_types, the portal types which are specific

View file

@ -1,8 +1,9 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re, os, os.path, Cookie import re, os, os.path, Cookie
from appy.shared.utils import getOsTempFolder from appy.shared.utils import getOsTempFolder
import appy.gen
from appy.gen import Type, Search, Selection 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 import AbstractMixin
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
from appy.gen.plone25.wrappers import AbstractWrapper from appy.gen.plone25.wrappers import AbstractWrapper
@ -13,6 +14,17 @@ jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ToolMixin(AbstractMixin): class ToolMixin(AbstractMixin):
_appy_meta_type = 'Tool' _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): def getFlavour(self, contextObjOrPortalType, appy=False):
'''Gets the flavour that corresponds to p_contextObjOrPortalType.''' '''Gets the flavour that corresponds to p_contextObjOrPortalType.'''
if isinstance(contextObjOrPortalType, basestring): 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 os, os.path, types, mimetypes
import appy.gen 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.utils import *
from appy.gen.layout import Table, defaultPageLayouts from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.plone25.descriptors import ClassDescriptor from appy.gen.plone25.descriptors import ClassDescriptor
@ -338,14 +338,30 @@ class AbstractMixin:
i = res.startNumber i = res.startNumber
# Is it possible and more efficient to perform a single query in # Is it possible and more efficient to perform a single query in
# uid_catalog and get the result in the order of specified uids? # uid_catalog and get the result in the order of specified uids?
toUnlink = []
while i < (res.startNumber + res.batchSize): while i < (res.startNumber + res.batchSize):
if i >= res.totalNumber: break if i >= res.totalNumber: break
refUid = sortedUids[i] refUid = sortedUids[i]
refObject = self.uid_catalog(UID=refUid)[0].getObject() 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: if not ploneObjects:
refObject = refObject.appy() refObject = refObject.appy()
res.objects.append(refObject) 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 res.objects and noListIfSingleObj:
if appyType.multiplicity[1] == 1: if appyType.multiplicity[1] == 1:
res.objects = res.objects[0] res.objects = res.objects[0]
@ -413,18 +429,6 @@ class AbstractMixin:
res = sortedObjectsUids.index(obj.UID()) res = sortedObjectsUids.index(obj.UID())
return res 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): def getAppyType(self, name, asDict=False, className=None):
'''Returns the Appy type named p_name. If no p_className is defined, the '''Returns the Appy type named p_name. If no p_className is defined, the
field is supposed to belong to self's class.''' field is supposed to belong to self's class.'''
@ -696,10 +700,10 @@ class AbstractMixin:
# Get the corresponding Appy transition # Get the corresponding Appy transition
transition = workflow._transitionsMapping[transitionName] transition = workflow._transitionsMapping[transitionName]
user = self.portal_membership.getAuthenticatedMember() 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 # It is a role. Transition may be triggered if the user has this
# role. # role.
res = user.has_role(transition.condition, self) res = user.has_role(transition.condition.name, self)
elif type(transition.condition) == types.FunctionType: elif type(transition.condition) == types.FunctionType:
res = transition.condition(workflow, self.appy()) res = transition.condition(workflow, self.appy())
elif type(transition.condition) in (tuple, list): elif type(transition.condition) in (tuple, list):
@ -843,28 +847,6 @@ class AbstractMixin:
rq.appyWrappers[uid] = wrapper rq.appyWrappers[uid] = wrapper
return 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, def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False,
noListIfSingleObj=False): noListIfSingleObj=False):
'''This method returns the list of objects linked to this one '''This method returns the list of objects linked to this one
@ -927,19 +909,14 @@ class AbstractMixin:
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 Ref
refContentTypeName = self.getAppyRefPortalType(appyType.name) refType = self.getTool().getPortalType(appyType.klass)
refContentType = getattr(self.portal_types, refContentTypeName) if refType not in addPermissions: continue
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]
# Get roles that may add this content type # Get roles that may add this content type
creators = getattr(appyClass, 'creators', None) creators = getattr(appyType.klass, 'creators', None)
if not creators: if not creators:
creators = self.getProductConfig().defaultAddRoles creators = self.getProductConfig().defaultAddRoles
# Add those creators to the list of creators for this meta_type # Add those creators to the list of creators for this meta_type
addPermission = addPermissions[refMetaType] addPermission = addPermissions[refType]
if addPermission in allCreators: if addPermission in allCreators:
allCreators[addPermission] = allCreators[\ allCreators[addPermission] = allCreators[\
addPermission].union(creators) addPermission].union(creators)

View file

@ -7,7 +7,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import copy, types import copy, types
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection, Group from appy.gen import *
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ModelClass: class ModelClass:
@ -70,6 +70,25 @@ class ModelClass:
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType)) res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
return res 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): class PodTemplate(ModelClass):
description = String(format=String.TEXT) description = String(format=String.TEXT)
podTemplate = File(multiplicity=(1,1)) 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 # First arg is None because we don't know yet if it will link
# to the predefined Flavour class or a custom class defined # to the predefined Flavour class or a custom class defined
# in the application. # 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", unoEnabledPython = String(group="connectionToOpenOffice",
validator=validPythonWithUno) validator=validPythonWithUno)
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)
_appy_attributes = ['flavours', 'unoEnabledPython', 'openOfficePort', _appy_attributes = ['flavours', 'users', 'unoEnabledPython',
'numberOfResultsPerPage', 'listBoxesMaximumWidth'] 'openOfficePort', 'numberOfResultsPerPage',
'listBoxesMaximumWidth']
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

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

View file

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

View file

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

View file

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

View file

@ -1,38 +1,15 @@
<!codeHeader!> <!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 import appy.gen
from appy.gen.plone25.installer import PloneInstaller from appy.gen.plone25.installer import PloneInstaller
<!imports!> import Products.<!applicationName!>.config as config
catalogMap = {}
<!catalogMap!>
appClasses = <!appClasses!>
appClassNames = [<!appClassNames!>]
allClassNames = [<!allClassNames!>]
workflows = {<!workflows!>}
showPortlet = <!showPortlet!>
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def install(self, reinstall=False): def install(self, reinstall=False):
'''Installation of product "<!applicationName!>"''' '''Installation procedure.'''
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self, return PloneInstaller(reinstall, self, config).install()
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
catalogMap, applicationRoles, defaultAddRoles, workflows,
<!appFrontPage!>, showPortlet, globals())
return ploneInstaller.install()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def uninstall(self, reinstall=False): def uninstall(self, reinstall=False):
'''Uninstallation of product "<!applicationName!>"''' '''Uninstallation procedure.'''
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self, return PloneInstaller(reinstall, self, config).uninstall()
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
catalogMap, applicationRoles, defaultAddRoles, workflows,
<!appFrontPage!>, showPortlet, globals())
return ploneInstaller.uninstall()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

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

View file

@ -170,21 +170,13 @@ th {
/* overflow: visible; IE produces ugly results with this */ /* overflow: visible; IE produces ugly results with this */
} }
.listing { .listing { margin: 0em 0em; }
margin: 0em 0em;
}
.listing td, .stx table td { .listing td, .stx table td {
padding-right: 0.1em; padding : 0.1em 0.3em 0.1em 0.3em;
padding-left: 0.3em;
padding-top: 0.3em;
padding-bottom: 0em;
border-top : 1px solid #8CACBB; border-top : 1px solid #8CACBB;
} }
.listing th, .stx table th { padding: 0.25em 0.3em; }
.vertical td { .vertical td { padding-left: 0.3em; }
padding-left: 0.3em;
}
.innerAppyTable { .innerAppyTable {
border-width: 0px; 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 numberOfExecutedTests += 1
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from config import * import config
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
from appy.gen.plone25.installer import ZopeInstaller from appy.gen.plone25.installer import ZopeInstaller
logger = logging.getLogger(PROJECTNAME)
# Zope-level installation of the generated product. ---------------------------- # Zope-level installation of the generated product. ----------------------------
def initialize(context): def initialize(context):
@ -46,8 +34,6 @@ def initialize(context):
# I need to do those imports here; else, types and add permissions will not # I need to do those imports here; else, types and add permissions will not
# be registered. # be registered.
classes = [<!classes!>] classes = [<!classes!>]
ZopeInstaller(context, PROJECTNAME, ZopeInstaller(context, <!applicationName!>Tool.<!applicationName!>Tool,
<!applicationName!>Tool.<!applicationName!>Tool, config, classes).install()
DEFAULT_ADD_CONTENT_PERMISSION, ADD_CONTENT_PERMISSIONS,
logger, globals(), 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.ToolWrapper import ToolWrapper
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
from Globals import InitializeClass from Globals import InitializeClass
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
<!imports!> <!imports!>
@ -11,6 +12,9 @@ from AccessControl import ClassSecurityInfo
class PodTemplate(PodTemplateWrapper): class PodTemplate(PodTemplateWrapper):
'''This class represents a POD template for this application.''' '''This class represents a POD template for this application.'''
<!podTemplateBody!> <!podTemplateBody!>
class User(UserWrapper):
'''This class represents a user.'''
<!userBody!>
class Flavour(FlavourWrapper): class Flavour(FlavourWrapper):
'''This class represents the Appy class used for defining a flavour.''' '''This class represents the Appy class used for defining a flavour.'''
folder=True folder=True

View file

@ -10,10 +10,26 @@ import Extensions.appyWrappers as wraps
# every Archetype instance has a method "getProductConfig" that returns this # every Archetype instance has a method "getProductConfig" that returns this
# module. # module.
from persistent.list import PersistentList 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 OFS.Image import File
from DateTime import DateTime from DateTime import DateTime
from Products.CMFCore import utils as cmfutils
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.PloneBatch import Batch 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 import logging
logger = logging.getLogger('<!applicationName!>') logger = logging.getLogger('<!applicationName!>')
@ -25,10 +41,19 @@ DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
ADD_CONTENT_PERMISSIONS = { ADD_CONTENT_PERMISSIONS = {
<!addPermissions!>} <!addPermissions!>}
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles)) 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 following dict, we keep one instance for every Appy workflow defined
# in the application. Those prototypical instances will be used for executing # in the application. Those prototypical instances will be used for executing
# user-defined actions and transitions. For each instance, we add a special # 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 # In the followinf dict, we store, for every Appy class, a dict of appy types
# keyed by their names. # keyed by their names.
attributesDict = {<!attributesDict!>} 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) isField = isinstance(fieldNameOrClass, basestring)
# Determine the portal type of the object to create # Determine the portal type of the object to create
if isField: if isField:
fieldName = fieldNameOrClass fieldName = idPrefix = fieldNameOrClass
idPrefix = fieldName appyType = self.o.getAppyType(fieldName)
portalType = self.o.getAppyRefPortalType(fieldName) portalType = self.tool.o.getPortalType(appyType.klass)
else: else:
theClass = fieldNameOrClass klass = fieldNameOrClass
idPrefix = theClass.__name__ idPrefix = klass.__name__
portalType = self.o._appy_getAtType(theClass, self.flavour.o) portalType = self.tool.o.getPortalType(klass)
# Determine object id # Determine object id
if kwargs.has_key('id'): if kwargs.has_key('id'):
objId = kwargs['id'] objId = kwargs['id']

View file

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

View file

@ -308,4 +308,21 @@ class FileWrapper:
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error') tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
return return
return filePath 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 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: class Languages:
'''This class gives access to the country codes as standardized by '''This class gives access to the language codes as standardized by
ISO-639. The file has been downloaded in July 2009 from ISO-639. The file has been downloaded in July 2009 from
http://www.loc.gov/standards/iso639-2/ascii_8bits.html (UTF-8 version)''' http://www.loc.gov/standards/iso639-2/ascii_8bits.html (UTF-8 version)'''
def __init__(self): def __init__(self):
self.fileName = os.path.dirname(__file__) + '/CountryCodesIso639.2.txt' self.fileName = os.path.dirname(__file__) + '/LanguageCodesIso639.2.txt'
self.languageCodes = [] self.languageCodes = []
# Names of languages in English
self.languageNames = [] 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() self.parseFile()
def parseFile(self): def parseFile(self):
'''Parses the language codes and names in the ISO file and puts them in '''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) f = file(self.fileName)
for line in f: for line in f:
if line.strip(): if line.strip():
@ -27,11 +185,16 @@ class Countries:
# I take only those that have a 2-chars ISO-639-1 code. # I take only those that have a 2-chars ISO-639-1 code.
self.languageCodes.append(lineElems[2]) self.languageCodes.append(lineElems[2])
self.languageNames.append(lineElems[3]) 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() f.close()
def exists(self, countryCode): def exists(self, code):
'''Is p_countryCode a valid 2-digits country code?''' '''Is p_code a valid 2-digits language/country code?'''
return countryCode in self.languageCodes return code in self.languageCodes
def __repr__(self): def __repr__(self):
i = -1 i = -1
@ -41,7 +204,7 @@ class Countries:
res += 'Language: ' + languageCode + ' - ' + self.languageNames[i] res += 'Language: ' + languageCode + ' - ' + self.languageNames[i]
res += '\n' res += '\n'
return res return res
countries = Countries() # As this is international, I instantiate it. languages = Languages() # As this is international, I instantiate it.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class BelgianCities: class BelgianCities:

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. # 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: class FolderDeleter:
@ -47,6 +47,29 @@ def cleanFolder(folder, exts=extsToClean, verbose=False):
if verbose: print 'Removing %s...' % fileToRemove if verbose: print 'Removing %s...' % fileToRemove
os.remove(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: class Traceback:
'''Dumps the last traceback into a string.''' '''Dumps the last traceback into a string.'''