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',
|
2012-11-02 16:27:54 -05:00
|
|
|
'backd', '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__)
|
2012-03-26 12:09:45 -05:00
|
|
|
res += '"%s":Pge("%s", show=%s),'% (page.name, page.name, pageShow)
|
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-01-11 10:16:36 -06:00
|
|
|
'password2', 'email', 'roles', '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
|
|
|
|
email = gen.String(show=showEmail)
|
|
|
|
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)
|
|
|
|
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)),
|
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-01-09 03:46:14 -06:00
|
|
|
actionsPage = gen.Page('actions')
|
2011-01-14 02:06:25 -06:00
|
|
|
def getPoFile(self): pass
|
2013-01-09 03:46:14 -06:00
|
|
|
po = gen.Action(action=getPoFile, page=actionsPage, result='filetmp')
|
|
|
|
sourceLanguage = gen.String(page=actionsPage, width=4)
|
|
|
|
title = gen.String(show=False, indexed=True, page=actionsPage)
|
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)
|
2012-05-05 10:04:19 -05:00
|
|
|
content = gen.String(format=gen.String.XHTML, layouts='f', richText=True)
|
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.
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
|
2010-10-14 07:43:56 -05:00
|
|
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
2012-11-05 06:12:18 -06:00
|
|
|
'searchFields')
|
2012-05-05 10:04:19 -05:00
|
|
|
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
2012-07-18 14:58:11 -05:00
|
|
|
'appyVersion', 'dateFormat', 'hourFormat', 'users',
|
|
|
|
'connectedUsers', 'groups', 'translations',
|
2012-06-03 14:55:26 -05:00
|
|
|
'loadTranslationsAtStartup', 'pages', 'unoEnabledPython',
|
|
|
|
'openOfficePort', 'numberOfResultsPerPage')
|
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)
|
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))
|
|
|
|
|
|
|
|
# Document generation page
|
2012-09-13 07:43:40 -05:00
|
|
|
dgp = {'page': gen.Page('documents', show=isManagerEdit)}
|
2012-05-05 10:04:19 -05:00
|
|
|
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
|
|
|
unoEnabledPython = gen.String(show=False,validator=validPythonWithUno,**dgp)
|
|
|
|
openOfficePort = gen.Integer(default=2002, show=False, **dgp)
|
|
|
|
# User interface page
|
|
|
|
numberOfResultsPerPage = gen.Integer(default=30,
|
|
|
|
page=gen.Page('userInterface', show=False))
|
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
|
|
|
# ------------------------------------------------------------------------------
|