Bugfix in new.py; added new user management.
This commit is contained in:
parent
fa974239f3
commit
eb52c1bb7d
10
bin/new.py
10
bin/new.py
|
@ -5,7 +5,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, shutil
|
||||
from optparse import OptionParser
|
||||
from appy.shared.utils import cleanFolder
|
||||
from appy.shared.utils import cleanFolder, copyFolder
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class NewError(Exception): pass
|
||||
|
@ -103,7 +103,7 @@ class NewScript:
|
|||
os.system(cmd)
|
||||
else:
|
||||
# Copy thre product into the instance
|
||||
shutil.copytree(folderName, destFolder)
|
||||
copyFolder(folderName, destFolder)
|
||||
|
||||
uglyChunks = ('pkg_resources', '.declare_namespace(')
|
||||
def findPythonPackageInEgg(self, currentFolder):
|
||||
|
@ -199,7 +199,7 @@ class NewScript:
|
|||
# A Zope product. Copy its content in Products.
|
||||
innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder))
|
||||
destFolder = j(productsFolder, os.path.basename(innerFolder))
|
||||
shutil.copytree(innerFolder, destFolder)
|
||||
copyFolder(innerFolder, destFolder)
|
||||
else:
|
||||
# A standard Python package. Copy its content in lib/python.
|
||||
# Go into the subFolder that is not EGG-INFO.
|
||||
|
@ -218,7 +218,7 @@ class NewScript:
|
|||
# libFolder.
|
||||
innerFolder = self.getSubFolder(eggFolder)
|
||||
destFolder = j(productsFolder,os.path.basename(innerFolder))
|
||||
shutil.copytree(innerFolder, destFolder)
|
||||
copyFolder(innerFolder, destFolder)
|
||||
else:
|
||||
packageFolder = self.findPythonPackageInEgg(eggFolder)
|
||||
# Create the destination folder(s) in the instance,
|
||||
|
@ -252,7 +252,7 @@ class NewScript:
|
|||
else:
|
||||
destFolder = libFolder
|
||||
destFolder = j(destFolder, os.path.basename(packageFolder))
|
||||
shutil.copytree(packageFolder, destFolder)
|
||||
copyFolder(packageFolder, destFolder)
|
||||
self.patchPlone(productsFolder, libFolder)
|
||||
|
||||
def manageArgs(self, args):
|
||||
|
|
121
gen/__init__.py
121
gen/__init__.py
|
@ -5,8 +5,8 @@ from appy.gen.layout import Table
|
|||
from appy.gen.layout import defaultFieldLayouts
|
||||
from appy.gen.po import PoMessage
|
||||
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
|
||||
FileWrapper
|
||||
from appy.shared.data import countries
|
||||
FileWrapper, getClassName
|
||||
from appy.shared.data import languages
|
||||
|
||||
# Default Appy permissions -----------------------------------------------------
|
||||
r, w, d = ('read', 'write', 'delete')
|
||||
|
@ -14,7 +14,8 @@ digit = re.compile('[0-9]')
|
|||
alpha = re.compile('[a-zA-Z0-9]')
|
||||
letter = re.compile('[a-zA-Z]')
|
||||
nullValues = (None, '', ' ')
|
||||
validatorTypes = (types.FunctionType, type(re.compile('')))
|
||||
validatorTypes = (types.FunctionType, types.UnboundMethodType,
|
||||
type(re.compile('')))
|
||||
emptyTuple = ()
|
||||
|
||||
# Descriptor classes used for refining descriptions of elements in types
|
||||
|
@ -383,14 +384,7 @@ class Type:
|
|||
self.name = name
|
||||
# Determine ids of i18n labels for this field
|
||||
if not klass: prefix = appName
|
||||
elif klass.__module__.endswith('.appyWrappers'):
|
||||
prefix = appName + klass.__name__
|
||||
elif Tool in klass.__bases__:
|
||||
prefix = appName + 'Tool'
|
||||
elif Flavour in klass.__bases__:
|
||||
prefix = appName + 'Flavour'
|
||||
else:
|
||||
prefix = klass.__module__.replace('.', '_') + '_' + klass.__name__
|
||||
else: prefix = getClassName(klass, appName)
|
||||
self.labelId = '%s_%s' % (prefix, name)
|
||||
self.descrId = self.labelId + '_descr'
|
||||
self.helpId = self.labelId + '_help'
|
||||
|
@ -583,7 +577,7 @@ class Type:
|
|||
value = self.getStorableValue(value)
|
||||
if self.validator and (type(self.validator) in validatorTypes):
|
||||
obj = obj.appy()
|
||||
if type(self.validator) == validatorTypes[0]:
|
||||
if type(self.validator) != validatorTypes[-1]:
|
||||
# It is a custom function. Execute it.
|
||||
try:
|
||||
validValue = self.validator(obj, value)
|
||||
|
@ -598,7 +592,7 @@ class Type:
|
|||
return str(e)
|
||||
except:
|
||||
return obj.translate('%s_valid' % self.labelId)
|
||||
elif type(self.validator) == validatorTypes[1]:
|
||||
else:
|
||||
# It is a regular expression
|
||||
if not self.validator.match(value):
|
||||
# If the regular expression is among the default ones, we
|
||||
|
@ -739,7 +733,7 @@ class String(Type):
|
|||
# Maximum size is 34 chars
|
||||
if (len(v) < 8) or (len(v) > 34): return False
|
||||
# 2 first chars must be a valid country code
|
||||
if not countries.exists(v[:2].lower()): return False
|
||||
if not languages.exists(v[:2].lower()): return False
|
||||
# 2 next chars are a control code whose value must be between 0 and 96.
|
||||
try:
|
||||
code = int(v[2:4])
|
||||
|
@ -768,7 +762,7 @@ class String(Type):
|
|||
for c in value[:4]:
|
||||
if not letter.match(c): return False
|
||||
# 2 next chars must be a valid country code
|
||||
if not countries.exists(value[4:6].lower()): return False
|
||||
if not languages.exists(value[4:6].lower()): return False
|
||||
# Last chars represent some location within a country (a city, a
|
||||
# province...). They can only be letters or figures.
|
||||
for c in value[6:]:
|
||||
|
@ -1356,24 +1350,71 @@ class Pod(Type):
|
|||
self.validable = False
|
||||
|
||||
# Workflow-specific types ------------------------------------------------------
|
||||
class Role:
|
||||
'''Represents a role.'''
|
||||
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous',
|
||||
'Authenticated')
|
||||
ploneLocalRoles = ('Owner',)
|
||||
ploneUngrantableRoles = ('Anonymous', 'Authenticated')
|
||||
def __init__(self, name, local=False, grantable=True):
|
||||
self.name = name
|
||||
self.local = local # True if it can be used as local role only.
|
||||
# It is a standard Plone role or an application-specific one?
|
||||
self.plone = name in self.ploneRoles
|
||||
if self.plone and (name in self.ploneLocalRoles):
|
||||
self.local = True
|
||||
self.grantable = grantable
|
||||
if self.plone and (name in self.ploneUngrantableRoles):
|
||||
self.grantable = False
|
||||
# An ungrantable role is one that is, like the Anonymous or
|
||||
# Authenticated roles, automatically attributed to a user.
|
||||
|
||||
class State:
|
||||
def __init__(self, permissions, initial=False, phase='main', show=True):
|
||||
self.permissions = permissions #~{s_permissionName:[s_roleName]}~ This
|
||||
# dict gives, for every permission managed by a workflow, the list of
|
||||
# roles for which the permission is granted in this state.
|
||||
# Standard permissions are 'read', 'write' and 'delete'.
|
||||
self.usedRoles = {}
|
||||
# The following dict ~{s_permissionName:[s_roleName|Role_role]}~
|
||||
# gives, for every permission managed by a workflow, the list of roles
|
||||
# for which the permission is granted in this state. Standard
|
||||
# permissions are 'read', 'write' and 'delete'.
|
||||
self.permissions = permissions
|
||||
self.initial = initial
|
||||
self.phase = phase
|
||||
self.show = show
|
||||
def getUsedRoles(self):
|
||||
res = set()
|
||||
for roleValue in self.permissions.itervalues():
|
||||
if isinstance(roleValue, basestring):
|
||||
res.add(roleValue)
|
||||
elif roleValue:
|
||||
for role in roleValue:
|
||||
res.add(role)
|
||||
return list(res)
|
||||
# Standardize the way roles are expressed within self.permissions
|
||||
self.standardizeRoles()
|
||||
|
||||
def getRole(self, role):
|
||||
'''p_role can be the name of a role or a Role instance. If it is the
|
||||
name of a role, this method returns self.usedRoles[role] if it
|
||||
exists, or creates a Role instance, puts it in self.usedRoles and
|
||||
returns it else. If it is a Role instance, the method stores it in
|
||||
self.usedRoles if it not in it yet and returns it.'''
|
||||
if isinstance(role, basestring):
|
||||
if role in self.usedRoles:
|
||||
return self.usedRoles[role]
|
||||
else:
|
||||
theRole = Role(role)
|
||||
self.usedRoles[role] = theRole
|
||||
return theRole
|
||||
else:
|
||||
if role.name not in self.usedRoles:
|
||||
self.usedRoles[role.name] = role
|
||||
return role
|
||||
|
||||
def standardizeRoles(self):
|
||||
'''This method converts, within self.permissions, every role to a
|
||||
Role instance. Every used role is stored in self.usedRoles.'''
|
||||
for permission, roles in self.permissions.items():
|
||||
if isinstance(roles, basestring) or isinstance(roles, Role):
|
||||
self.permissions[permission] = [self.getRole(roles)]
|
||||
elif roles:
|
||||
rolesList = []
|
||||
for role in roles:
|
||||
rolesList.append(self.getRole(role))
|
||||
self.permissions[permission] = rolesList
|
||||
|
||||
def getUsedRoles(self): return self.usedRoles.values()
|
||||
|
||||
def getTransitions(self, transitions, selfIsFromState=True):
|
||||
'''Among p_transitions, returns those whose fromState is p_self (if
|
||||
p_selfIsFromState is True) or those whose toState is p_self (if
|
||||
|
@ -1383,6 +1424,7 @@ class State:
|
|||
if self in t.getStates(selfIsFromState):
|
||||
res.append(t)
|
||||
return res
|
||||
|
||||
def getPermissions(self):
|
||||
'''If you get the permissions mapping through self.permissions, dict
|
||||
values may be of different types (a list of roles, a single role or
|
||||
|
@ -1407,6 +1449,9 @@ class Transition:
|
|||
# transition at several places in the state-transition diagram. It may
|
||||
# be useful for "undo" transitions, for example.
|
||||
self.condition = condition
|
||||
if isinstance(condition, basestring):
|
||||
# The condition specifies the name of a role.
|
||||
self.condition = Role(condition)
|
||||
self.action = action
|
||||
self.notify = notify # If not None, it is a method telling who must be
|
||||
# notified by email after the transition has been executed.
|
||||
|
@ -1414,14 +1459,14 @@ class Transition:
|
|||
# the transition. It will only be possible by code.
|
||||
|
||||
def getUsedRoles(self):
|
||||
'''If self.condition is specifies a role.'''
|
||||
'''self.condition can specify a role.'''
|
||||
res = []
|
||||
if isinstance(self.condition, basestring):
|
||||
res = [self.condition]
|
||||
if isinstance(self.condition, Role):
|
||||
res.append(self.condition)
|
||||
return res
|
||||
|
||||
def isSingle(self):
|
||||
'''If this transitions is only define between 2 states, returns True.
|
||||
'''If this transition is only defined between 2 states, returns True.
|
||||
Else, returns False.'''
|
||||
return isinstance(self.states[0], State)
|
||||
|
||||
|
@ -1517,13 +1562,16 @@ class Selection:
|
|||
return value
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Tool:
|
||||
class Model: pass
|
||||
class Tool(Model):
|
||||
'''If you want so define a custom tool class, she must inherit from this
|
||||
class.'''
|
||||
class Flavour:
|
||||
class Flavour(Model):
|
||||
'''A flavour represents a given group of configuration options. If you want
|
||||
to define a custom flavour class, she must inherit from this class.'''
|
||||
def __init__(self, name): self.name = name
|
||||
class User(Model):
|
||||
'''If you want to extend or modify the User class, subclass me.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Config:
|
||||
|
@ -1544,6 +1592,11 @@ class Config:
|
|||
# For every language code that you specify in this list, appy.gen will
|
||||
# produce and maintain translation files.
|
||||
self.languages = ['en']
|
||||
# If languageSelector is True, on every page, a language selector will
|
||||
# allow to switch between languages defined in self.languages. Else,
|
||||
# the browser-defined language will be used for choosing the language
|
||||
# of returned pages.
|
||||
self.languageSelector = False
|
||||
# People having one of these roles will be able to create instances
|
||||
# of classes defined in your application.
|
||||
self.defaultCreators = ['Manager', 'Owner']
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, parser, symbol, token, types
|
||||
from appy.gen import Type, State, Config, Tool, Flavour
|
||||
from appy.gen import Type, State, Config, Tool, Flavour, User
|
||||
from appy.gen.descriptors import *
|
||||
from appy.gen.utils import produceNiceMessage
|
||||
import appy.pod, appy.pod.renderer
|
||||
|
@ -133,7 +133,8 @@ class Generator:
|
|||
# Default descriptor classes
|
||||
self.descriptorClasses = {
|
||||
'class': ClassDescriptor, 'tool': ClassDescriptor,
|
||||
'flavour': ClassDescriptor, 'workflow': WorkflowDescriptor}
|
||||
'flavour': ClassDescriptor, 'user': ClassDescriptor,
|
||||
'workflow': WorkflowDescriptor}
|
||||
# The following dict contains a series of replacements that need to be
|
||||
# applied to file templates to generate files.
|
||||
self.repls = {'applicationName': self.applicationName,
|
||||
|
@ -143,6 +144,7 @@ class Generator:
|
|||
self.classes = []
|
||||
self.tool = None
|
||||
self.flavour = None
|
||||
self.user = None
|
||||
self.workflows = []
|
||||
self.initialize()
|
||||
self.config = Config.getDefault()
|
||||
|
@ -235,6 +237,12 @@ class Generator:
|
|||
self.flavour = klass(moduleElem, attrs, self)
|
||||
else:
|
||||
self.flavour.update(moduleElem, attrs)
|
||||
elif issubclass(moduleElem, User):
|
||||
if not self.user:
|
||||
klass = self.descriptorClasses['user']
|
||||
self.user = klass(moduleElem, attrs, self)
|
||||
else:
|
||||
self.user.update(moduleElem, attrs)
|
||||
else:
|
||||
descriptorClass = self.descriptorClasses['class']
|
||||
descriptor = descriptorClass(moduleElem,attrs, self)
|
||||
|
|
|
@ -185,6 +185,6 @@ class Table(LayoutElement):
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
defaultPageLayouts = {
|
||||
'view': Table('m;-s|-n!-w;-b|'), 'edit': Table('m;-s|-n!-w;-b|')}
|
||||
'view': Table('m;-s|-n!-w|-b|'), 'edit': Table('m;-w|-b|')}
|
||||
defaultFieldLayouts = {'view': 'l;f!', 'edit': 'lrv;f!'}
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -12,9 +12,9 @@ import appy.gen
|
|||
import appy.gen.descriptors
|
||||
from appy.gen.po import PoMessage
|
||||
from appy.gen import Date, String, State, Transition, Type, Search, \
|
||||
Selection, Import
|
||||
Selection, Import, Role
|
||||
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
|
||||
sequenceTypes
|
||||
sequenceTypes, getClassName
|
||||
TABS = 4 # Number of blanks in a Python indentation.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -110,13 +110,7 @@ class FieldDescriptor:
|
|||
# Update the list of referers
|
||||
self.generator.addReferer(self, relationship)
|
||||
# Add the widget label for the back reference
|
||||
refClassName = ClassDescriptor.getClassName(self.appyType.klass)
|
||||
if issubclass(self.appyType.klass, ModelClass):
|
||||
refClassName = self.applicationName + self.appyType.klass.__name__
|
||||
elif issubclass(self.appyType.klass, appy.gen.Tool):
|
||||
refClassName = '%sTool' % self.applicationName
|
||||
elif issubclass(self.appyType.klass, appy.gen.Flavour):
|
||||
refClassName = '%sFlavour' % self.applicationName
|
||||
refClassName = getClassName(self.appyType.klass, self.applicationName)
|
||||
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
|
||||
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
|
||||
poMsg.produceNiceDefault()
|
||||
|
@ -240,7 +234,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
|||
# for child classes of this class as well, but at this time we don't
|
||||
# know yet every sub-class. So we store field definitions here; the
|
||||
# Generator will propagate them later.
|
||||
self.name = self.getClassName(klass)
|
||||
self.name = getClassName(self.klass, generator.applicationName)
|
||||
self.predefined = False
|
||||
self.customized = False
|
||||
|
||||
|
@ -276,11 +270,6 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
|||
# Currently, we generate Archetypes fields for Refs only.
|
||||
self.schema += '\n' + fieldDef
|
||||
|
||||
@staticmethod
|
||||
def getClassName(klass):
|
||||
'''Generates the name of the corresponding Archetypes class.'''
|
||||
return klass.__module__.replace('.', '_') + '_' + klass.__name__
|
||||
|
||||
def isAbstract(self):
|
||||
'''Is self.klass abstract?'''
|
||||
res = False
|
||||
|
@ -322,7 +311,14 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
|||
'''Gets the specific creators defined for this class.'''
|
||||
res = []
|
||||
if self.klass.__dict__.has_key('creators') and self.klass.creators:
|
||||
res += list(self.klass.creators)
|
||||
for creator in self.klass.creators:
|
||||
if isinstance(creator, Role):
|
||||
if creator.local:
|
||||
raise 'Local role "%s" cannot be used as a creator.' % \
|
||||
creator.name
|
||||
res.append(creator)
|
||||
else:
|
||||
res.append(Role(creator))
|
||||
return res
|
||||
|
||||
def getCreateMean(self, type='Import'):
|
||||
|
@ -379,7 +375,6 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
'''Represents the POD-specific fields that must be added to the tool.'''
|
||||
def __init__(self, klass, generator):
|
||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||
self.name = '%sTool' % generator.applicationName
|
||||
self.modelClass = self.klass
|
||||
self.predefined = True
|
||||
self.customized = False
|
||||
|
@ -405,7 +400,6 @@ class FlavourClassDescriptor(ClassDescriptor):
|
|||
for the generated application.'''
|
||||
def __init__(self, klass, generator):
|
||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||
self.name = '%sFlavour' % generator.applicationName
|
||||
self.attributesByClass = klass._appy_classes
|
||||
self.modelClass = self.klass
|
||||
self.predefined = True
|
||||
|
@ -431,13 +425,37 @@ class PodTemplateClassDescriptor(ClassDescriptor):
|
|||
'''Represents a POD template.'''
|
||||
def __init__(self, klass, generator):
|
||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||
self.name = '%sPodTemplate' % generator.applicationName
|
||||
self.modelClass = self.klass
|
||||
self.predefined = True
|
||||
self.customized = False
|
||||
def getParents(self, allClasses=()): return ['PodTemplate']
|
||||
def isRoot(self): return False
|
||||
|
||||
class UserClassDescriptor(ClassDescriptor):
|
||||
'''Represents an Archetypes-compliant class that corresponds to the User
|
||||
for the generated application.'''
|
||||
def __init__(self, klass, generator):
|
||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||
self.modelClass = self.klass
|
||||
self.predefined = True
|
||||
self.customized = False
|
||||
def getParents(self, allClasses=()):
|
||||
res = ['User']
|
||||
if self.customized:
|
||||
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
|
||||
return res
|
||||
def update(self, klass, attributes):
|
||||
'''This method is called by the generator when he finds a custom user
|
||||
definition. We must then add the custom user elements in this
|
||||
default User descriptor.'''
|
||||
self.orderedAttributes += attributes
|
||||
self.klass = klass
|
||||
self.customized = True
|
||||
def isFolder(self, klass=None): return True
|
||||
def isRoot(self): return False
|
||||
def generateSchema(self):
|
||||
ClassDescriptor.generateSchema(self, configClass=True)
|
||||
|
||||
class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
|
||||
'''Represents a workflow.'''
|
||||
# How to map Appy permissions to Plone permissions ?
|
||||
|
@ -497,11 +515,11 @@ class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
|
|||
permissionsMapping = {}
|
||||
for permission, roles in state.getPermissions().iteritems():
|
||||
for plonePerm in self.getPlonePermissions(permission):
|
||||
permissionsMapping[plonePerm] = roles
|
||||
permissionsMapping[plonePerm] = [r.name for r in roles]
|
||||
# Add 'Review portal content' to anyone; this is not a security
|
||||
# problem because we limit the triggering of every transition
|
||||
# individually.
|
||||
allRoles = self.generator.getAllUsedRoles()
|
||||
allRoles = [r.name for r in self.generator.getAllUsedRoles()]
|
||||
if 'Manager' not in allRoles: allRoles.append('Manager')
|
||||
permissionsMapping['Review portal content'] = allRoles
|
||||
res[stateName] = (tNames, permissionsMapping)
|
||||
|
|
|
@ -7,10 +7,12 @@ import appy.gen
|
|||
from appy.gen import *
|
||||
from appy.gen.po import PoMessage, PoFile, PoParser
|
||||
from appy.gen.generator import Generator as AbstractGenerator
|
||||
from model import ModelClass, PodTemplate, Flavour, Tool
|
||||
from appy.gen.utils import getClassName
|
||||
from model import ModelClass, PodTemplate, User, Flavour, Tool
|
||||
from descriptors import FieldDescriptor, ClassDescriptor, \
|
||||
WorkflowDescriptor, ToolClassDescriptor, \
|
||||
FlavourClassDescriptor, PodTemplateClassDescriptor
|
||||
FlavourClassDescriptor, PodTemplateClassDescriptor, \
|
||||
UserClassDescriptor
|
||||
|
||||
# Common methods that need to be defined on every Archetype class --------------
|
||||
COMMON_METHODS = '''
|
||||
|
@ -36,12 +38,14 @@ class Generator(AbstractGenerator):
|
|||
self.tool = ToolClassDescriptor(Tool, self)
|
||||
self.flavour = FlavourClassDescriptor(Flavour, self)
|
||||
self.podTemplate = PodTemplateClassDescriptor(PodTemplate, self)
|
||||
self.user = UserClassDescriptor(User, self)
|
||||
# i18n labels to generate
|
||||
self.labels = [] # i18n labels
|
||||
self.toolName = '%sTool' % self.applicationName
|
||||
self.flavourName = '%sFlavour' % self.applicationName
|
||||
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
||||
self.podTemplateName = '%sPodTemplate' % self.applicationName
|
||||
self.userName = '%sUser' % self.applicationName
|
||||
self.portletName = '%s_portlet' % self.applicationName.lower()
|
||||
self.queryName = '%s_query' % self.applicationName.lower()
|
||||
self.skinsFolder = 'skins/%s' % self.applicationName
|
||||
|
@ -54,7 +58,7 @@ class Generator(AbstractGenerator):
|
|||
{'toolName': self.toolName, 'flavourName': self.flavourName,
|
||||
'portletName': self.portletName, 'queryName': self.queryName,
|
||||
'toolInstanceName': self.toolInstanceName,
|
||||
'podTemplateName': self.podTemplateName,
|
||||
'podTemplateName': self.podTemplateName, 'userName': self.userName,
|
||||
'commonMethods': commonMethods})
|
||||
self.referers = {}
|
||||
|
||||
|
@ -149,18 +153,19 @@ class Generator(AbstractGenerator):
|
|||
msg('image_required', '', msg.IMAGE_REQUIRED),
|
||||
]
|
||||
# Create a label for every role added by this application
|
||||
for role in self.getAllUsedRoles(appOnly=True):
|
||||
self.labels.append(msg('role_%s' % role,'', role, niceDefault=True))
|
||||
for role in self.getAllUsedRoles():
|
||||
self.labels.append(msg('role_%s' % role.name,'', role.name,
|
||||
niceDefault=True))
|
||||
# Create basic files (config.py, Install.py, etc)
|
||||
self.generateTool()
|
||||
self.generateConfig()
|
||||
self.generateInit()
|
||||
self.generateInstall()
|
||||
self.generateWorkflows()
|
||||
self.generateWrappers()
|
||||
self.generateTests()
|
||||
if self.config.frontPage:
|
||||
self.generateFrontPage()
|
||||
self.copyFile('Install.py', self.repls, destFolder='Extensions')
|
||||
self.copyFile('configure.zcml', self.repls)
|
||||
self.copyFile('import_steps.xml', self.repls,
|
||||
destFolder='profiles/default')
|
||||
|
@ -227,41 +232,49 @@ class Generator(AbstractGenerator):
|
|||
if not poFile.generated:
|
||||
poFile.generate()
|
||||
|
||||
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous')
|
||||
def getAllUsedRoles(self, appOnly=False):
|
||||
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
|
||||
'''Produces a list of all the roles used within all workflows and
|
||||
classes defined in this application. If p_appOnly is True, it
|
||||
returns only roles which are specific to this application (ie it
|
||||
removes predefined Plone roles like Member, Manager, etc.'''
|
||||
res = []
|
||||
classes defined in this application.
|
||||
|
||||
If p_plone is True, it keeps only Plone-standard roles; if p_plone
|
||||
is False, it keeps only roles which are specific to this application;
|
||||
if p_plone is None it has no effect (so it keeps both roles).
|
||||
|
||||
If p_local is True, it keeps only local roles (ie, roles that can
|
||||
only be granted locally); if p_local is False, it keeps only "global"
|
||||
roles; if p_local is None it has no effect (so it keeps both roles).
|
||||
|
||||
If p_grantable is True, it keeps only roles that the admin can
|
||||
grant; if p_grantable is False, if keeps only ungrantable roles (ie
|
||||
those that are implicitly granted by the system like role
|
||||
"Authenticated"); if p_grantable is None it keeps both roles.'''
|
||||
allRoles = {} # ~{s_roleName:Role_role}~
|
||||
# Gather roles from workflow states and transitions
|
||||
for wfDescr in self.workflows:
|
||||
# Browse states and transitions
|
||||
for attr in dir(wfDescr.klass):
|
||||
attrValue = getattr(wfDescr.klass, attr)
|
||||
if isinstance(attrValue, State) or \
|
||||
isinstance(attrValue, Transition):
|
||||
res += attrValue.getUsedRoles()
|
||||
for role in attrValue.getUsedRoles():
|
||||
if role.name not in allRoles:
|
||||
allRoles[role.name] = role
|
||||
# Gather roles from "creators" attributes from every class
|
||||
for cDescr in self.getClasses(include='all'):
|
||||
res += cDescr.getCreators()
|
||||
res = list(set(res))
|
||||
if appOnly:
|
||||
for ploneRole in self.ploneRoles:
|
||||
if ploneRole in res:
|
||||
res.remove(ploneRole)
|
||||
for role in cDescr.getCreators():
|
||||
if role.name not in allRoles:
|
||||
allRoles[role.name] = role
|
||||
res = allRoles.values()
|
||||
# Filter the result according to parameters
|
||||
for p in ('plone', 'local', 'grantable'):
|
||||
if eval(p) != None:
|
||||
res = [r for r in res if eval('r.%s == %s' % (p, p))]
|
||||
return res
|
||||
|
||||
def addReferer(self, fieldDescr, relationship):
|
||||
'''p_fieldDescr is a Ref type definition. We will create in config.py a
|
||||
dict that lists all back references, by type.'''
|
||||
k = fieldDescr.appyType.klass
|
||||
if issubclass(k, ModelClass):
|
||||
refClassName = self.applicationName + k.__name__
|
||||
elif issubclass(k, appy.gen.Tool):
|
||||
refClassName = '%sTool' % self.applicationName
|
||||
elif issubclass(k, appy.gen.Flavour):
|
||||
refClassName = '%sFlavour' % self.applicationName
|
||||
else:
|
||||
refClassName = ClassDescriptor.getClassName(k)
|
||||
refClassName = getClassName(k, self.applicationName)
|
||||
if not self.referers.has_key(refClassName):
|
||||
self.referers[refClassName] = []
|
||||
self.referers[refClassName].append( (fieldDescr, relationship))
|
||||
|
@ -277,6 +290,59 @@ class Generator(AbstractGenerator):
|
|||
return res
|
||||
|
||||
def generateConfig(self):
|
||||
repls = self.repls.copy()
|
||||
# Compute imports
|
||||
imports = ['import %s' % self.applicationName]
|
||||
classDescrs = self.getClasses(include='custom')
|
||||
for classDescr in (classDescrs + self.workflows):
|
||||
theImport = 'import %s' % classDescr.klass.__module__
|
||||
if theImport not in imports:
|
||||
imports.append(theImport)
|
||||
repls['imports'] = '\n'.join(imports)
|
||||
# Compute default add roles
|
||||
repls['defaultAddRoles'] = ','.join(
|
||||
['"%s"' % r for r in self.config.defaultCreators])
|
||||
# Compute list of add permissions
|
||||
addPermissions = ''
|
||||
for classDescr in self.getClasses(include='allButTool'):
|
||||
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
||||
self.applicationName, classDescr.name)
|
||||
repls['addPermissions'] = addPermissions
|
||||
# Compute root classes
|
||||
rootClasses = ''
|
||||
for classDescr in self.classes:
|
||||
if classDescr.isRoot():
|
||||
rootClasses += "'%s'," % classDescr.name
|
||||
repls['rootClasses'] = rootClasses
|
||||
# Compute list of class definitions
|
||||
appClasses = []
|
||||
for classDescr in self.classes:
|
||||
k = classDescr.klass
|
||||
appClasses.append('%s.%s' % (k.__module__, k.__name__))
|
||||
repls['appClasses'] = "[%s]" % ','.join(appClasses)
|
||||
# Compute lists of class names
|
||||
allClassNames = '"%s",' % self.flavourName
|
||||
allClassNames += '"%s",' % self.podTemplateName
|
||||
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
|
||||
allClassNames += appClassNames
|
||||
repls['allClassNames'] = allClassNames
|
||||
repls['appClassNames'] = appClassNames
|
||||
# Compute classes whose instances must not be catalogued.
|
||||
catalogMap = ''
|
||||
blackClasses = [self.toolName, self.flavourName, self.podTemplateName]
|
||||
for blackClass in blackClasses:
|
||||
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
||||
catalogMap += "catalogMap['%s']['black'] = " \
|
||||
"['portal_catalog']\n" % blackClass
|
||||
repls['catalogMap'] = catalogMap
|
||||
# Compute workflows
|
||||
workflows = ''
|
||||
for classDescr in self.getClasses(include='all'):
|
||||
if hasattr(classDescr.klass, 'workflow'):
|
||||
wfName = WorkflowDescriptor.getWorkflowName(
|
||||
classDescr.klass.workflow)
|
||||
workflows += '\n "%s":"%s",' % (classDescr.name, wfName)
|
||||
repls['workflows'] = workflows
|
||||
# Compute workflow instances initialisation
|
||||
wfInit = ''
|
||||
for workflowDescr in self.workflows:
|
||||
|
@ -295,24 +361,7 @@ class Generator(AbstractGenerator):
|
|||
for stateName in workflowDescr.getStateNames(ordered=True):
|
||||
wfInit += 'wf._states.append("%s")\n' % stateName
|
||||
wfInit += 'workflowInstances[%s] = wf\n' % className
|
||||
# Compute imports
|
||||
imports = ['import %s' % self.applicationName]
|
||||
classDescrs = self.getClasses(include='custom')
|
||||
for classDescr in (classDescrs + self.workflows):
|
||||
theImport = 'import %s' % classDescr.klass.__module__
|
||||
if theImport not in imports:
|
||||
imports.append(theImport)
|
||||
# Compute root classes
|
||||
rootClasses = ''
|
||||
for classDescr in self.classes:
|
||||
if classDescr.isRoot():
|
||||
rootClasses += "'%s'," % classDescr.name
|
||||
# Compute list of add permissions
|
||||
addPermissions = ''
|
||||
for classDescr in self.classes:
|
||||
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
||||
self.applicationName, classDescr.name)
|
||||
repls = self.repls.copy()
|
||||
repls['workflowInstancesInit'] = wfInit
|
||||
# Compute the list of ordered attributes (foward and backward, inherited
|
||||
# included) for every Appy class.
|
||||
attributes = []
|
||||
|
@ -348,17 +397,22 @@ class Generator(AbstractGenerator):
|
|||
aDict += '"%s":attributes["%s"][%d],' % \
|
||||
(attrNames[i], classDescr.name, i)
|
||||
attributesDict.append('"%s":{%s}' % (classDescr.name, aDict))
|
||||
# Compute list of used roles for registering them if needed
|
||||
repls['roles'] = ','.join(['"%s"' % r for r in \
|
||||
self.getAllUsedRoles(appOnly=True)])
|
||||
repls['rootClasses'] = rootClasses
|
||||
repls['workflowInstancesInit'] = wfInit
|
||||
repls['imports'] = '\n'.join(imports)
|
||||
repls['attributes'] = ',\n '.join(attributes)
|
||||
repls['attributesDict'] = ',\n '.join(attributesDict)
|
||||
repls['defaultAddRoles'] = ','.join(
|
||||
['"%s"' % r for r in self.config.defaultCreators])
|
||||
repls['addPermissions'] = addPermissions
|
||||
# Compute list of used roles for registering them if needed
|
||||
specificRoles = self.getAllUsedRoles(plone=False)
|
||||
repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles])
|
||||
globalRoles = self.getAllUsedRoles(plone=False, local=False)
|
||||
repls['gRoles'] = ','.join(['"%s"' % r.name for r in globalRoles])
|
||||
grantableRoles = self.getAllUsedRoles(local=False, grantable=True)
|
||||
repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles])
|
||||
# Generate configuration options
|
||||
repls['showPortlet'] = self.config.showPortlet
|
||||
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
|
||||
repls['languageSelector'] = self.config.languageSelector
|
||||
repls['minimalistPlone'] = self.config.minimalistPlone
|
||||
|
||||
repls['appFrontPage'] = bool(self.config.frontPage)
|
||||
self.copyFile('config.py', repls)
|
||||
|
||||
def generateInit(self):
|
||||
|
@ -376,50 +430,6 @@ class Generator(AbstractGenerator):
|
|||
repls['totalNumberOfTests'] = self.totalNumberOfTests
|
||||
self.copyFile('__init__.py', repls)
|
||||
|
||||
def generateInstall(self):
|
||||
# Compute lists of class names
|
||||
allClassNames = '"%s",' % self.flavourName
|
||||
allClassNames += '"%s",' % self.podTemplateName
|
||||
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
|
||||
allClassNames += appClassNames
|
||||
# Compute imports
|
||||
imports = []
|
||||
for classDescr in self.classes:
|
||||
theImport = 'import %s' % classDescr.klass.__module__
|
||||
if theImport not in imports:
|
||||
imports.append(theImport)
|
||||
# Compute list of application classes
|
||||
appClasses = []
|
||||
for classDescr in self.classes:
|
||||
k = classDescr.klass
|
||||
appClasses.append('%s.%s' % (k.__module__, k.__name__))
|
||||
# Compute classes whose instances must not be catalogued.
|
||||
catalogMap = ''
|
||||
blackClasses = [self.toolName, self.flavourName, self.podTemplateName]
|
||||
for blackClass in blackClasses:
|
||||
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
||||
catalogMap += "catalogMap['%s']['black'] = " \
|
||||
"['portal_catalog']\n" % blackClass
|
||||
# Compute workflows
|
||||
workflows = ''
|
||||
for classDescr in self.getClasses(include='all'):
|
||||
if hasattr(classDescr.klass, 'workflow'):
|
||||
wfName = WorkflowDescriptor.getWorkflowName(
|
||||
classDescr.klass.workflow)
|
||||
workflows += '\n "%s":"%s",' % (classDescr.name, wfName)
|
||||
# Generate the resulting file.
|
||||
repls = self.repls.copy()
|
||||
repls['allClassNames'] = allClassNames
|
||||
repls['appClassNames'] = appClassNames
|
||||
repls['catalogMap'] = catalogMap
|
||||
repls['imports'] = '\n'.join(imports)
|
||||
repls['appClasses'] = "[%s]" % ','.join(appClasses)
|
||||
repls['minimalistPlone'] = self.config.minimalistPlone
|
||||
repls['showPortlet'] = self.config.showPortlet
|
||||
repls['appFrontPage'] = bool(self.config.frontPage)
|
||||
repls['workflows'] = workflows
|
||||
self.copyFile('Install.py', repls, destFolder='Extensions')
|
||||
|
||||
def generateWorkflows(self):
|
||||
'''Generates the file that contains one function by workflow.
|
||||
Those functions are called by Plone for registering the workflows.'''
|
||||
|
@ -468,14 +478,18 @@ class Generator(AbstractGenerator):
|
|||
'''Returns the descriptors for all the classes in the generated
|
||||
gen-application. If p_include is "all", it includes the descriptors
|
||||
for the config-related classes (tool, flavour, etc); if
|
||||
p_include is "custom", it includes descriptors for the
|
||||
config-related classes for which the user has created a sub-class.'''
|
||||
p_include is "allButTool", it includes the same descriptors, the
|
||||
tool excepted; if p_include is "custom", it includes descriptors
|
||||
for the config-related classes for which the user has created a
|
||||
sub-class.'''
|
||||
if not include: return self.classes
|
||||
else:
|
||||
res = self.classes[:]
|
||||
configClasses = [self.tool, self.flavour, self.podTemplate]
|
||||
configClasses = [self.tool,self.flavour,self.podTemplate,self.user]
|
||||
if include == 'all':
|
||||
res += configClasses
|
||||
elif include == 'allButTool':
|
||||
res += configClasses[1:]
|
||||
elif include == 'custom':
|
||||
res += [c for c in configClasses if c.customized]
|
||||
return res
|
||||
|
@ -564,6 +578,7 @@ class Generator(AbstractGenerator):
|
|||
repls['toolBody'] = Tool._appy_getBody()
|
||||
repls['flavourBody'] = Flavour._appy_getBody()
|
||||
repls['podTemplateBody'] = PodTemplate._appy_getBody()
|
||||
repls['userBody'] = User._appy_getBody()
|
||||
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
|
||||
|
||||
def generateTests(self):
|
||||
|
@ -581,7 +596,8 @@ class Generator(AbstractGenerator):
|
|||
# We need a front page, but no specific one has been given.
|
||||
# So we will create a basic one that will simply display
|
||||
# some translated text.
|
||||
self.labels.append(msg('front_page_text', '', msg.FRONT_PAGE_TEXT))
|
||||
self.labels.append(PoMessage('front_page_text', '',
|
||||
PoMessage.FRONT_PAGE_TEXT))
|
||||
repls['pageContent'] = '<span tal:replace="structure python: ' \
|
||||
'tool.translateWithMapping(\'front_page_text\')"/>'
|
||||
else:
|
||||
|
@ -605,6 +621,9 @@ class Generator(AbstractGenerator):
|
|||
Tool.flavours.klass = Flavour
|
||||
if self.flavour.customized:
|
||||
Tool.flavours.klass = self.flavour.klass
|
||||
Tool.users.klass = User
|
||||
if self.user.customized:
|
||||
Tool.users.klass = self.user.klass
|
||||
self.tool.generateSchema()
|
||||
repls['fields'] = self.tool.schema
|
||||
repls['methods'] = self.tool.methods
|
||||
|
@ -659,6 +678,16 @@ class Generator(AbstractGenerator):
|
|||
repls['wrapperClass'] = '%s_Wrapper' % self.podTemplate.name
|
||||
self.copyFile('PodTemplate.py', repls,
|
||||
destName='%s.py' % self.podTemplateName)
|
||||
# Generate the User class
|
||||
self.user.generateSchema()
|
||||
self.labels += [ Msg(self.userName, '', Msg.USER),
|
||||
Msg('%s_edit_descr' % self.userName, '', ' ')]
|
||||
repls = self.repls.copy()
|
||||
repls['fields'] = self.user.schema
|
||||
repls['methods'] = self.user.methods
|
||||
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
|
||||
self.copyFile('UserTemplate.py', repls,
|
||||
destName='%s.py' % self.userName)
|
||||
|
||||
def generateClass(self, classDescr):
|
||||
'''Is called each time an Appy class is found in the application, for
|
||||
|
@ -681,7 +710,7 @@ class Generator(AbstractGenerator):
|
|||
implements = [baseClass]
|
||||
for baseClass in classDescr.klass.__bases__:
|
||||
if self.determineAppyType(baseClass) == 'class':
|
||||
bcName = ClassDescriptor.getClassName(baseClass)
|
||||
bcName = getClassName(baseClass)
|
||||
parents.remove('ClassMixin')
|
||||
parents.append(bcName)
|
||||
implements.append(bcName)
|
||||
|
|
|
@ -18,30 +18,27 @@ class ZCTextIndexInfo:
|
|||
class PloneInstaller:
|
||||
'''This Plone installer runs every time the generated Plone product is
|
||||
installed or uninstalled (in the Plone configuration interface).'''
|
||||
def __init__(self, reinstall, productName, ploneSite, minimalistPlone,
|
||||
appClasses, appClassNames, allClassNames, catalogMap, applicationRoles,
|
||||
defaultAddRoles, workflows, appFrontPage, showPortlet, ploneStuff):
|
||||
def __init__(self, reinstall, ploneSite, config):
|
||||
# p_cfg is the configuration module of the Plone product.
|
||||
self.reinstall = reinstall # Is it a fresh install or a re-install?
|
||||
self.productName = productName
|
||||
self.ploneSite = ploneSite
|
||||
self.minimalistPlone = minimalistPlone # If True, lots of basic Plone
|
||||
# stuff will be hidden.
|
||||
self.appClasses = appClasses # The list of classes declared in the
|
||||
# gen-application.
|
||||
self.appClassNames = appClassNames # Names of those classes
|
||||
self.allClassNames = allClassNames # Includes Flavour and PodTemplate
|
||||
self.catalogMap = catalogMap # Indicates classes to be indexed or not
|
||||
self.applicationRoles = applicationRoles # Roles defined in the app
|
||||
self.defaultAddRoles = defaultAddRoles # The default roles that can add
|
||||
# content
|
||||
self.workflows = workflows # Dict whose keys are class names and whose
|
||||
# values are workflow names (=the workflow
|
||||
# used by the content type)
|
||||
self.appFrontPage = appFrontPage # Does this app define a site-wide
|
||||
# front page?
|
||||
self.showPortlet = showPortlet # Must we show the application portlet?
|
||||
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
|
||||
self.attributes = ploneStuff['GLOBALS']['attributes']
|
||||
self.config = cfg = config
|
||||
# Unwrap some useful variables from config
|
||||
self.productName = cfg.PROJECTNAME
|
||||
self.minimalistPlone = cfg.minimalistPlone
|
||||
self.appClasses = cfg.appClasses
|
||||
self.appClassNames = cfg.appClassNames
|
||||
self.allClassNames = cfg.allClassNames
|
||||
self.catalogMap = cfg.catalogMap
|
||||
self.applicationRoles = cfg.applicationRoles # Roles defined in the app
|
||||
self.defaultAddRoles = cfg.defaultAddRoles
|
||||
self.workflows = cfg.workflows
|
||||
self.appFrontPage = cfg.appFrontPage
|
||||
self.showPortlet = cfg.showPortlet
|
||||
self.languages = cfg.languages
|
||||
self.languageSelector = cfg.languageSelector
|
||||
self.attributes = cfg.attributes
|
||||
# A buffer for logging purposes
|
||||
self.toLog = StringIO()
|
||||
self.typeAliases = {'sharing': '', 'gethtml': '',
|
||||
'(Default)': 'skynView', 'edit': 'skyn/edit',
|
||||
|
@ -166,7 +163,7 @@ class PloneInstaller:
|
|||
site.manage_delObjects(['skyn'])
|
||||
# This way, if Appy has moved from one place to the other, the
|
||||
# directory view will always refer to the correct place.
|
||||
addDirView = self.ploneStuff['manage_addDirectoryView']
|
||||
addDirView = self.config.manage_addDirectoryView
|
||||
addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn')
|
||||
|
||||
def installTypes(self):
|
||||
|
@ -174,11 +171,9 @@ class PloneInstaller:
|
|||
gen-classes.'''
|
||||
site = self.ploneSite
|
||||
# Do Plone-based type registration
|
||||
classes = self.ploneStuff['listTypes'](self.productName)
|
||||
self.ploneStuff['installTypes'](site, self.toLog, classes,
|
||||
self.productName)
|
||||
self.ploneStuff['install_subskin'](site, self.toLog,
|
||||
self.ploneStuff['GLOBALS'])
|
||||
classes = self.config.listTypes(self.productName)
|
||||
self.config.installTypes(site, self.toLog, classes, self.productName)
|
||||
self.config.install_subskin(site, self.toLog, self.config.__dict__)
|
||||
# Set appy view/edit pages for every created type
|
||||
for className in self.allClassNames + ['%sTool' % self.productName]:
|
||||
# I did not put the app tool in self.allClassNames because it
|
||||
|
@ -204,7 +199,7 @@ class PloneInstaller:
|
|||
factoryTool.manage_setPortalFactoryTypes(listOfTypeIds=factoryTypes)
|
||||
|
||||
# Configure CatalogMultiplex: tell what types will be catalogued or not.
|
||||
atTool = getattr(site, self.ploneStuff['ARCHETYPETOOLNAME'])
|
||||
atTool = getattr(site, self.config.ARCHETYPETOOLNAME)
|
||||
for meta_type in self.catalogMap:
|
||||
submap = self.catalogMap[meta_type]
|
||||
current_catalogs = Set(
|
||||
|
@ -294,7 +289,7 @@ class PloneInstaller:
|
|||
try:
|
||||
self.ploneSite.manage_addProduct[
|
||||
self.productName].manage_addTool(self.toolName)
|
||||
except self.ploneStuff['BadRequest']:
|
||||
except self.config.BadRequest:
|
||||
# If an instance with the same name already exists, this error will
|
||||
# be unelegantly raised by Zope.
|
||||
pass
|
||||
|
@ -357,7 +352,7 @@ class PloneInstaller:
|
|||
groups if needed.'''
|
||||
site = self.ploneSite
|
||||
data = list(site.__ac_roles__)
|
||||
for role in self.applicationRoles:
|
||||
for role in self.config.applicationRoles:
|
||||
if not role in data:
|
||||
data.append(role)
|
||||
# Add to portal_role_manager
|
||||
|
@ -373,11 +368,13 @@ class PloneInstaller:
|
|||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
# Create a specific group and grant him this role
|
||||
# If it is a global role, create a specific group and grant him
|
||||
# this role
|
||||
if role not in self.config.applicationGlobalRoles: continue
|
||||
group = '%s_group' % role
|
||||
if not site.portal_groups.getGroupById(group):
|
||||
site.portal_groups.addGroup(group, title=group)
|
||||
site.portal_groups.setRolesForGroup(group, [role])
|
||||
if site.portal_groups.getGroupById(group): continue # Already there
|
||||
site.portal_groups.addGroup(group, title=group)
|
||||
site.portal_groups.setRolesForGroup(group, [role])
|
||||
site.__ac_roles__ = tuple(data)
|
||||
|
||||
def installWorkflows(self):
|
||||
|
@ -386,7 +383,7 @@ class PloneInstaller:
|
|||
for contentType, workflowName in self.workflows.iteritems():
|
||||
# Register the workflow if needed
|
||||
if workflowName not in wfTool.listWorkflows():
|
||||
wfMethod = self.ploneStuff['ExternalMethod']('temp', 'temp',
|
||||
wfMethod = self.config.ExternalMethod('temp', 'temp',
|
||||
self.productName + '.workflows', 'create_%s' % workflowName)
|
||||
workflow = wfMethod(self, workflowName)
|
||||
wfTool._setObject(workflowName, workflow)
|
||||
|
@ -401,18 +398,14 @@ class PloneInstaller:
|
|||
cssName = self.productName + '.css'
|
||||
cssTitle = self.productName + ' CSS styles'
|
||||
cssInfo = {'id': cssName, 'title': cssTitle}
|
||||
portalCss = self.ploneSite.portal_css
|
||||
try:
|
||||
portalCss = self.ploneSite.portal_css
|
||||
try:
|
||||
portalCss.unregisterResource(cssInfo['id'])
|
||||
except:
|
||||
pass
|
||||
defaults = {'id': '', 'media': 'all', 'enabled': True}
|
||||
defaults.update(cssInfo)
|
||||
portalCss.registerStylesheet(**defaults)
|
||||
portalCss.unregisterResource(cssInfo['id'])
|
||||
except:
|
||||
# No portal_css registry
|
||||
pass
|
||||
defaults = {'id': '', 'media': 'all', 'enabled': True}
|
||||
defaults.update(cssInfo)
|
||||
portalCss.registerStylesheet(**defaults)
|
||||
|
||||
def managePortlets(self):
|
||||
'''Shows or hides the application-specific portlet and configures other
|
||||
|
@ -456,6 +449,22 @@ class PloneInstaller:
|
|||
if indexInfo:
|
||||
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
|
||||
|
||||
def manageLanguages(self):
|
||||
'''Manages the languages supported by the application.'''
|
||||
if self.languageSelector:
|
||||
# We must install the PloneLanguageTool if not done yet
|
||||
qi = self.ploneSite.portal_quickinstaller
|
||||
if not qi.isProductInstalled('PloneLanguageTool'):
|
||||
qi.installProduct('PloneLanguageTool')
|
||||
languageTool = self.ploneSite.portal_languages
|
||||
defLanguage = self.languages[0]
|
||||
languageTool.manage_setLanguageSettings(defaultLanguage=defLanguage,
|
||||
supportedLanguages=self.languages, setContentN=None,
|
||||
setCookieN=True, setRequestN=True, setPathN=True,
|
||||
setForcelanguageUrls=True, setAllowContentLanguageFallback=None,
|
||||
setUseCombinedLanguageCodes=None, displayFlags=False,
|
||||
startNeutral=False)
|
||||
|
||||
def finalizeInstallation(self):
|
||||
'''Performs some final installation steps.'''
|
||||
site = self.ploneSite
|
||||
|
@ -493,6 +502,7 @@ class PloneInstaller:
|
|||
self.installStyleSheet()
|
||||
self.managePortlets()
|
||||
self.manageIndexes()
|
||||
self.manageLanguages()
|
||||
self.finalizeInstallation()
|
||||
self.log("Installation of %s done." % self.productName)
|
||||
|
||||
|
@ -545,17 +555,16 @@ def traverseWrapper(self, path, response=None, validated_hook=None):
|
|||
class ZopeInstaller:
|
||||
'''This Zope installer runs every time Zope starts and encounters this
|
||||
generated Zope product.'''
|
||||
def __init__(self, zopeContext, productName, toolClass,
|
||||
defaultAddContentPermission, addContentPermissions,
|
||||
logger, ploneStuff, classes):
|
||||
def __init__(self, zopeContext, toolClass, config, classes):
|
||||
self.zopeContext = zopeContext
|
||||
self.productName = productName
|
||||
self.toolClass = toolClass
|
||||
self.defaultAddContentPermission = defaultAddContentPermission
|
||||
self.addContentPermissions = addContentPermissions
|
||||
self.logger = logger
|
||||
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
|
||||
self.config = cfg = config
|
||||
self.classes = classes
|
||||
# Unwrap some useful config variables
|
||||
self.productName = cfg.PROJECTNAME
|
||||
self.logger = cfg.logger
|
||||
self.defaultAddContentPermission = cfg.DEFAULT_ADD_CONTENT_PERMISSION
|
||||
self.addContentPermissions = cfg.ADD_CONTENT_PERMISSIONS
|
||||
|
||||
def completeAppyTypes(self):
|
||||
'''We complete here the initialisation process of every Appy type of
|
||||
|
@ -574,23 +583,23 @@ class ZopeInstaller:
|
|||
|
||||
def installApplication(self):
|
||||
'''Performs some application-wide installation steps.'''
|
||||
register = self.ploneStuff['DirectoryView'].registerDirectory
|
||||
register('skins', self.ploneStuff['product_globals'])
|
||||
register = self.config.DirectoryView.registerDirectory
|
||||
register('skins', self.config.__dict__)
|
||||
# Register the appy skin folder among DirectoryView'able folders
|
||||
register('skin', appy.getPath() + '/gen/plone25')
|
||||
|
||||
def installTool(self):
|
||||
'''Installs the tool.'''
|
||||
self.ploneStuff['ToolInit'](self.productName + ' Tools',
|
||||
self.config.ToolInit(self.productName + ' Tools',
|
||||
tools = [self.toolClass], icon='tool.gif').initialize(
|
||||
self.zopeContext)
|
||||
|
||||
def installTypes(self):
|
||||
'''Installs and configures the types defined in the application.'''
|
||||
contentTypes, constructors, ftis = self.ploneStuff['process_types'](
|
||||
self.ploneStuff['listTypes'](self.productName), self.productName)
|
||||
contentTypes, constructors, ftis = self.config.process_types(
|
||||
self.config.listTypes(self.productName), self.productName)
|
||||
|
||||
self.ploneStuff['cmfutils'].ContentInit(self.productName + ' Content',
|
||||
self.config.cmfutils.ContentInit(self.productName + ' Content',
|
||||
content_types = contentTypes,
|
||||
permission = self.defaultAddContentPermission,
|
||||
extra_constructors = constructors, fti = ftis).initialize(
|
||||
|
@ -611,14 +620,14 @@ class ZopeInstaller:
|
|||
global originalTraverse
|
||||
if not originalTraverse:
|
||||
# User tracking is not enabled yet. Do it now.
|
||||
BaseRequest = self.ploneStuff['BaseRequest']
|
||||
BaseRequest = self.config.BaseRequest
|
||||
originalTraverse = BaseRequest.traverse
|
||||
BaseRequest.traverse = traverseWrapper
|
||||
|
||||
def finalizeInstallation(self):
|
||||
'''Performs some final installation steps.'''
|
||||
# Apply customization policy if any
|
||||
cp = self.ploneStuff['CustomizationPolicy']
|
||||
cp = self.config.CustomizationPolicy
|
||||
if cp and hasattr(cp, 'register'): cp.register(context)
|
||||
|
||||
def install(self):
|
||||
|
|
|
@ -18,33 +18,10 @@ POD_ERROR = 'An error occurred while generating the document. Please ' \
|
|||
# ------------------------------------------------------------------------------
|
||||
class FlavourMixin(AbstractMixin):
|
||||
_appy_meta_type = 'Flavour'
|
||||
def getPortalType(self, metaTypeOrAppyType):
|
||||
def getPortalType(self, metaTypeOrAppyClass):
|
||||
'''Returns the name of the portal_type that is based on
|
||||
p_metaTypeOrAppyType in this flavour.'''
|
||||
res = metaTypeOrAppyType
|
||||
isPredefined = False
|
||||
isAppy = False
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if not isinstance(res, basestring):
|
||||
res = ClassDescriptor.getClassName(res)
|
||||
isAppy = True
|
||||
if res.find('Extensions_appyWrappers') != -1:
|
||||
isPredefined = True
|
||||
elems = res.split('_')
|
||||
res = '%s%s' % (elems[1], elems[4])
|
||||
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Tool):
|
||||
# This is the custom tool
|
||||
isPredefined = True
|
||||
res = '%sTool' % appName
|
||||
elif isAppy and issubclass(metaTypeOrAppyType, appy.gen.Flavour):
|
||||
# This is the custom Flavour
|
||||
isPredefined = True
|
||||
res = '%sFlavour' % appName
|
||||
if not isPredefined:
|
||||
number = self.appy().number
|
||||
if number != 1:
|
||||
res = '%s_%d' % (res, number)
|
||||
return res
|
||||
return self.getParentNode().getPortalType(metaTypeOrAppyClass)
|
||||
|
||||
def registerPortalTypes(self):
|
||||
'''Registers, into portal_types, the portal types which are specific
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import re, os, os.path, Cookie
|
||||
from appy.shared.utils import getOsTempFolder
|
||||
import appy.gen
|
||||
from appy.gen import Type, Search, Selection
|
||||
from appy.gen.utils import SomeObjects, sequenceTypes
|
||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
|
@ -13,6 +14,17 @@ jsMessages = ('no_elem_selected', 'delete_confirm')
|
|||
# ------------------------------------------------------------------------------
|
||||
class ToolMixin(AbstractMixin):
|
||||
_appy_meta_type = 'Tool'
|
||||
def getPortalType(self, metaTypeOrAppyClass):
|
||||
'''Returns the name of the portal_type that is based on
|
||||
p_metaTypeOrAppyType in this flavour.'''
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if not isinstance(metaTypeOrAppyClass, basestring):
|
||||
res = getClassName(metaTypeOrAppyClass, appName)
|
||||
if res.find('Extensions_appyWrappers') != -1:
|
||||
elems = res.split('_')
|
||||
res = '%s%s' % (elems[1], elems[4])
|
||||
return res
|
||||
|
||||
def getFlavour(self, contextObjOrPortalType, appy=False):
|
||||
'''Gets the flavour that corresponds to p_contextObjOrPortalType.'''
|
||||
if isinstance(contextObjOrPortalType, basestring):
|
||||
|
|
7
gen/plone25/mixins/UserMixin.py
Normal file
7
gen/plone25/mixins/UserMixin.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.mixins import AbstractMixin
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class UserMixin(AbstractMixin):
|
||||
_appy_meta_type = 'UserMixin'
|
||||
# ------------------------------------------------------------------------------
|
|
@ -8,7 +8,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, types, mimetypes
|
||||
import appy.gen
|
||||
from appy.gen import Type, String, Selection
|
||||
from appy.gen import Type, String, Selection, Role
|
||||
from appy.gen.utils import *
|
||||
from appy.gen.layout import Table, defaultPageLayouts
|
||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||
|
@ -338,14 +338,30 @@ class AbstractMixin:
|
|||
i = res.startNumber
|
||||
# Is it possible and more efficient to perform a single query in
|
||||
# uid_catalog and get the result in the order of specified uids?
|
||||
toUnlink = []
|
||||
while i < (res.startNumber + res.batchSize):
|
||||
if i >= res.totalNumber: break
|
||||
refUid = sortedUids[i]
|
||||
refObject = self.uid_catalog(UID=refUid)[0].getObject()
|
||||
i += 1
|
||||
tool = self.getTool()
|
||||
if refObject.meta_type != tool.getPortalType(appyType.klass):
|
||||
toUnlink.append(refObject)
|
||||
continue
|
||||
if not ploneObjects:
|
||||
refObject = refObject.appy()
|
||||
res.objects.append(refObject)
|
||||
i += 1
|
||||
# Unlink dummy linked objects
|
||||
if toUnlink:
|
||||
suffix = '%s%s' % (fieldName[0].upper(), fieldName[1:])
|
||||
exec 'linkedObjects = self.get%s()' % suffix
|
||||
for dummyObject in toUnlink:
|
||||
linkedObjects.remove(dummyObject)
|
||||
self.getProductConfig().logger.warn('DB error: Ref %s.%s ' \
|
||||
'contains a %s instance "%s". It was removed.' % \
|
||||
(self.meta_type, fieldName, dummyObject.meta_type,
|
||||
dummyObject.getId()))
|
||||
exec 'self.set%s(linkedObjects)' % suffix
|
||||
if res.objects and noListIfSingleObj:
|
||||
if appyType.multiplicity[1] == 1:
|
||||
res.objects = res.objects[0]
|
||||
|
@ -413,18 +429,6 @@ class AbstractMixin:
|
|||
res = sortedObjectsUids.index(obj.UID())
|
||||
return res
|
||||
|
||||
def getAppyRefPortalType(self, fieldName):
|
||||
'''Gets the portal type of objects linked to me through Ref field named
|
||||
p_fieldName.'''
|
||||
appyType = self.getAppyType(fieldName)
|
||||
tool = self.getTool()
|
||||
if self._appy_meta_type == 'Flavour':
|
||||
flavour = self.appy()
|
||||
else:
|
||||
portalTypeName = self._appy_getPortalType(self.REQUEST)
|
||||
flavour = tool.getFlavour(portalTypeName)
|
||||
return self._appy_getAtType(appyType.klass, flavour)
|
||||
|
||||
def getAppyType(self, name, asDict=False, className=None):
|
||||
'''Returns the Appy type named p_name. If no p_className is defined, the
|
||||
field is supposed to belong to self's class.'''
|
||||
|
@ -696,10 +700,10 @@ class AbstractMixin:
|
|||
# Get the corresponding Appy transition
|
||||
transition = workflow._transitionsMapping[transitionName]
|
||||
user = self.portal_membership.getAuthenticatedMember()
|
||||
if isinstance(transition.condition, basestring):
|
||||
if isinstance(transition.condition, Role):
|
||||
# It is a role. Transition may be triggered if the user has this
|
||||
# role.
|
||||
res = user.has_role(transition.condition, self)
|
||||
res = user.has_role(transition.condition.name, self)
|
||||
elif type(transition.condition) == types.FunctionType:
|
||||
res = transition.condition(workflow, self.appy())
|
||||
elif type(transition.condition) in (tuple, list):
|
||||
|
@ -843,28 +847,6 @@ class AbstractMixin:
|
|||
rq.appyWrappers[uid] = wrapper
|
||||
return wrapper
|
||||
|
||||
def _appy_getAtType(self, appyClass, flavour=None):
|
||||
'''Gets the name of the Archetypes class that corresponds to
|
||||
p_appyClass (which is a Python class coming from the user
|
||||
application). If p_flavour is specified, the method returns the name
|
||||
of the specific Archetypes class in this flavour (ie suffixed with
|
||||
the flavour number).'''
|
||||
res = ClassDescriptor.getClassName(appyClass)
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
if res.find('Extensions_appyWrappers') != -1:
|
||||
# This is not a content type defined Maybe I am a tool or flavour
|
||||
res = appName + appyClass.__name__
|
||||
elif issubclass(appyClass, appy.gen.Tool):
|
||||
# This is the custom tool
|
||||
res = '%sTool' % appName
|
||||
elif issubclass(appyClass, appy.gen.Flavour):
|
||||
# This is the custom Flavour
|
||||
res = '%sFlavour' % appName
|
||||
else:
|
||||
if flavour and flavour.number != 1:
|
||||
res += '_%d' % flavour.number
|
||||
return res
|
||||
|
||||
def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False,
|
||||
noListIfSingleObj=False):
|
||||
'''This method returns the list of objects linked to this one
|
||||
|
@ -927,19 +909,14 @@ class AbstractMixin:
|
|||
if appyType.type != 'Ref': continue
|
||||
if appyType.isBack or appyType.link: continue
|
||||
# Indeed, no possibility to create objects with such Ref
|
||||
refContentTypeName = self.getAppyRefPortalType(appyType.name)
|
||||
refContentType = getattr(self.portal_types, refContentTypeName)
|
||||
refMetaType = refContentType.content_meta_type
|
||||
if refMetaType not in addPermissions: continue
|
||||
# Indeed, there is no specific "add" permission is defined for tool
|
||||
# and flavour, for example.
|
||||
appyClass = refContentType.wrapperClass.__bases__[-1]
|
||||
refType = self.getTool().getPortalType(appyType.klass)
|
||||
if refType not in addPermissions: continue
|
||||
# Get roles that may add this content type
|
||||
creators = getattr(appyClass, 'creators', None)
|
||||
creators = getattr(appyType.klass, 'creators', None)
|
||||
if not creators:
|
||||
creators = self.getProductConfig().defaultAddRoles
|
||||
# Add those creators to the list of creators for this meta_type
|
||||
addPermission = addPermissions[refMetaType]
|
||||
addPermission = addPermissions[refType]
|
||||
if addPermission in allCreators:
|
||||
allCreators[addPermission] = allCreators[\
|
||||
addPermission].union(creators)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
import copy, types
|
||||
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection, Group
|
||||
from appy.gen import *
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ModelClass:
|
||||
|
@ -70,6 +70,25 @@ class ModelClass:
|
|||
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
||||
return res
|
||||
|
||||
class User(ModelClass):
|
||||
# All methods defined below are fake. Real versions are in the wrapper.
|
||||
title = String(show=False)
|
||||
gm = {'group': 'main', 'multiplicity': (1,1)}
|
||||
name = String(**gm)
|
||||
firstName = String(**gm)
|
||||
def showLogin(self): pass
|
||||
def validateLogin(self): pass
|
||||
login = String(show=showLogin, validator=validateLogin, **gm)
|
||||
def showPassword(self): pass
|
||||
def validatePassword(self): pass
|
||||
password1 = String(format=String.PASSWORD, show=showPassword,
|
||||
validator=validatePassword, **gm)
|
||||
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
|
||||
gm['multiplicity'] = (0, None)
|
||||
roles = String(validator=Selection('getGrantableRoles'), **gm)
|
||||
_appy_attributes = ['title', 'name', 'firstName', 'login',
|
||||
'password1', 'password2', 'roles']
|
||||
|
||||
class PodTemplate(ModelClass):
|
||||
description = String(format=String.TEXT)
|
||||
podTemplate = File(multiplicity=(1,1))
|
||||
|
@ -290,12 +309,18 @@ class Tool(ModelClass):
|
|||
# First arg is None because we don't know yet if it will link
|
||||
# to the predefined Flavour class or a custom class defined
|
||||
# in the application.
|
||||
def validPythonWithUno(self, value): pass
|
||||
users = Ref(None, multiplicity=(0,None), add=True, link=False,
|
||||
back=Ref(attribute='toTool'), page='users',
|
||||
shownInfo=('login', 'title', 'roles'), showHeaders=True)
|
||||
# First arg is None because we don't know yet if it will link to the
|
||||
# predefined User class or a custom class defined in the application.
|
||||
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
||||
unoEnabledPython = String(group="connectionToOpenOffice",
|
||||
validator=validPythonWithUno)
|
||||
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
||||
numberOfResultsPerPage = Integer(default=30)
|
||||
listBoxesMaximumWidth = Integer(default=100)
|
||||
_appy_attributes = ['flavours', 'unoEnabledPython', 'openOfficePort',
|
||||
'numberOfResultsPerPage', 'listBoxesMaximumWidth']
|
||||
_appy_attributes = ['flavours', 'users', 'unoEnabledPython',
|
||||
'openOfficePort', 'numberOfResultsPerPage',
|
||||
'listBoxesMaximumWidth']
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
tal:content="structure python: tool.translate('%s_page_%s' % (contextObj.meta_type, aPage))">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<td align="right">
|
||||
<img tal:define="nav request/nav|nothing;
|
||||
nav python: test(nav, '&nav=%s' % nav, '')"
|
||||
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
objs refObjects/objects;
|
||||
totalNumber refObjects/totalNumber;
|
||||
batchSize refObjects/batchSize;
|
||||
folder python: test(contextObj.isPrincipiaFolderish, contextObj, contextObj.getParentNode());
|
||||
folder python: contextObj.isPrincipiaFolderish and contextObj or contextObj.getParentNode();
|
||||
flavour python:tool.getFlavour(contextObj);
|
||||
linkedPortalType python:flavour.getPortalType(appyType['klass']);
|
||||
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
|
||||
|
@ -205,7 +205,7 @@
|
|||
<tal:comment replace="nothing">Object title, shown here if not specified somewhere
|
||||
else in appyType.shownInfo.</tal:comment>
|
||||
<td tal:condition="python: 'title' not in appyType['shownInfo']"><metal:showObjectTitle
|
||||
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
|
||||
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Additional fields that must be shown</tal:comment>
|
||||
<td tal:repeat="widget widgets">
|
||||
|
@ -217,7 +217,7 @@
|
|||
innerRef python:True"
|
||||
condition="python: widget['name'] != 'title'">
|
||||
<metal:showField use-macro="portal/skyn/widgets/show/macros/field" />
|
||||
</tal:showOtherField>
|
||||
</tal:showOtherField>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Actions</tal:comment>
|
||||
<td align="right">
|
||||
|
@ -248,7 +248,7 @@
|
|||
requestValue python: request.get(rname, []);
|
||||
inRequest python: request.has_key(rname);
|
||||
allObjects python: contextObj.getSelectableAppyRefs(name);
|
||||
refUids python: [o.UID() for o in here.getAppyRefs(name)['objects']];
|
||||
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
||||
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
||||
|
||||
<select tal:attributes="name rname;
|
||||
|
|
|
@ -26,14 +26,13 @@
|
|||
isOneLine python: fmt in (0,3)">
|
||||
|
||||
<tal:choice condition="isSelect">
|
||||
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True);
|
||||
multiValued python: (widget['multiplicity'][1] != 1) and True or False"
|
||||
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True)"
|
||||
tal:attributes="name name;
|
||||
id name;
|
||||
multiple python: multiValued and 'multiple' or '';
|
||||
multiple python: isMultiple and 'multiple' or '';
|
||||
onchange python: isMaster and ('javascript:updateSlaves(getMasterValue(this), \'%s\')' % widget['id']) or '';
|
||||
class widget/master_css;
|
||||
size python: multiValued and widget['height'] or 1">
|
||||
size python: isMultiple and widget['height'] or 1">
|
||||
<option tal:repeat="possibleValue possibleValues"
|
||||
tal:attributes="value python: possibleValue[0];
|
||||
selected python:contextObj.fieldValueSelected(name, possibleValue[0], rawValue)"
|
||||
|
@ -42,7 +41,8 @@
|
|||
</tal:choice>
|
||||
<tal:line condition="python: isOneLine and not isSelect">
|
||||
<input tal:attributes="id name; name name; size widget/width;
|
||||
value python: test(inRequest, requestValue, value)" type="text"/>
|
||||
value python: test(inRequest, requestValue, value);
|
||||
type python: (widget['format'] == 3) and 'password' or 'text'"/>
|
||||
</tal:line>
|
||||
<tal:textarea condition="python: fmt == 1">
|
||||
<textarea tal:attributes="id name; name name;
|
||||
|
@ -66,8 +66,14 @@
|
|||
</metal:edit>
|
||||
|
||||
<tal:comment replace="nothing">Cell macro for a String.</tal:comment>
|
||||
<metal:cell define-macro="cell">
|
||||
<metal:call use-macro="portal/skyn/widgets/string/macros/view"/>
|
||||
<metal:cell define-macro="cell"
|
||||
tal:define="multipleValues python: value and isMultiple">
|
||||
<tal:multiple condition="multipleValues"
|
||||
content="python: ', '.join(value)">
|
||||
</tal:multiple>
|
||||
<tal:notMultiple condition="not: multipleValues">
|
||||
<metal:call use-macro="portal/skyn/widgets/string/macros/view"/>
|
||||
</tal:notMultiple>
|
||||
</metal:cell>
|
||||
|
||||
<tal:comment replace="nothing">Search macro for a String.</tal:comment>
|
||||
|
|
|
@ -20,7 +20,6 @@ class <!flavourName!>(OrderedBaseFolder, FlavourMixin):
|
|||
allowed_content_types = []
|
||||
filter_content_types = 0
|
||||
global_allow = 1
|
||||
#content_icon = '<!flavourName!>.gif'
|
||||
immediate_view = 'skyn/view'
|
||||
default_view = 'skyn/view'
|
||||
suppl_views = ()
|
||||
|
|
|
@ -1,38 +1,15 @@
|
|||
<!codeHeader!>
|
||||
from zExceptions import BadRequest
|
||||
from Products.CMFCore.DirectoryView import manage_addDirectoryView
|
||||
from Products.ExternalMethod.ExternalMethod import ExternalMethod
|
||||
from Products.Archetypes.Extensions.utils import installTypes
|
||||
from Products.Archetypes.Extensions.utils import install_subskin
|
||||
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
|
||||
from Products.Archetypes.atapi import listTypes
|
||||
from Products.<!applicationName!>.config import applicationRoles,defaultAddRoles
|
||||
from Products.<!applicationName!>.config import product_globals as GLOBALS
|
||||
import appy.gen
|
||||
from appy.gen.plone25.installer import PloneInstaller
|
||||
<!imports!>
|
||||
catalogMap = {}
|
||||
<!catalogMap!>
|
||||
appClasses = <!appClasses!>
|
||||
appClassNames = [<!appClassNames!>]
|
||||
allClassNames = [<!allClassNames!>]
|
||||
workflows = {<!workflows!>}
|
||||
showPortlet = <!showPortlet!>
|
||||
import Products.<!applicationName!>.config as config
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def install(self, reinstall=False):
|
||||
'''Installation of product "<!applicationName!>"'''
|
||||
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self,
|
||||
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
|
||||
catalogMap, applicationRoles, defaultAddRoles, workflows,
|
||||
<!appFrontPage!>, showPortlet, globals())
|
||||
return ploneInstaller.install()
|
||||
'''Installation procedure.'''
|
||||
return PloneInstaller(reinstall, self, config).install()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def uninstall(self, reinstall=False):
|
||||
'''Uninstallation of product "<!applicationName!>"'''
|
||||
ploneInstaller = PloneInstaller(reinstall, "<!applicationName!>", self,
|
||||
<!minimalistPlone!>, appClasses, appClassNames, allClassNames,
|
||||
catalogMap, applicationRoles, defaultAddRoles, workflows,
|
||||
<!appFrontPage!>, showPortlet, globals())
|
||||
return ploneInstaller.uninstall()
|
||||
'''Uninstallation procedure.'''
|
||||
return PloneInstaller(reinstall, self, config).uninstall()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -14,19 +14,17 @@ class <!applicationName!>PodTemplate(BaseContent, PodTemplateMixin):
|
|||
'''POD template.'''
|
||||
security = ClassSecurityInfo()
|
||||
__implements__ = (getattr(BaseContent,'__implements__',()),)
|
||||
|
||||
archetype_name = '<!applicationName!>PodTemplate'
|
||||
meta_type = '<!applicationName!>PodTemplate'
|
||||
portal_type = '<!applicationName!>PodTemplate'
|
||||
allowed_content_types = []
|
||||
filter_content_types = 0
|
||||
global_allow = 1
|
||||
#content_icon = '<!applicationName!>PodTemplate.gif'
|
||||
immediate_view = 'skyn/view'
|
||||
default_view = 'skyn/view'
|
||||
suppl_views = ()
|
||||
typeDescription = "<!applicationName!>PodTemplate"
|
||||
typeDescMsgId = '<!applicationName!>_edit_descr'
|
||||
typeDescMsgId = '<!applicationName!>PodTemplate_edit_descr'
|
||||
wrapperClass = <!wrapperClass!>
|
||||
schema = fullSchema
|
||||
for elem in dir(PodTemplateMixin):
|
||||
|
|
|
@ -170,21 +170,13 @@ th {
|
|||
/* overflow: visible; IE produces ugly results with this */
|
||||
}
|
||||
|
||||
.listing {
|
||||
margin: 0em 0em;
|
||||
}
|
||||
|
||||
.listing { margin: 0em 0em; }
|
||||
.listing td, .stx table td {
|
||||
padding-right: 0.1em;
|
||||
padding-left: 0.3em;
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0em;
|
||||
padding : 0.1em 0.3em 0.1em 0.3em;
|
||||
border-top : 1px solid #8CACBB;
|
||||
}
|
||||
|
||||
.vertical td {
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
.listing th, .stx table th { padding: 0.25em 0.3em; }
|
||||
.vertical td { padding-left: 0.3em; }
|
||||
|
||||
.innerAppyTable {
|
||||
border-width: 0px;
|
||||
|
|
34
gen/plone25/templates/UserTemplate.py
Normal file
34
gen/plone25/templates/UserTemplate.py
Normal 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!>')
|
|
@ -25,20 +25,8 @@ def countTest():
|
|||
numberOfExecutedTests += 1
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
from config import *
|
||||
from ZPublisher.HTTPRequest import BaseRequest
|
||||
import logging
|
||||
try:
|
||||
import CustomizationPolicy
|
||||
except ImportError:
|
||||
CustomizationPolicy = None
|
||||
from Products.CMFCore import utils as cmfutils
|
||||
from Products.CMFCore import DirectoryView
|
||||
from Products.CMFPlone.utils import ToolInit
|
||||
from Products.Archetypes.atapi import *
|
||||
from Products.Archetypes import listTypes
|
||||
import config
|
||||
from appy.gen.plone25.installer import ZopeInstaller
|
||||
logger = logging.getLogger(PROJECTNAME)
|
||||
|
||||
# Zope-level installation of the generated product. ----------------------------
|
||||
def initialize(context):
|
||||
|
@ -46,8 +34,6 @@ def initialize(context):
|
|||
# I need to do those imports here; else, types and add permissions will not
|
||||
# be registered.
|
||||
classes = [<!classes!>]
|
||||
ZopeInstaller(context, PROJECTNAME,
|
||||
<!applicationName!>Tool.<!applicationName!>Tool,
|
||||
DEFAULT_ADD_CONTENT_PERMISSION, ADD_CONTENT_PERMISSIONS,
|
||||
logger, globals(), classes).install()
|
||||
ZopeInstaller(context, <!applicationName!>Tool.<!applicationName!>Tool,
|
||||
config, classes).install()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -4,6 +4,7 @@ from appy.gen.plone25.wrappers import AbstractWrapper
|
|||
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
||||
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
|
||||
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
|
||||
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
|
||||
from Globals import InitializeClass
|
||||
from AccessControl import ClassSecurityInfo
|
||||
<!imports!>
|
||||
|
@ -11,6 +12,9 @@ from AccessControl import ClassSecurityInfo
|
|||
class PodTemplate(PodTemplateWrapper):
|
||||
'''This class represents a POD template for this application.'''
|
||||
<!podTemplateBody!>
|
||||
class User(UserWrapper):
|
||||
'''This class represents a user.'''
|
||||
<!userBody!>
|
||||
class Flavour(FlavourWrapper):
|
||||
'''This class represents the Appy class used for defining a flavour.'''
|
||||
folder=True
|
||||
|
|
|
@ -10,10 +10,26 @@ import Extensions.appyWrappers as wraps
|
|||
# every Archetype instance has a method "getProductConfig" that returns this
|
||||
# module.
|
||||
from persistent.list import PersistentList
|
||||
from zExceptions import BadRequest
|
||||
from ZPublisher.HTTPRequest import BaseRequest
|
||||
try:
|
||||
import CustomizationPolicy
|
||||
except ImportError:
|
||||
CustomizationPolicy = None
|
||||
from OFS.Image import File
|
||||
from DateTime import DateTime
|
||||
from Products.CMFCore import utils as cmfutils
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from Products.CMFPlone.PloneBatch import Batch
|
||||
from Products.CMFPlone.utils import ToolInit
|
||||
from Products.CMFCore import DirectoryView
|
||||
from Products.CMFCore.DirectoryView import manage_addDirectoryView
|
||||
from Products.ExternalMethod.ExternalMethod import ExternalMethod
|
||||
from Products.Archetypes.Extensions.utils import installTypes
|
||||
from Products.Archetypes.Extensions.utils import install_subskin
|
||||
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
|
||||
from Products.Archetypes import listTypes, process_types
|
||||
import appy.gen
|
||||
import logging
|
||||
logger = logging.getLogger('<!applicationName!>')
|
||||
|
||||
|
@ -25,10 +41,19 @@ DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
|
|||
ADD_CONTENT_PERMISSIONS = {
|
||||
<!addPermissions!>}
|
||||
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
|
||||
product_globals = globals()
|
||||
applicationRoles = [<!roles!>]
|
||||
rootClasses = [<!rootClasses!>]
|
||||
|
||||
# Applications classes, in various formats and flavours
|
||||
rootClasses = [<!rootClasses!>]
|
||||
appClasses = <!appClasses!>
|
||||
appClassNames = [<!appClassNames!>]
|
||||
allClassNames = [<!allClassNames!>]
|
||||
# List of classes that must be hidden from the catalog
|
||||
catalogMap = {}
|
||||
<!catalogMap!>
|
||||
|
||||
# Dict whose keys are class names and whose values are workflow names (=the
|
||||
# workflow used by the content type)
|
||||
workflows = {<!workflows!>}
|
||||
# In the following dict, we keep one instance for every Appy workflow defined
|
||||
# in the application. Those prototypical instances will be used for executing
|
||||
# user-defined actions and transitions. For each instance, we add a special
|
||||
|
@ -43,4 +68,16 @@ attributes = {<!attributes!>}
|
|||
# In the followinf dict, we store, for every Appy class, a dict of appy types
|
||||
# keyed by their names.
|
||||
attributesDict = {<!attributesDict!>}
|
||||
|
||||
# Application roles
|
||||
applicationRoles = [<!roles!>]
|
||||
applicationGlobalRoles = [<!gRoles!>]
|
||||
grantableRoles = [<!grRoles!>]
|
||||
|
||||
# Configuration options
|
||||
showPortlet = <!showPortlet!>
|
||||
languages = [<!languages!>]
|
||||
languageSelector = <!languageSelector!>
|
||||
minimalistPlone = <!minimalistPlone!>
|
||||
appFrontPage = <!appFrontPage!>
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
93
gen/plone25/wrappers/UserWrapper.py
Normal file
93
gen/plone25/wrappers/UserWrapper.py
Normal 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)
|
||||
# ------------------------------------------------------------------------------
|
|
@ -151,13 +151,13 @@ class AbstractWrapper:
|
|||
isField = isinstance(fieldNameOrClass, basestring)
|
||||
# Determine the portal type of the object to create
|
||||
if isField:
|
||||
fieldName = fieldNameOrClass
|
||||
idPrefix = fieldName
|
||||
portalType = self.o.getAppyRefPortalType(fieldName)
|
||||
fieldName = idPrefix = fieldNameOrClass
|
||||
appyType = self.o.getAppyType(fieldName)
|
||||
portalType = self.tool.o.getPortalType(appyType.klass)
|
||||
else:
|
||||
theClass = fieldNameOrClass
|
||||
idPrefix = theClass.__name__
|
||||
portalType = self.o._appy_getAtType(theClass, self.flavour.o)
|
||||
klass = fieldNameOrClass
|
||||
idPrefix = klass.__name__
|
||||
portalType = self.tool.o.getPortalType(klass)
|
||||
# Determine object id
|
||||
if kwargs.has_key('id'):
|
||||
objId = kwargs['id']
|
||||
|
|
|
@ -40,6 +40,7 @@ class PoMessage:
|
|||
'comment every time a transition is triggered'
|
||||
MSG_showAllStatesInPhaseFor = 'Show all states in phase'
|
||||
POD_TEMPLATE = 'POD template'
|
||||
USER = 'User'
|
||||
POD_ASKACTION = 'Trigger related action'
|
||||
DEFAULT_VALID_ERROR = 'Please fill or correct this.'
|
||||
REF_NO = 'No object.'
|
||||
|
|
17
gen/utils.py
17
gen/utils.py
|
@ -308,4 +308,21 @@ class FileWrapper:
|
|||
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
|
||||
return
|
||||
return filePath
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def getClassName(klass, appName=None):
|
||||
'''Generates, from appy-class p_klass, the name of the corresponding
|
||||
Archetypes class. For some classes, name p_appName is required: it is
|
||||
part of the class name.'''
|
||||
moduleName = klass.__module__
|
||||
if (moduleName == 'appy.gen.plone25.model') or \
|
||||
moduleName.endswith('.appyWrappers'):
|
||||
# This is a model (generation time or run time)
|
||||
res = appName + klass.__name__
|
||||
elif klass.__bases__ and (klass.__bases__[-1].__module__ == 'appy.gen'):
|
||||
# This is a customized class (inherits from appy.gen.Tool, User,...)
|
||||
res = appName + klass.__bases__[-1].__name__
|
||||
else: # This is a standard class
|
||||
res = klass.__module__.replace('.', '_') + '_' + klass.__name__
|
||||
return res
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -5,20 +5,178 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path
|
||||
|
||||
# List of names of language in their own language ------------------------------
|
||||
# It was copied from Plone 2.5.5 (PloneLanguageTool), don't know any "authentic
|
||||
# source" for that.
|
||||
nativeNames = {
|
||||
'aa' : 'магIарул мацI',
|
||||
'ab' : 'бызшәа',
|
||||
'af' : 'Afrikaans',
|
||||
'am' : 'አማርኛ',
|
||||
'ar' : 'العربية',
|
||||
'as' : 'অসমিয়া',
|
||||
'ay' : 'Aymara',
|
||||
'az' : 'Azəri Türkçəsi',
|
||||
'ba' : 'Bashkir',
|
||||
'be' : 'Беларускі',
|
||||
'bg' : 'Български',
|
||||
'bh' : 'Bihari',
|
||||
'bi' : 'Bislama',
|
||||
'bn' : 'বাংলা',
|
||||
'bo' : 'བོད་སྐད་',
|
||||
'bs' : 'Bosanski',
|
||||
'br' : 'Brezhoneg',
|
||||
'ca' : 'Català',
|
||||
'ch' : 'Chamoru',
|
||||
'co' : 'Corsu',
|
||||
'cs' : 'Čeština',
|
||||
'cy' : 'Cymraeg',
|
||||
'da' : 'Dansk',
|
||||
'de' : 'Deutsch',
|
||||
'dz' : 'རྫོང་ཁ',
|
||||
'el' : 'Ελληνικά',
|
||||
'en' : 'English',
|
||||
'eo' : 'Esperanto',
|
||||
'es' : 'Español',
|
||||
'et' : 'Eesti',
|
||||
'eu' : 'Euskara',
|
||||
'fa' : 'فارسی',
|
||||
'fi' : 'Suomi',
|
||||
'fj' : 'Fiji',
|
||||
'fo' : 'Føroyska',
|
||||
'fr' : 'Français',
|
||||
'fy' : 'Frysk',
|
||||
'ga' : 'Gaeilge',
|
||||
'gd' : 'Gàidhlig',
|
||||
'gl' : 'Galego',
|
||||
'gn' : 'Guarani',
|
||||
'gu' : 'ગુજરાતી',
|
||||
'gv' : 'Gaelg',
|
||||
'ha' : 'هَوُس',
|
||||
'he' : 'עברית',
|
||||
'hi' : 'हिंदी',
|
||||
'hr' : 'Hrvatski',
|
||||
'hu' : 'Magyar',
|
||||
'hy' : 'Հայերէն',
|
||||
'ia' : 'Interlingua',
|
||||
'id' : 'Bahasa Indonesia',
|
||||
'ie' : 'Interlingue',
|
||||
'ik' : 'Inupiak',
|
||||
'is' : 'Íslenska',
|
||||
'it' : 'Italiano',
|
||||
'iu' : 'ᐃᓄᒃᑎᑐᑦ',
|
||||
'ja' : '日本語',
|
||||
'jbo': 'lojban',
|
||||
'jw' : 'Basa Jawi',
|
||||
'ka' : 'ქართული',
|
||||
'kk' : 'ﻗﺎﺯﺍﻗﺸﺎ',
|
||||
'kl' : 'Greenlandic',
|
||||
'km' : 'ខ្មែរ',
|
||||
'kn' : 'ಕನ್ನಡ',
|
||||
'ko' : '한국어',
|
||||
'ks' : 'काऽशुर',
|
||||
'ku' : 'Kurdí',
|
||||
'kw' : 'Kernewek',
|
||||
'ky' : 'Кыргыз',
|
||||
'la' : 'Latin',
|
||||
'lb' : 'Lëtzebuergesch',
|
||||
'li' : 'Limburgs',
|
||||
'ln' : 'Lingala',
|
||||
'lo' : 'ພາສາລາວ',
|
||||
'lt' : 'Lietuviskai',
|
||||
'lv' : 'Latviešu',
|
||||
'mg' : 'Malagasy',
|
||||
'mi' : 'Maori',
|
||||
'mk' : 'Македонски',
|
||||
'ml' : 'മലയാളം',
|
||||
'mn' : 'Монгол',
|
||||
'mo' : 'Moldavian',
|
||||
'mr' : 'मराठी',
|
||||
'ms' : 'Bahasa Melayu',
|
||||
'mt' : 'Malti',
|
||||
'my' : 'Burmese',
|
||||
'na' : 'Nauru',
|
||||
'ne' : 'नेपाली',
|
||||
'nl' : 'Nederlands',
|
||||
'no' : 'Norsk',
|
||||
'nn' : 'Nynorsk',
|
||||
'oc' : 'Languedoc',
|
||||
'om' : 'Oromo',
|
||||
'or' : 'ଓଡ଼ିଆ',
|
||||
'pa' : 'ਪੰਜਾਬੀ',
|
||||
'pl' : 'Polski',
|
||||
'ps' : 'پښتو',
|
||||
'pt' : 'Português',
|
||||
'qu' : 'Quechua',
|
||||
'rm' : 'Rumantsch',
|
||||
'rn' : 'Kirundi',
|
||||
'ro' : 'Română',
|
||||
'ru' : 'Русский',
|
||||
'rw' : 'Kiyarwanda',
|
||||
'sa' : 'संस्कृत',
|
||||
'sd' : 'Sindhi',
|
||||
'se' : 'Northern Sámi',
|
||||
'sg' : 'Sangho',
|
||||
'sh' : 'Serbo-Croatian',
|
||||
'si' : 'Singhalese',
|
||||
'sk' : 'Slovenčina',
|
||||
'sl' : 'Slovenščina',
|
||||
'sm' : 'Samoan',
|
||||
'sn' : 'Shona',
|
||||
'so' : 'Somali',
|
||||
'sq' : 'Shqip',
|
||||
'sr' : 'српски',
|
||||
'ss' : 'Siswati',
|
||||
'st' : 'Sesotho',
|
||||
'su' : 'Sudanese',
|
||||
'sv' : 'Svenska',
|
||||
'sw' : 'Kiswahili',
|
||||
'ta' : 'தமிழ',
|
||||
'te' : 'తెలుగు',
|
||||
'tg' : 'Тоҷики',
|
||||
'th' : 'ไทย',
|
||||
'ti' : 'ትግርኛ',
|
||||
'tk' : 'түркmенче',
|
||||
'tl' : 'Tagalog',
|
||||
'tn' : 'Setswana',
|
||||
'to' : 'Lea faka-Tonga',
|
||||
'tr' : 'Türkçe',
|
||||
'ts' : 'Tsonga',
|
||||
'tt' : 'татарча',
|
||||
'tw' : 'Twi',
|
||||
'ug' : 'Uigur',
|
||||
'uk' : 'Українська',
|
||||
'ur' : 'اردو',
|
||||
'uz' : 'Ўзбекча',
|
||||
'vi' : 'Tiếng Việt',
|
||||
'vo' : 'Volapük',
|
||||
'wa' : 'Walon',
|
||||
'wo' : 'Wolof',
|
||||
'xh' : 'isiXhosa',
|
||||
'yi' : 'ײִדיש',
|
||||
'yo' : 'Yorùbá',
|
||||
'za' : 'Zhuang',
|
||||
'zh' : '中文',
|
||||
'zu' : 'isiZulu'
|
||||
}
|
||||
# ------------------------------------------------------------------------------
|
||||
class Countries:
|
||||
'''This class gives access to the country codes as standardized by
|
||||
class Languages:
|
||||
'''This class gives access to the language codes as standardized by
|
||||
ISO-639. The file has been downloaded in July 2009 from
|
||||
http://www.loc.gov/standards/iso639-2/ascii_8bits.html (UTF-8 version)'''
|
||||
def __init__(self):
|
||||
self.fileName = os.path.dirname(__file__) + '/CountryCodesIso639.2.txt'
|
||||
self.fileName = os.path.dirname(__file__) + '/LanguageCodesIso639.2.txt'
|
||||
self.languageCodes = []
|
||||
# Names of languages in English
|
||||
self.languageNames = []
|
||||
# Names of languages in their language. It is not part of ISO 639.2 and
|
||||
# is taken from dict languageNames above.
|
||||
self.nativeNames = []
|
||||
self.parseFile()
|
||||
|
||||
def parseFile(self):
|
||||
'''Parses the language codes and names in the ISO file and puts them in
|
||||
self.languageCodes and self.languageNames.'''
|
||||
self.languageCodes, self.languageNames and self.nativeNames.'''
|
||||
f = file(self.fileName)
|
||||
for line in f:
|
||||
if line.strip():
|
||||
|
@ -27,11 +185,16 @@ class Countries:
|
|||
# I take only those that have a 2-chars ISO-639-1 code.
|
||||
self.languageCodes.append(lineElems[2])
|
||||
self.languageNames.append(lineElems[3])
|
||||
if lineElems[2] in nativeNames:
|
||||
self.nativeNames.append(nativeNames[lineElems[2]])
|
||||
else:
|
||||
# Put the english name nevertheless.
|
||||
self.nativeNames.append(lineElems[3])
|
||||
f.close()
|
||||
|
||||
def exists(self, countryCode):
|
||||
'''Is p_countryCode a valid 2-digits country code?'''
|
||||
return countryCode in self.languageCodes
|
||||
def exists(self, code):
|
||||
'''Is p_code a valid 2-digits language/country code?'''
|
||||
return code in self.languageCodes
|
||||
|
||||
def __repr__(self):
|
||||
i = -1
|
||||
|
@ -41,7 +204,7 @@ class Countries:
|
|||
res += 'Language: ' + languageCode + ' - ' + self.languageNames[i]
|
||||
res += '\n'
|
||||
return res
|
||||
countries = Countries() # As this is international, I instantiate it.
|
||||
languages = Languages() # As this is international, I instantiate it.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class BelgianCities:
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, traceback, unicodedata
|
||||
import os, os.path, sys, traceback, unicodedata, shutil
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class FolderDeleter:
|
||||
|
@ -47,6 +47,29 @@ def cleanFolder(folder, exts=extsToClean, verbose=False):
|
|||
if verbose: print 'Removing %s...' % fileToRemove
|
||||
os.remove(fileToRemove)
|
||||
|
||||
|
||||
def copyFolder(source, dest, cleanDest=False):
|
||||
'''Copies the content of folder p_source to folder p_dest. p_dest is
|
||||
created, with intermediary subfolders if required. If p_cleanDest is
|
||||
True, it removes completely p_dest if it existed.'''
|
||||
dest = os.path.abspath(dest)
|
||||
# Delete the dest folder if required
|
||||
if os.path.exists(dest) and cleanDest:
|
||||
FolderDeleter.delete(dest)
|
||||
# Create the dest folder if it does not exist
|
||||
if not os.path.exists(dest):
|
||||
os.makedirs(dest)
|
||||
# Copy the content of p_source to p_dest.
|
||||
for name in os.listdir(source):
|
||||
sourceName = os.path.join(source, name)
|
||||
destName = os.path.join(dest, name)
|
||||
if os.path.isfile(sourceName):
|
||||
# Copy a single file
|
||||
shutil.copy(sourceName, destName)
|
||||
elif os.path.isdir(sourceName):
|
||||
# Copy a subfolder (recursively)
|
||||
copyFolder(sourceName, destName)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Traceback:
|
||||
'''Dumps the last traceback into a string.'''
|
||||
|
|
Loading…
Reference in a new issue