2009-06-29 07:06:01 -05:00
|
|
|
'''This file contains basic classes that will be added into any user
|
|
|
|
application for creating the basic structure of the application "Tool" which
|
2011-12-05 08:11:29 -06:00
|
|
|
is the set of web pages used for configuring the application.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2010-11-10 08:15:00 -06:00
|
|
|
import types
|
2011-12-05 08:11:29 -06:00
|
|
|
import appy.gen as gen
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2011-09-06 14:46:57 -05:00
|
|
|
# Prototypical instances of every type -----------------------------------------
|
|
|
|
class Protos:
|
|
|
|
protos = {}
|
|
|
|
# List of attributes that can't be given to a Type constructor
|
|
|
|
notInit = ('id', 'type', 'pythonType', 'slaves', 'isSelect', 'hasLabel',
|
2011-10-01 15:40:13 -05:00
|
|
|
'hasDescr', 'hasHelp', 'required', 'filterable', 'validable',
|
2013-08-21 15:25:27 -05:00
|
|
|
'isBack', 'sync', 'pageName', 'masterName')
|
2011-09-06 14:46:57 -05:00
|
|
|
@classmethod
|
|
|
|
def get(self, appyType):
|
|
|
|
'''Returns a prototype instance for p_appyType.'''
|
|
|
|
className = appyType.__class__.__name__
|
|
|
|
isString = (className == 'String')
|
|
|
|
if isString:
|
|
|
|
# For Strings, we create one prototype per format, because default
|
|
|
|
# values may change according to format.
|
|
|
|
className += str(appyType.format)
|
|
|
|
if className in self.protos: return self.protos[className]
|
|
|
|
# The prototype does not exist yet: create it
|
|
|
|
if isString:
|
|
|
|
proto = appyType.__class__(format=appyType.format)
|
|
|
|
# Now, we fake to be able to detect default values
|
|
|
|
proto.format = 0
|
|
|
|
else:
|
|
|
|
proto = appyType.__class__()
|
|
|
|
self.protos[className] = proto
|
|
|
|
return proto
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class ModelClass:
|
|
|
|
'''This class is the abstract class of all predefined application classes
|
2010-10-14 07:43:56 -05:00
|
|
|
used in the Appy model: Tool, User, etc. All methods and attributes of
|
|
|
|
those classes are part of the Appy machinery and are prefixed with _appy_
|
|
|
|
in order to avoid name conflicts with user-defined parts of the
|
|
|
|
application model.'''
|
2013-01-11 10:16:36 -06:00
|
|
|
# In any ModelClass subclass we need to declare attributes in the following
|
|
|
|
# list (including back attributes), to keep track of attributes order.
|
|
|
|
_appy_attributes = []
|
2012-03-26 12:09:45 -05:00
|
|
|
folder = False
|
2010-01-06 11:36:16 -06:00
|
|
|
@classmethod
|
2011-09-06 14:46:57 -05:00
|
|
|
def _appy_getTypeBody(klass, appyType, wrapperName):
|
2009-06-29 07:06:01 -05:00
|
|
|
'''This method returns the code declaration for p_appyType.'''
|
|
|
|
typeArgs = ''
|
2011-09-06 14:46:57 -05:00
|
|
|
proto = Protos.get(appyType)
|
|
|
|
for name, value in appyType.__dict__.iteritems():
|
|
|
|
# Some attrs can't be given to the constructor
|
|
|
|
if name in Protos.notInit: continue
|
|
|
|
# If the given value corresponds to the default value, don't give it
|
|
|
|
if value == getattr(proto, name): continue
|
|
|
|
if name == 'layouts':
|
2011-01-14 02:06:25 -06:00
|
|
|
# For Tool attributes we do not copy layout info. Indeed, most
|
|
|
|
# fields added to the Tool are config-related attributes whose
|
|
|
|
# layouts must be standard.
|
2011-09-06 14:46:57 -05:00
|
|
|
if klass.__name__ == 'Tool': continue
|
|
|
|
layouts = appyType.getInputLayouts()
|
|
|
|
# For the Translation class that has potentially thousands of
|
|
|
|
# attributes, the most used layout is cached in a global var in
|
2011-12-05 03:52:18 -06:00
|
|
|
# named "tfw" in wrappers.py.
|
2011-09-06 14:46:57 -05:00
|
|
|
if (klass.__name__ == 'Translation') and \
|
|
|
|
(layouts == '{"edit":"f","cell":"f","view":"f",}'):
|
|
|
|
value = 'tfw'
|
|
|
|
else:
|
|
|
|
value = appyType.getInputLayouts()
|
2012-03-26 12:09:45 -05:00
|
|
|
elif (name == 'klass') and value and (value == klass):
|
|
|
|
# This is a auto-Ref (a Ref that references the klass itself).
|
|
|
|
# At this time, we can't reference the class that is still being
|
|
|
|
# defined. So we initialize it to None. The post-init of the
|
|
|
|
# field must be done manually in wrappers.py.
|
|
|
|
value = 'None'
|
2011-09-06 14:46:57 -05:00
|
|
|
elif isinstance(value, basestring):
|
|
|
|
value = '"%s"' % value
|
2011-12-05 08:11:29 -06:00
|
|
|
elif isinstance(value, gen.Ref):
|
2011-09-06 14:46:57 -05:00
|
|
|
if not value.isBack: continue
|
|
|
|
value = klass._appy_getTypeBody(value, wrapperName)
|
|
|
|
elif type(value) == type(ModelClass):
|
|
|
|
moduleName = value.__module__
|
2009-06-29 07:06:01 -05:00
|
|
|
if moduleName.startswith('appy.gen'):
|
2011-09-06 14:46:57 -05:00
|
|
|
value = value.__name__
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2011-09-06 14:46:57 -05:00
|
|
|
value = '%s.%s' % (moduleName, value.__name__)
|
2011-12-05 08:11:29 -06:00
|
|
|
elif isinstance(value, gen.Selection):
|
2011-09-06 14:46:57 -05:00
|
|
|
value = 'Selection("%s")' % value.methodName
|
2011-12-05 08:11:29 -06:00
|
|
|
elif isinstance(value, gen.Group):
|
2011-11-28 15:50:01 -06:00
|
|
|
value = 'Grp("%s")' % value.name
|
2011-12-05 08:11:29 -06:00
|
|
|
elif isinstance(value, gen.Page):
|
2012-03-26 12:09:45 -05:00
|
|
|
value = 'pges["%s"]' % value.name
|
2011-09-06 14:46:57 -05:00
|
|
|
elif callable(value):
|
2012-02-21 05:09:42 -06:00
|
|
|
className = wrapperName
|
|
|
|
if (appyType.type == 'Ref') and appyType.isBack:
|
2012-05-29 13:50:18 -05:00
|
|
|
className = value.im_class.__name__
|
2012-02-21 05:09:42 -06:00
|
|
|
value = '%s.%s' % (className, value.__name__)
|
2011-09-06 14:46:57 -05:00
|
|
|
typeArgs += '%s=%s,' % (name, value)
|
2009-06-29 07:06:01 -05:00
|
|
|
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
|
|
|
|
2010-01-06 11:36:16 -06:00
|
|
|
@classmethod
|
2009-06-29 07:06:01 -05:00
|
|
|
def _appy_getBody(klass):
|
|
|
|
'''This method returns the code declaration of this class. We will dump
|
2011-12-05 03:52:18 -06:00
|
|
|
this in wrappers.py in the Zope product.'''
|
2011-09-06 14:46:57 -05:00
|
|
|
className = klass.__name__
|
|
|
|
# Determine the name of the class and its wrapper. Because so much
|
|
|
|
# attributes can be generated on a TranslationWrapper, shortcutting it
|
|
|
|
# to 'TW' may reduce the generated file from several kilobytes.
|
|
|
|
if className == 'Translation': wrapperName = 'WT'
|
|
|
|
else: wrapperName = 'W%s' % className
|
|
|
|
res = 'class %s(%s):\n' % (className, wrapperName)
|
|
|
|
# Tool must be folderish
|
2012-03-26 12:09:45 -05:00
|
|
|
if klass.folder: res += ' folder=True\n'
|
2011-01-14 02:06:25 -06:00
|
|
|
# First, scan all attributes, determine all used pages and create a
|
|
|
|
# dict with it. It will prevent us from creating a new Page instance
|
|
|
|
# for every field.
|
|
|
|
pages = {}
|
2011-09-06 14:46:57 -05:00
|
|
|
layouts = []
|
|
|
|
for name in klass._appy_attributes:
|
|
|
|
exec 'appyType = klass.%s' % name
|
2011-01-14 02:06:25 -06:00
|
|
|
if appyType.page.name not in pages:
|
|
|
|
pages[appyType.page.name] = appyType.page
|
2012-03-26 12:09:45 -05:00
|
|
|
res += ' pges = {'
|
2011-01-14 02:06:25 -06:00
|
|
|
for page in pages.itervalues():
|
|
|
|
# Determine page show
|
|
|
|
pageShow = page.show
|
|
|
|
if isinstance(pageShow, basestring): pageShow='"%s"' % pageShow
|
2012-05-05 10:04:19 -05:00
|
|
|
elif callable(pageShow):
|
|
|
|
pageShow = '%s.%s' % (wrapperName, pageShow.__name__)
|
2013-10-20 11:12:39 -05:00
|
|
|
pShow = ''
|
|
|
|
if pageShow != True:
|
|
|
|
pShow = ', show=%s' % pageShow
|
|
|
|
# For translation pages, fixed labels are used.
|
|
|
|
label = ''
|
|
|
|
if className == 'Translation':
|
|
|
|
name = (page.name == 'main') and 'Options' or page.name
|
|
|
|
label = ', label="%s"' % name
|
|
|
|
res += '"%s":Pge("%s"%s%s),' % (page.name, page.name, pShow, label)
|
2011-01-14 02:06:25 -06:00
|
|
|
res += '}\n'
|
2013-01-11 10:16:36 -06:00
|
|
|
# Secondly, dump every (not Ref.isBack) attribute
|
2011-09-06 14:46:57 -05:00
|
|
|
for name in klass._appy_attributes:
|
|
|
|
exec 'appyType = klass.%s' % name
|
2013-01-11 10:16:36 -06:00
|
|
|
if (appyType.type == 'Ref') and appyType.isBack: continue
|
2011-09-06 14:46:57 -05:00
|
|
|
typeBody = klass._appy_getTypeBody(appyType, wrapperName)
|
|
|
|
res += ' %s=%s\n' % (name, typeBody)
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
# The User class ---------------------------------------------------------------
|
2010-09-02 09:16:08 -05:00
|
|
|
class User(ModelClass):
|
2010-10-14 07:43:56 -05:00
|
|
|
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
|
2013-09-06 09:19:56 -05:00
|
|
|
'password2', 'email', 'roles', 'source', 'groups',
|
|
|
|
'toTool']
|
2010-09-02 09:16:08 -05:00
|
|
|
# All methods defined below are fake. Real versions are in the wrapper.
|
2011-12-05 08:11:29 -06:00
|
|
|
title = gen.String(show=False, indexed=True)
|
2012-05-29 13:50:18 -05:00
|
|
|
gm = {'group': 'main', 'width': 25}
|
2012-03-03 16:29:32 -06:00
|
|
|
def showName(self): pass
|
|
|
|
name = gen.String(show=showName, **gm)
|
|
|
|
firstName = gen.String(show=showName, **gm)
|
2012-05-29 13:50:18 -05:00
|
|
|
def showEmail(self): pass
|
2013-08-21 05:35:30 -05:00
|
|
|
email = gen.String(show=showEmail, **gm)
|
2013-09-06 09:19:56 -05:00
|
|
|
# Where is this user stored? By default, in the ZODB. But the user can be
|
|
|
|
# stored in an external LDAP (source='ldap').
|
|
|
|
source = gen.String(show=False, default='zodb', layouts='f', **gm)
|
2012-05-29 13:50:18 -05:00
|
|
|
gm['multiplicity'] = (1,1)
|
2010-09-02 09:16:08 -05:00
|
|
|
def showLogin(self): pass
|
|
|
|
def validateLogin(self): pass
|
2011-12-05 08:11:29 -06:00
|
|
|
login = gen.String(show=showLogin, validator=validateLogin,
|
|
|
|
indexed=True, **gm)
|
2010-09-02 09:16:08 -05:00
|
|
|
def showPassword(self): pass
|
|
|
|
def validatePassword(self): pass
|
2011-12-05 08:11:29 -06:00
|
|
|
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
|
|
|
validator=validatePassword, **gm)
|
|
|
|
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
2010-09-02 09:16:08 -05:00
|
|
|
gm['multiplicity'] = (0, None)
|
2012-02-21 05:09:42 -06:00
|
|
|
def showRoles(self): pass
|
|
|
|
roles = gen.String(show=showRoles, indexed=True,
|
|
|
|
validator=gen.Selection('getGrantableRoles'), **gm)
|
2010-09-02 09:16:08 -05:00
|
|
|
|
2011-11-28 15:50:01 -06:00
|
|
|
# The Group class --------------------------------------------------------------
|
|
|
|
class Group(ModelClass):
|
2013-01-11 10:16:36 -06:00
|
|
|
_appy_attributes = ['title', 'login', 'roles', 'users', 'toTool2']
|
2011-11-28 15:50:01 -06:00
|
|
|
# All methods defined below are fake. Real versions are in the wrapper.
|
|
|
|
m = {'group': 'main', 'width': 25, 'indexed': True}
|
2011-12-05 08:11:29 -06:00
|
|
|
title = gen.String(multiplicity=(1,1), **m)
|
2011-11-28 15:50:01 -06:00
|
|
|
def showLogin(self): pass
|
|
|
|
def validateLogin(self): pass
|
2011-12-05 08:11:29 -06:00
|
|
|
login = gen.String(show=showLogin, validator=validateLogin,
|
|
|
|
multiplicity=(1,1), **m)
|
|
|
|
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
|
|
|
multiplicity=(0,None), **m)
|
2013-09-22 15:08:48 -05:00
|
|
|
def getSelectableUsers(self): pass
|
2011-12-05 08:11:29 -06:00
|
|
|
users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
|
2012-06-13 02:31:09 -05:00
|
|
|
back=gen.Ref(attribute='groups', show=User.showRoles,
|
|
|
|
multiplicity=(0,None)),
|
2013-09-22 15:08:48 -05:00
|
|
|
select=getSelectableUsers, height=15,
|
2011-12-05 08:11:29 -06:00
|
|
|
showHeaders=True, shownInfo=('title', 'login'))
|
2011-11-28 15:50:01 -06:00
|
|
|
|
2011-01-14 02:06:25 -06:00
|
|
|
# The Translation class --------------------------------------------------------
|
|
|
|
class Translation(ModelClass):
|
2013-01-11 10:16:36 -06:00
|
|
|
_appy_attributes = ['po', 'title', 'sourceLanguage', 'trToTool']
|
2011-01-14 02:06:25 -06:00
|
|
|
# All methods defined below are fake. Real versions are in the wrapper.
|
2013-10-20 11:12:39 -05:00
|
|
|
title = gen.String(show=False, indexed=True,
|
|
|
|
page=gen.Page('main',label='Main'))
|
2011-01-14 02:06:25 -06:00
|
|
|
def getPoFile(self): pass
|
2013-10-20 11:12:39 -05:00
|
|
|
po = gen.Action(action=getPoFile, result='filetmp')
|
|
|
|
sourceLanguage = gen.String(width=4)
|
2011-09-06 14:46:57 -05:00
|
|
|
def label(self): pass
|
|
|
|
def show(self, name): pass
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2012-03-26 12:09:45 -05:00
|
|
|
# The Page class ---------------------------------------------------------------
|
|
|
|
class Page(ModelClass):
|
2013-01-11 10:16:36 -06:00
|
|
|
_appy_attributes = ['title', 'content', 'pages', 'parent', 'toTool3']
|
2012-03-26 12:09:45 -05:00
|
|
|
folder = True
|
2013-01-11 10:16:36 -06:00
|
|
|
title = gen.String(show='edit', multiplicity=(1,1), indexed=True)
|
2013-03-25 10:38:52 -05:00
|
|
|
content = gen.String(format=gen.String.XHTML, layouts='f')
|
2012-03-26 12:09:45 -05:00
|
|
|
# Pages can contain other pages.
|
|
|
|
def showSubPages(self): pass
|
|
|
|
pages = gen.Ref(None, multiplicity=(0,None), add=True, link=False,
|
|
|
|
back=gen.Ref(attribute='parent', show=False),
|
2012-03-27 03:37:41 -05:00
|
|
|
show=showSubPages, navigable=True)
|
2012-03-26 12:09:45 -05:00
|
|
|
Page.pages.klass = Page
|
|
|
|
setattr(Page, Page.pages.back.attribute, Page.pages.back)
|
|
|
|
|
2011-01-14 02:06:25 -06:00
|
|
|
# The Tool class ---------------------------------------------------------------
|
2012-02-02 10:30:54 -06:00
|
|
|
# Prefixes of the fields generated on the Tool.
|
2012-05-05 10:04:19 -05:00
|
|
|
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
2013-09-21 10:46:42 -05:00
|
|
|
'appyVersion', 'dateFormat', 'hourFormat',
|
|
|
|
'unoEnabledPython', 'openOfficePort',
|
|
|
|
'numberOfResultsPerPage', 'users', 'connectedUsers',
|
|
|
|
'groups', 'translations', 'loadTranslationsAtStartup',
|
|
|
|
'pages')
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
class Tool(ModelClass):
|
|
|
|
# In a ModelClass we need to declare attributes in the following list.
|
|
|
|
_appy_attributes = list(defaultToolFields)
|
2012-03-26 12:09:45 -05:00
|
|
|
folder = True
|
2010-10-14 07:43:56 -05:00
|
|
|
|
|
|
|
# Tool attributes
|
2012-05-05 10:04:19 -05:00
|
|
|
def isManager(self): pass
|
2012-05-08 07:49:45 -05:00
|
|
|
def isManagerEdit(self): pass
|
2012-07-18 14:58:11 -05:00
|
|
|
lf = {'layouts':'f'}
|
2012-12-03 09:18:24 -06:00
|
|
|
title = gen.String(show=False, page=gen.Page('main', show=False),
|
|
|
|
default='Configuration', **lf)
|
2012-07-18 14:58:11 -05:00
|
|
|
mailHost = gen.String(default='localhost:25', **lf)
|
|
|
|
mailEnabled = gen.Boolean(default=False, **lf)
|
|
|
|
mailFrom = gen.String(default='info@appyframework.org', **lf)
|
|
|
|
appyVersion = gen.String(**lf)
|
|
|
|
dateFormat = gen.String(default='%d/%m/%Y', **lf)
|
|
|
|
hourFormat = gen.String(default='%H:%M', **lf)
|
2013-09-21 10:46:42 -05:00
|
|
|
unoEnabledPython = gen.String(default='/usr/bin/python', **lf)
|
|
|
|
openOfficePort = gen.Integer(default=2002, **lf)
|
|
|
|
numberOfResultsPerPage = gen.Integer(default=30, **lf)
|
2012-05-05 10:04:19 -05:00
|
|
|
|
2011-09-14 14:01:58 -05:00
|
|
|
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
|
2012-07-18 14:58:11 -05:00
|
|
|
userPage = gen.Page('users', show=isManager)
|
2011-12-05 08:11:29 -06:00
|
|
|
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
2012-07-18 14:58:11 -05:00
|
|
|
back=gen.Ref(attribute='toTool', show=False), page=userPage,
|
2011-12-05 08:11:29 -06:00
|
|
|
queryable=True, queryFields=('title', 'login'),
|
2012-11-26 06:58:27 -06:00
|
|
|
show=isManager,
|
2011-12-05 08:11:29 -06:00
|
|
|
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
2012-07-18 14:58:11 -05:00
|
|
|
def computeConnectedUsers(self): pass
|
|
|
|
connectedUsers = gen.Computed(method=computeConnectedUsers, page=userPage,
|
2012-11-26 06:58:27 -06:00
|
|
|
plainText=False, show=isManager)
|
2011-12-05 08:11:29 -06:00
|
|
|
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
|
|
|
back=gen.Ref(attribute='toTool2', show=False),
|
2012-11-26 06:58:27 -06:00
|
|
|
page=gen.Page('groups', show=isManager), show=isManager,
|
2011-12-05 08:11:29 -06:00
|
|
|
queryable=True, queryFields=('title', 'login'),
|
|
|
|
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
2012-06-03 14:55:26 -05:00
|
|
|
pt = gen.Page('translations', show=isManager)
|
2011-12-05 08:11:29 -06:00
|
|
|
translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
|
2012-06-03 14:55:26 -05:00
|
|
|
link=False, show='view', page=pt,
|
|
|
|
back=gen.Ref(attribute='trToTool', show=False))
|
2012-08-21 12:57:23 -05:00
|
|
|
loadTranslationsAtStartup = gen.Boolean(default=True, show=False, page=pt,
|
|
|
|
layouts='f')
|
2012-03-26 12:09:45 -05:00
|
|
|
pages = gen.Ref(Page, multiplicity=(0,None), add=True, link=False,
|
|
|
|
show='view', back=gen.Ref(attribute='toTool3', show=False),
|
2012-05-05 10:04:19 -05:00
|
|
|
page=gen.Page('pages', show=isManager))
|
|
|
|
|
2010-01-06 11:36:16 -06:00
|
|
|
@classmethod
|
2009-06-29 07:06:01 -05:00
|
|
|
def _appy_clean(klass):
|
|
|
|
toClean = []
|
|
|
|
for k, v in klass.__dict__.iteritems():
|
|
|
|
if not k.startswith('__') and (not k.startswith('_appy_')):
|
2010-10-14 07:43:56 -05:00
|
|
|
if k not in defaultToolFields:
|
2009-06-29 07:06:01 -05:00
|
|
|
toClean.append(k)
|
|
|
|
for k in toClean:
|
|
|
|
exec 'del klass.%s' % k
|
2010-10-14 07:43:56 -05:00
|
|
|
klass._appy_attributes = list(defaultToolFields)
|
2012-03-26 12:09:45 -05:00
|
|
|
klass.folder = True
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|