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