2011-12-05 03:52:18 -06:00
|
|
|
'''Descriptor classes defined in this file are "intermediary" classes that
|
|
|
|
gather, from the user application, information about found gen- or workflow-
|
|
|
|
classes.'''
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
2011-12-05 03:52:18 -06:00
|
|
|
import types, copy
|
2011-12-05 08:11:29 -06:00
|
|
|
import appy.gen as gen
|
2012-11-06 04:32:39 -06:00
|
|
|
import po
|
2011-12-05 03:52:18 -06:00
|
|
|
from model import ModelClass, toolFieldPrefixes
|
|
|
|
from utils import produceNiceMessage, getClassName
|
|
|
|
TABS = 4 # Number of blanks in a Python indentation.
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class Descriptor: # Abstract
|
|
|
|
def __init__(self, klass, orderedAttributes, generator):
|
2010-08-05 11:23:17 -05:00
|
|
|
# The corresponding Python class
|
|
|
|
self.klass = klass
|
|
|
|
# The names of the static appy-compliant attributes declared in
|
|
|
|
# self.klass
|
|
|
|
self.orderedAttributes = orderedAttributes
|
|
|
|
# A reference to the code generator.
|
|
|
|
self.generator = generator
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def __repr__(self): return '<Class %s>' % self.klass.__name__
|
|
|
|
|
|
|
|
class ClassDescriptor(Descriptor):
|
|
|
|
'''This class gives information about an Appy class.'''
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def __init__(self, klass, orderedAttributes, generator):
|
2011-12-05 08:11:29 -06:00
|
|
|
Descriptor.__init__(self, klass, orderedAttributes, generator)
|
2011-12-05 03:52:18 -06:00
|
|
|
self.methods = '' # Needed method definitions will be generated here
|
|
|
|
self.name = getClassName(self.klass, generator.applicationName)
|
|
|
|
self.predefined = False
|
|
|
|
self.customized = False
|
|
|
|
# Phase and page names will be calculated later, when first required.
|
|
|
|
self.phases = None
|
|
|
|
self.pages = None
|
|
|
|
|
2010-01-06 11:36:16 -06:00
|
|
|
def getOrderedAppyAttributes(self, condition=None):
|
2009-06-29 07:06:01 -05:00
|
|
|
'''Returns the appy types for all attributes of this class and parent
|
2010-01-06 11:36:16 -06:00
|
|
|
class(es). If a p_condition is specified, ony Appy types matching
|
|
|
|
the condition will be returned. p_condition must be a string
|
|
|
|
containing an expression that will be evaluated with, in its context,
|
|
|
|
"self" being this ClassDescriptor and "attrValue" being the current
|
2010-08-05 11:23:17 -05:00
|
|
|
Type instance.
|
|
|
|
|
|
|
|
Order of returned attributes already takes into account type's
|
|
|
|
"move" attributes.'''
|
|
|
|
attrs = []
|
2009-06-29 07:06:01 -05:00
|
|
|
# First, get the attributes for the current class
|
|
|
|
for attrName in self.orderedAttributes:
|
2010-08-05 11:23:17 -05:00
|
|
|
try:
|
|
|
|
attrValue = getattr(self.klass, attrName)
|
|
|
|
hookClass = self.klass
|
|
|
|
except AttributeError:
|
|
|
|
attrValue = getattr(self.modelClass, attrName)
|
|
|
|
hookClass = self.modelClass
|
2011-12-05 08:11:29 -06:00
|
|
|
if isinstance(attrValue, gen.Type):
|
2010-01-06 11:36:16 -06:00
|
|
|
if not condition or eval(condition):
|
2010-08-05 11:23:17 -05:00
|
|
|
attrs.append( (attrName, attrValue, hookClass) )
|
2009-06-29 07:06:01 -05:00
|
|
|
# Then, add attributes from parent classes
|
|
|
|
for baseClass in self.klass.__bases__:
|
|
|
|
# Find the classDescr that corresponds to baseClass
|
|
|
|
baseClassDescr = None
|
|
|
|
for classDescr in self.generator.classes:
|
|
|
|
if classDescr.klass == baseClass:
|
|
|
|
baseClassDescr = classDescr
|
|
|
|
break
|
|
|
|
if baseClassDescr:
|
2010-08-05 11:23:17 -05:00
|
|
|
attrs = baseClassDescr.getOrderedAppyAttributes() + attrs
|
|
|
|
# Modify attributes order by using "move" attributes
|
|
|
|
res = []
|
|
|
|
for name, appyType, klass in attrs:
|
|
|
|
if appyType.move:
|
|
|
|
newPosition = len(res) - abs(appyType.move)
|
|
|
|
if newPosition <= 0:
|
|
|
|
newPosition = 0
|
|
|
|
res.insert(newPosition, (name, appyType, klass))
|
|
|
|
else:
|
|
|
|
res.append((name, appyType, klass))
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
|
|
|
def getChildren(self):
|
|
|
|
'''Returns, among p_allClasses, the classes that inherit from p_self.'''
|
|
|
|
res = []
|
|
|
|
for classDescr in self.generator.classes:
|
|
|
|
if (classDescr.klass != self.klass) and \
|
|
|
|
issubclass(classDescr.klass, self.klass):
|
|
|
|
res.append(classDescr)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def getPhases(self):
|
2011-01-14 02:06:25 -06:00
|
|
|
'''Lazy-gets the phases defined on fields of this class.'''
|
|
|
|
if not hasattr(self, 'phases') or (self.phases == None):
|
|
|
|
self.phases = []
|
|
|
|
for fieldName, appyType, klass in self.getOrderedAppyAttributes():
|
|
|
|
if appyType.page.phase in self.phases: continue
|
|
|
|
self.phases.append(appyType.page.phase)
|
|
|
|
return self.phases
|
|
|
|
|
|
|
|
def getPages(self):
|
|
|
|
'''Lazy-gets the page names defined on fields of this class.'''
|
|
|
|
if not hasattr(self, 'pages') or (self.pages == None):
|
|
|
|
self.pages = []
|
|
|
|
for fieldName, appyType, klass in self.getOrderedAppyAttributes():
|
|
|
|
if appyType.page.name in self.pages: continue
|
|
|
|
self.pages.append(appyType.page.name)
|
|
|
|
return self.pages
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2011-12-05 03:52:18 -06:00
|
|
|
def getParents(self, allClasses):
|
|
|
|
parentWrapper = 'AbstractWrapper'
|
|
|
|
parentClass = '%s.%s' % (self.klass.__module__, self.klass.__name__)
|
|
|
|
if self.klass.__bases__:
|
|
|
|
baseClassName = self.klass.__bases__[0].__name__
|
|
|
|
for k in allClasses:
|
|
|
|
if self.klass.__name__ == baseClassName:
|
|
|
|
parentWrapper = '%s_Wrapper' % k.name
|
|
|
|
return (parentWrapper, parentClass)
|
|
|
|
|
[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
|
|
|
def generateSchema(self):
|
|
|
|
'''Generates i18n and other related stuff for this class.'''
|
2011-12-05 03:52:18 -06:00
|
|
|
for attrName in self.orderedAttributes:
|
|
|
|
try:
|
|
|
|
attrValue = getattr(self.klass, attrName)
|
|
|
|
except AttributeError:
|
|
|
|
attrValue = getattr(self.modelClass, attrName)
|
[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
|
|
|
if not isinstance(attrValue, gen.Type): continue
|
|
|
|
FieldDescriptor(attrName, attrValue, self).generate()
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def isAbstract(self):
|
|
|
|
'''Is self.klass abstract?'''
|
|
|
|
res = False
|
|
|
|
if self.klass.__dict__.has_key('abstract'):
|
|
|
|
res = self.klass.__dict__['abstract']
|
|
|
|
return res
|
|
|
|
|
|
|
|
def isRoot(self):
|
|
|
|
'''Is self.klass root? A root class represents some kind of major
|
|
|
|
concept into the application. For example, creating instances
|
|
|
|
of such classes will be easy from the user interface.'''
|
|
|
|
res = False
|
|
|
|
if self.klass.__dict__.has_key('root'):
|
|
|
|
res = self.klass.__dict__['root']
|
|
|
|
return res
|
|
|
|
|
|
|
|
def isFolder(self, klass=None):
|
|
|
|
'''Must self.klass be a folder? If klass is not None, this method tests
|
|
|
|
it on p_klass instead of self.klass.'''
|
|
|
|
res = False
|
|
|
|
theClass = self.klass
|
|
|
|
if klass:
|
|
|
|
theClass = klass
|
|
|
|
if theClass.__dict__.has_key('folder'):
|
|
|
|
res = theClass.__dict__['folder']
|
|
|
|
else:
|
|
|
|
if theClass.__bases__:
|
|
|
|
res = self.isFolder(theClass.__bases__[0])
|
|
|
|
return res
|
|
|
|
|
|
|
|
def getCreators(self):
|
|
|
|
'''Gets the specific creators defined for this class.'''
|
|
|
|
res = []
|
|
|
|
if self.klass.__dict__.has_key('creators') and self.klass.creators:
|
|
|
|
for creator in self.klass.creators:
|
2011-12-05 08:11:29 -06:00
|
|
|
if isinstance(creator, gen.Role):
|
2011-12-05 03:52:18 -06:00
|
|
|
if creator.local:
|
|
|
|
raise 'Local role "%s" cannot be used as a creator.' % \
|
|
|
|
creator.name
|
|
|
|
res.append(creator)
|
|
|
|
else:
|
2011-12-05 08:11:29 -06:00
|
|
|
res.append(gen.Role(creator))
|
2011-12-05 03:52:18 -06:00
|
|
|
return res
|
|
|
|
|
|
|
|
def getCreateMean(self, type='Import'):
|
|
|
|
'''Returns the mean for this class that corresponds to p_type, or
|
|
|
|
None if the class does not support this create mean.'''
|
2013-06-07 09:37:00 -05:00
|
|
|
if not self.klass.__dict__.has_key('create'): return
|
2011-12-05 03:52:18 -06:00
|
|
|
else:
|
|
|
|
means = self.klass.create
|
2013-06-07 09:37:00 -05:00
|
|
|
if not means: return
|
2011-12-05 03:52:18 -06:00
|
|
|
if not isinstance(means, tuple) and not isinstance(means, list):
|
|
|
|
means = [means]
|
|
|
|
for mean in means:
|
|
|
|
exec 'found = isinstance(mean, %s)' % type
|
|
|
|
if found: return mean
|
|
|
|
|
|
|
|
@staticmethod
|
2013-06-07 09:37:00 -05:00
|
|
|
def getSearches(klass, tool=None):
|
|
|
|
'''Returns the list of searches that are defined on this class. If
|
|
|
|
p_tool is given, we are at execution time (not a generation time),
|
|
|
|
and we may potentially execute search.show methods that allow to
|
|
|
|
conditionnaly include a search or not.'''
|
2011-12-05 03:52:18 -06:00
|
|
|
if klass.__dict__.has_key('search'):
|
|
|
|
searches = klass.__dict__['search']
|
2013-06-07 09:37:00 -05:00
|
|
|
if not tool: return searches
|
|
|
|
# Evaluate attributes "show" for every search.
|
|
|
|
return [s for s in searches if s.isShowable(klass, tool)]
|
|
|
|
return []
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def getSearch(klass, searchName):
|
|
|
|
'''Gets the search named p_searchName.'''
|
|
|
|
for search in ClassDescriptor.getSearches(klass):
|
|
|
|
if search.name == searchName:
|
|
|
|
return search
|
|
|
|
|
|
|
|
def addIndexMethod(self, field):
|
|
|
|
'''For indexed p_field, this method generates a method that allows to
|
|
|
|
get the value of the field as must be copied into the corresponding
|
|
|
|
index.'''
|
|
|
|
m = self.methods
|
|
|
|
spaces = TABS
|
|
|
|
n = field.fieldName
|
|
|
|
m += '\n' + ' '*spaces + 'def get%s%s(self):\n' % (n[0].upper(), n[1:])
|
|
|
|
spaces += TABS
|
|
|
|
m += ' '*spaces + "'''Gets indexable value of field \"%s\".'''\n" % n
|
|
|
|
m += ' '*spaces + 'return self.getAppyType("%s").getIndexValue(' \
|
|
|
|
'self)\n' % n
|
|
|
|
self.methods = m
|
|
|
|
|
|
|
|
def addField(self, fieldName, fieldType):
|
|
|
|
'''Adds a new field to the Tool.'''
|
|
|
|
exec "self.modelClass.%s = fieldType" % fieldName
|
|
|
|
if fieldName in self.modelClass._appy_attributes:
|
2013-05-29 17:46:11 -05:00
|
|
|
print('Warning, field "%s" is already existing on class "%s"' % \
|
|
|
|
(fieldName, self.modelClass.__name__))
|
2011-12-05 03:52:18 -06:00
|
|
|
return
|
|
|
|
self.modelClass._appy_attributes.append(fieldName)
|
|
|
|
self.orderedAttributes.append(fieldName)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2009-06-29 07:06:01 -05:00
|
|
|
class WorkflowDescriptor(Descriptor):
|
|
|
|
'''This class gives information about an Appy workflow.'''
|
2011-07-26 15:15:04 -05:00
|
|
|
@staticmethod
|
|
|
|
def getWorkflowName(klass):
|
|
|
|
'''Returns the name of this workflow.'''
|
|
|
|
res = klass.__module__.replace('.', '_') + '_' + klass.__name__
|
|
|
|
return res.lower()
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class FieldDescriptor:
|
|
|
|
'''This class gathers information about a specific typed attribute defined
|
|
|
|
in a gen-class.'''
|
|
|
|
|
|
|
|
def __init__(self, fieldName, appyType, classDescriptor):
|
|
|
|
self.appyType = appyType
|
|
|
|
self.classDescr = classDescriptor
|
|
|
|
self.generator = classDescriptor.generator
|
|
|
|
self.applicationName = classDescriptor.generator.applicationName
|
|
|
|
self.fieldName = fieldName
|
|
|
|
self.fieldParams = {'name': fieldName}
|
|
|
|
self.widgetParams = {}
|
|
|
|
self.fieldType = None
|
|
|
|
self.widgetType = None
|
|
|
|
|
2012-11-06 04:32:39 -06:00
|
|
|
def i18n(self, id, default, nice=True):
|
|
|
|
'''Shorthand for adding a new message into self.generator.labels.'''
|
|
|
|
self.generator.labels.append(id, default, nice=nice)
|
|
|
|
|
2011-12-05 03:52:18 -06:00
|
|
|
def __repr__(self):
|
|
|
|
return '<Field %s, %s>' % (self.fieldName, self.classDescr)
|
|
|
|
|
|
|
|
def getToolFieldMessage(self, fieldName):
|
|
|
|
'''Some attributes generated on the Tool class need a specific
|
|
|
|
default message, returned by this method.'''
|
|
|
|
res = fieldName
|
|
|
|
for prefix in toolFieldPrefixes:
|
|
|
|
fullPrefix = prefix + 'For'
|
|
|
|
if fieldName.startswith(fullPrefix):
|
|
|
|
messageId = 'MSG_%s' % prefix
|
2012-11-06 04:32:39 -06:00
|
|
|
res = getattr(po, messageId)
|
2011-12-05 03:52:18 -06:00
|
|
|
if res.find('%s') != -1:
|
|
|
|
# I must complete the message with the field name.
|
|
|
|
res = res % fieldName.split('_')[-1]
|
|
|
|
break
|
|
|
|
return res
|
|
|
|
|
|
|
|
def produceMessage(self, msgId, isLabel=True):
|
|
|
|
'''Gets the default label, description or help (depending on p_msgType)
|
|
|
|
for i18n message p_msgId.'''
|
|
|
|
default = ' '
|
2012-11-06 04:32:39 -06:00
|
|
|
niceDefault = False
|
2011-12-05 03:52:18 -06:00
|
|
|
if isLabel:
|
2012-11-06 04:32:39 -06:00
|
|
|
niceDefault = True
|
2011-12-05 03:52:18 -06:00
|
|
|
default = self.fieldName
|
|
|
|
# Some attributes need a specific predefined message
|
|
|
|
if isinstance(self.classDescr, ToolClassDescriptor):
|
|
|
|
default = self.getToolFieldMessage(self.fieldName)
|
2012-11-06 04:32:39 -06:00
|
|
|
if default != self.fieldName: niceDefault = False
|
|
|
|
return msgId, default, niceDefault
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def walkString(self):
|
|
|
|
'''How to generate an Appy String?'''
|
|
|
|
if self.appyType.isSelect and \
|
|
|
|
(type(self.appyType.validator) in (list, tuple)):
|
|
|
|
# Generate i18n messages for every possible value if the list
|
|
|
|
# of values is fixed.
|
|
|
|
for value in self.appyType.validator:
|
2012-11-06 04:32:39 -06:00
|
|
|
label = '%s_%s_list_%s' % (self.classDescr.name,
|
|
|
|
self.fieldName, value)
|
|
|
|
self.i18n(label, value)
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def walkAction(self):
|
|
|
|
'''Generates the i18n-related label.'''
|
|
|
|
if self.appyType.confirm:
|
|
|
|
label = '%s_%s_confirm' % (self.classDescr.name, self.fieldName)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(label, po.CONFIRM, nice=False)
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def walkRef(self):
|
|
|
|
'''How to generate a Ref?'''
|
|
|
|
# Add the label for the confirm message if relevant
|
|
|
|
if self.appyType.addConfirm:
|
|
|
|
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(label, po.CONFIRM, nice=False)
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def walkPod(self):
|
|
|
|
# Add i18n-specific messages
|
|
|
|
if self.appyType.askAction:
|
|
|
|
label = '%s_%s_askaction' % (self.classDescr.name, self.fieldName)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(label, po.POD_ASKACTION, nice=False)
|
2011-12-05 03:52:18 -06:00
|
|
|
# Add the POD-related fields on the Tool
|
|
|
|
self.generator.tool.addPodRelatedFields(self)
|
|
|
|
|
|
|
|
def walkList(self):
|
|
|
|
# Add i18n-specific messages
|
|
|
|
for name, field in self.appyType.fields:
|
|
|
|
label = '%s_%s_%s' % (self.classDescr.name, self.fieldName, name)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(label, name)
|
2011-12-05 03:52:18 -06:00
|
|
|
|
2012-10-03 07:44:34 -05:00
|
|
|
def walkCalendar(self):
|
|
|
|
# Add i18n-specific messages
|
2012-10-12 09:36:04 -05:00
|
|
|
eTypes = self.appyType.eventTypes
|
|
|
|
if not isinstance(eTypes, list) and not isinstance(eTypes, tuple):return
|
2012-10-03 07:44:34 -05:00
|
|
|
for et in self.appyType.eventTypes:
|
|
|
|
label = '%s_%s_event_%s' % (self.classDescr.name,self.fieldName,et)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(label, et)
|
2012-10-03 07:44:34 -05:00
|
|
|
|
2011-12-05 03:52:18 -06:00
|
|
|
def walkAppyType(self):
|
|
|
|
'''Walks into the Appy type definition and gathers data about the
|
|
|
|
i18n labels.'''
|
|
|
|
# Manage things common to all Appy types
|
[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
|
|
|
# Put an index on this field?
|
2011-12-05 08:11:29 -06:00
|
|
|
if self.appyType.indexed and (self.fieldName != 'title'):
|
2011-12-05 03:52:18 -06:00
|
|
|
self.classDescr.addIndexMethod(self)
|
|
|
|
# i18n labels
|
|
|
|
if not self.appyType.label:
|
|
|
|
# Create labels for generating them in i18n files, only if required.
|
2012-11-06 04:32:39 -06:00
|
|
|
i18nPrefix = '%s_%s' % (self.classDescr.name, self.fieldName)
|
2011-12-05 03:52:18 -06:00
|
|
|
if self.appyType.hasLabel:
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(*self.produceMessage(i18nPrefix))
|
2011-12-05 03:52:18 -06:00
|
|
|
if self.appyType.hasDescr:
|
|
|
|
descrId = i18nPrefix + '_descr'
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(*self.produceMessage(descrId,isLabel=False))
|
2011-12-05 03:52:18 -06:00
|
|
|
if self.appyType.hasHelp:
|
|
|
|
helpId = i18nPrefix + '_help'
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(*self.produceMessage(helpId, isLabel=False))
|
2011-12-05 03:52:18 -06:00
|
|
|
# Create i18n messages linked to pages and phases, only if there is more
|
|
|
|
# than one page/phase for the class.
|
|
|
|
if len(self.classDescr.getPhases()) > 1:
|
|
|
|
# Create the message for the name of the phase
|
|
|
|
phaseName = self.appyType.page.phase
|
|
|
|
msgId = '%s_phase_%s' % (self.classDescr.name, phaseName)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(msgId, phaseName)
|
2011-12-05 03:52:18 -06:00
|
|
|
if len(self.classDescr.getPages()) > 1:
|
|
|
|
# Create the message for the name of the page
|
|
|
|
pageName = self.appyType.page.name
|
|
|
|
msgId = '%s_page_%s' % (self.classDescr.name, pageName)
|
2012-11-06 04:32:39 -06:00
|
|
|
self.i18n(msgId, pageName)
|
2011-12-05 03:52:18 -06:00
|
|
|
# Create i18n messages linked to groups
|
|
|
|
group = self.appyType.group
|
|
|
|
if group and not group.label:
|
2012-11-06 04:32:39 -06:00
|
|
|
group.generateLabels(self.generator.labels, self.classDescr, set())
|
2011-12-05 03:52:18 -06:00
|
|
|
# Manage things which are specific to String types
|
|
|
|
if self.appyType.type == 'String': self.walkString()
|
|
|
|
# Manage things which are specific to Actions
|
|
|
|
elif self.appyType.type == 'Action': self.walkAction()
|
|
|
|
# Manage things which are specific to Ref types
|
|
|
|
elif self.appyType.type == 'Ref': self.walkRef()
|
|
|
|
# Manage things which are specific to Pod types
|
|
|
|
elif self.appyType.type == 'Pod': self.walkPod()
|
|
|
|
# Manage things which are specific to List types
|
|
|
|
elif self.appyType.type == 'List': self.walkList()
|
2012-10-03 07:44:34 -05:00
|
|
|
# Manage things which are specific to Calendar types
|
|
|
|
elif self.appyType.type == 'Calendar': self.walkCalendar()
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def generate(self):
|
|
|
|
'''Generates the i18n labels for this type.'''
|
|
|
|
self.walkAppyType()
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class ToolClassDescriptor(ClassDescriptor):
|
|
|
|
'''Represents the POD-specific fields that must be added to the tool.'''
|
|
|
|
def __init__(self, klass, generator):
|
|
|
|
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
|
|
|
self.modelClass = self.klass
|
|
|
|
self.predefined = True
|
|
|
|
self.customized = False
|
|
|
|
|
|
|
|
def getParents(self, allClasses=()):
|
|
|
|
res = ['Tool']
|
|
|
|
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 tool
|
|
|
|
definition. We must then add the custom tool elements in this default
|
|
|
|
Tool descriptor.'''
|
|
|
|
self.orderedAttributes += attributes
|
|
|
|
self.klass = klass
|
|
|
|
self.customized = True
|
|
|
|
|
|
|
|
def isFolder(self, klass=None): return True
|
|
|
|
def isRoot(self): return False
|
|
|
|
|
|
|
|
def addPodRelatedFields(self, fieldDescr):
|
|
|
|
'''Adds the fields needed in the Tool for configuring a Pod field.'''
|
|
|
|
className = fieldDescr.classDescr.name
|
|
|
|
# On what page and group to display those fields ?
|
2012-09-13 07:43:40 -05:00
|
|
|
pg = {'page': 'documents',
|
2011-12-05 08:11:29 -06:00
|
|
|
'group':gen.Group(fieldDescr.classDescr.klass.__name__,['50%']*2)}
|
2011-12-05 03:52:18 -06:00
|
|
|
# Add the field that will store the pod template.
|
|
|
|
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.File(**pg)
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
# Add the field that will store the output format(s)
|
|
|
|
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.String(validator=gen.Selection('getPodOutputFormats'),
|
|
|
|
multiplicity=(1,None), default=('odt',), **pg)
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
|
|
|
|
def addQueryResultColumns(self, classDescr):
|
|
|
|
'''Adds, for class p_classDescr, the attribute in the tool that allows
|
|
|
|
to select what default columns will be shown on query results.'''
|
|
|
|
className = classDescr.name
|
|
|
|
fieldName = 'resultColumnsFor%s' % className
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
|
2011-12-05 03:52:18 -06:00
|
|
|
'_appy_getAllFields*%s' % className), page='userInterface',
|
2012-08-21 12:57:23 -05:00
|
|
|
group=classDescr.klass.__name__, default=['title'])
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
|
|
|
|
def addSearchRelatedFields(self, classDescr):
|
|
|
|
'''Adds, for class p_classDescr, attributes related to the search
|
|
|
|
functionality for class p_classDescr.'''
|
|
|
|
className = classDescr.name
|
|
|
|
# Field that defines how many columns are shown on the custom search
|
|
|
|
# screen.
|
|
|
|
fieldName = 'numberOfSearchColumnsFor%s' % className
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.Integer(default=3, page='userInterface',
|
|
|
|
group=classDescr.klass.__name__)
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
# Field that allows to select, among all indexed fields, what fields
|
|
|
|
# must really be used in the search screen.
|
|
|
|
fieldName = 'searchFieldsFor%s' % className
|
|
|
|
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
|
|
|
|
condition='attrValue.indexed')]
|
2012-09-13 12:26:05 -05:00
|
|
|
if 'title' not in defaultValue: defaultValue.insert(0, 'title')
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
|
2011-12-05 03:52:18 -06:00
|
|
|
'_appy_getSearchableFields*%s' % className), default=defaultValue,
|
|
|
|
page='userInterface', group=classDescr.klass.__name__)
|
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
|
|
|
|
def addImportRelatedFields(self, classDescr):
|
|
|
|
'''Adds, for class p_classDescr, attributes related to the import
|
|
|
|
functionality for class p_classDescr.'''
|
|
|
|
className = classDescr.name
|
|
|
|
# Field that defines the path of the files to import.
|
|
|
|
fieldName = 'importPathFor%s' % className
|
|
|
|
defValue = classDescr.getCreateMean('Import').path
|
2011-12-05 08:11:29 -06:00
|
|
|
fieldType = gen.String(page='data', multiplicity=(1,1),
|
|
|
|
default=defValue,group=classDescr.klass.__name__)
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField(fieldName, fieldType)
|
|
|
|
|
|
|
|
class UserClassDescriptor(ClassDescriptor):
|
2011-12-05 08:11:29 -06:00
|
|
|
'''Appy-specific class for representing a user.'''
|
2011-12-05 03:52:18 -06:00
|
|
|
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 False
|
|
|
|
|
|
|
|
class GroupClassDescriptor(ClassDescriptor):
|
|
|
|
'''Represents the class that corresponds to the Group 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 = ['Group']
|
|
|
|
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 group
|
|
|
|
definition. We must then add the custom group elements in this
|
|
|
|
default Group descriptor.
|
|
|
|
|
|
|
|
NOTE: currently, it is not possible to define a custom Group
|
|
|
|
class.'''
|
|
|
|
self.orderedAttributes += attributes
|
|
|
|
self.klass = klass
|
|
|
|
self.customized = True
|
|
|
|
def isFolder(self, klass=None): return False
|
|
|
|
|
|
|
|
class TranslationClassDescriptor(ClassDescriptor):
|
|
|
|
'''Represents the set of translation ids for a gen-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=()): return ('Translation',)
|
2012-03-26 12:09:45 -05:00
|
|
|
def isFolder(self, klass=None): return False
|
2011-12-05 03:52:18 -06:00
|
|
|
|
|
|
|
def addLabelField(self, messageId, page):
|
|
|
|
'''Adds a Computed field that will display, in the source language, the
|
|
|
|
content of the text to translate.'''
|
2011-12-05 08:11:29 -06:00
|
|
|
field = gen.Computed(method=self.modelClass.label, plainText=False,
|
|
|
|
page=page, show=self.modelClass.show, layouts='f')
|
2011-12-05 03:52:18 -06:00
|
|
|
self.addField('%s_label' % messageId, field)
|
|
|
|
|
|
|
|
def addMessageField(self, messageId, page, i18nFiles):
|
|
|
|
'''Adds a message field corresponding to p_messageId to the Translation
|
|
|
|
class, on a given p_page. We need i18n files p_i18nFiles for
|
|
|
|
fine-tuning the String type to generate for this field (one-line?
|
|
|
|
several lines?...)'''
|
|
|
|
params = {'page':page, 'layouts':'f', 'show': self.modelClass.show}
|
|
|
|
appName = self.generator.applicationName
|
|
|
|
# Scan all messages corresponding to p_messageId from all translation
|
|
|
|
# files. We will define field length from the longer found message
|
|
|
|
# content.
|
|
|
|
maxLine = 100 # We suppose a line is 100 characters long.
|
|
|
|
width = 0
|
|
|
|
height = 0
|
|
|
|
for fileName, poFile in i18nFiles.iteritems():
|
|
|
|
if not fileName.startswith('%s-' % appName) or \
|
|
|
|
not i18nFiles[fileName].messagesDict.has_key(messageId):
|
|
|
|
# In this case this is not one of our Appy-managed translation
|
|
|
|
# files.
|
|
|
|
continue
|
|
|
|
msgContent = i18nFiles[fileName].messagesDict[messageId].msg
|
|
|
|
# Compute width
|
|
|
|
width = max(width, len(msgContent))
|
|
|
|
# Compute height (a "\n" counts for one line)
|
|
|
|
mHeight = int(len(msgContent)/maxLine) + msgContent.count('<br/>')
|
|
|
|
height = max(height, mHeight)
|
|
|
|
if height < 1:
|
|
|
|
# This is a one-line field.
|
|
|
|
params['width'] = width
|
|
|
|
else:
|
|
|
|
# This is a multi-line field, or a very-long-single-lined field
|
2011-12-05 08:11:29 -06:00
|
|
|
params['format'] = gen.String.TEXT
|
2011-12-05 03:52:18 -06:00
|
|
|
params['height'] = height
|
2011-12-05 08:11:29 -06:00
|
|
|
self.addField(messageId, gen.String(**params))
|
2012-03-26 12:09:45 -05:00
|
|
|
|
|
|
|
class PageClassDescriptor(ClassDescriptor):
|
|
|
|
'''Represents the class that corresponds to a Page.'''
|
|
|
|
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 = ['Page']
|
|
|
|
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 page
|
|
|
|
definition. We must then add the custom page elements in this
|
|
|
|
default Page descriptor.
|
|
|
|
|
|
|
|
NOTE: currently, it is not possible to define a custom Page class.'''
|
|
|
|
self.orderedAttributes += attributes
|
|
|
|
self.klass = klass
|
|
|
|
self.customized = True
|
|
|
|
def isFolder(self, klass=None): return True
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|