New translation system, that generates screens for updating translations through the web, within the configuration.
This commit is contained in:
parent
f3604624de
commit
ead9f7c2de
|
@ -1 +1 @@
|
||||||
0.6.1
|
0.6.2
|
||||||
|
|
|
@ -70,7 +70,8 @@ class Page:
|
||||||
showAttr = 'show%s' % elem.capitalize()
|
showAttr = 'show%s' % elem.capitalize()
|
||||||
# Get the value of the show attribute as identified above.
|
# Get the value of the show attribute as identified above.
|
||||||
show = getattr(self, showAttr)
|
show = getattr(self, showAttr)
|
||||||
if callable(show): show = show(obj.appy())
|
if callable(show):
|
||||||
|
show = show(obj.appy())
|
||||||
# Show value can be 'view', for example. Thanks to p_layoutType,
|
# Show value can be 'view', for example. Thanks to p_layoutType,
|
||||||
# convert show value to a real final boolean value.
|
# convert show value to a real final boolean value.
|
||||||
res = show
|
res = show
|
||||||
|
@ -407,7 +408,7 @@ class Type:
|
||||||
# permission (string) instead of assigning "True" to the following
|
# permission (string) instead of assigning "True" to the following
|
||||||
# arg(s). A named permission will be global to your whole Zope site, so
|
# arg(s). A named permission will be global to your whole Zope site, so
|
||||||
# take care to the naming convention. Typically, a named permission is
|
# take care to the naming convention. Typically, a named permission is
|
||||||
# of the form: "<yourAppName>: Write|Read xxx". If, for example, I want
|
# of the form: "<yourAppName>: Write|Read ---". If, for example, I want
|
||||||
# to define, for my application "MedicalFolder" a specific permission
|
# to define, for my application "MedicalFolder" a specific permission
|
||||||
# for a bunch of fields that can only be modified by a doctor, I can
|
# for a bunch of fields that can only be modified by a doctor, I can
|
||||||
# define a permission "MedicalFolder: Write medical information" and
|
# define a permission "MedicalFolder: Write medical information" and
|
||||||
|
@ -531,7 +532,7 @@ class Type:
|
||||||
return False
|
return False
|
||||||
# Evaluate self.show
|
# Evaluate self.show
|
||||||
if callable(self.show):
|
if callable(self.show):
|
||||||
res = self.show(obj.appy())
|
res = self.callMethod(obj, self.show)
|
||||||
else:
|
else:
|
||||||
res = self.show
|
res = self.show
|
||||||
# Take into account possible values 'view' and 'edit' for 'show' param.
|
# Take into account possible values 'view' and 'edit' for 'show' param.
|
||||||
|
@ -663,6 +664,15 @@ class Type:
|
||||||
default layouts. If None is returned, a global set of default layouts
|
default layouts. If None is returned, a global set of default layouts
|
||||||
will be used.'''
|
will be used.'''
|
||||||
|
|
||||||
|
def getInputLayouts(self):
|
||||||
|
'''Gets, as a string, the layouts as could have been specified as input
|
||||||
|
value for the Type constructor.'''
|
||||||
|
res = '{'
|
||||||
|
for k, v in self.layouts.iteritems():
|
||||||
|
res += '"%s":"%s",' % (k, v['layoutString'])
|
||||||
|
res += '}'
|
||||||
|
return res
|
||||||
|
|
||||||
def computeDefaultLayouts(self):
|
def computeDefaultLayouts(self):
|
||||||
'''This method gets the default layouts from an Appy type, or a copy
|
'''This method gets the default layouts from an Appy type, or a copy
|
||||||
from the global default field layouts when they are not available.'''
|
from the global default field layouts when they are not available.'''
|
||||||
|
@ -687,13 +697,12 @@ class Type:
|
||||||
# If there is no value, get the default value if any
|
# If there is no value, get the default value if any
|
||||||
if not self.editDefault:
|
if not self.editDefault:
|
||||||
# Return self.default, of self.default() if it is a method
|
# Return self.default, of self.default() if it is a method
|
||||||
if type(self.default) == types.FunctionType:
|
if callable(self.default):
|
||||||
appyObj = obj.appy()
|
|
||||||
try:
|
try:
|
||||||
return self.default(appyObj)
|
return self.callMethod(obj, self.default,
|
||||||
|
raiseOnError=True)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
appyObj.log('Exception while getting default value ' \
|
# Already logged.
|
||||||
'of field "%s": %s.' % (self.name, str(e)))
|
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self.default
|
return self.default
|
||||||
|
@ -839,11 +848,35 @@ class Type:
|
||||||
res.specificReadPermission = False
|
res.specificReadPermission = False
|
||||||
res.specificWritePermission = False
|
res.specificWritePermission = False
|
||||||
res.multiplicity = (0, self.multiplicity[1])
|
res.multiplicity = (0, self.multiplicity[1])
|
||||||
if type(res.validator) == types.FunctionType:
|
if callable(res.validator):
|
||||||
# We will not be able to call this function from the tool.
|
# We will not be able to call this function from the tool.
|
||||||
res.validator = None
|
res.validator = None
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def callMethod(self, obj, method, raiseOnError=False):
|
||||||
|
'''This method is used to call a p_method on p_obj. p_method is part of
|
||||||
|
this type definition (ie a default method, the method of a Computed
|
||||||
|
field, a method used for showing or not a field...). Normally, those
|
||||||
|
methods are called without any arg. But one may need, within the
|
||||||
|
method, to access the related field. This method tries to call
|
||||||
|
p_method with no arg *or* with the field arg.'''
|
||||||
|
obj = obj.appy()
|
||||||
|
try:
|
||||||
|
return method(obj)
|
||||||
|
except TypeError, te:
|
||||||
|
# Try a version of the method that would accept self as an
|
||||||
|
# additional parameter.
|
||||||
|
try:
|
||||||
|
return method(obj, self)
|
||||||
|
except Exception, e:
|
||||||
|
obj.log(Traceback.get(), type='error')
|
||||||
|
if raiseOnError: raise e
|
||||||
|
else: return str(e)
|
||||||
|
except Exception, e:
|
||||||
|
obj.log(Traceback.get(), type='error')
|
||||||
|
if raiseOnError: raise e
|
||||||
|
else: return str(e)
|
||||||
|
|
||||||
class Integer(Type):
|
class Integer(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
|
@ -1139,6 +1172,10 @@ class String(Type):
|
||||||
res = unicode(value)
|
res = unicode(value)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
res = str(value)
|
res = str(value)
|
||||||
|
# If value starts with a carriage return, add a space; else, it will
|
||||||
|
# be ignored.
|
||||||
|
if isinstance(res, basestring) and \
|
||||||
|
(res.startswith('\n') or res.startswith('\r\n')): res = ' ' + res
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getIndexValue(self, obj, forSearch=False):
|
def getIndexValue(self, obj, forSearch=False):
|
||||||
|
@ -1719,12 +1756,7 @@ class Computed(Type):
|
||||||
def getValue(self, obj):
|
def getValue(self, obj):
|
||||||
'''Computes the value instead of getting it in the database.'''
|
'''Computes the value instead of getting it in the database.'''
|
||||||
if not self.method: return
|
if not self.method: return
|
||||||
obj = obj.appy()
|
return self.callMethod(obj, self.method, raiseOnError=False)
|
||||||
try:
|
|
||||||
return self.method(obj)
|
|
||||||
except Exception, e:
|
|
||||||
obj.log(Traceback.get(), type='error')
|
|
||||||
return str(e)
|
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
if not isinstance(value, basestring): return str(value)
|
if not isinstance(value, basestring): return str(value)
|
||||||
|
@ -2106,6 +2138,11 @@ class Config:
|
||||||
# If you don't need the portlet that appy.gen has generated for your
|
# If you don't need the portlet that appy.gen has generated for your
|
||||||
# application, set the following parameter to False.
|
# application, set the following parameter to False.
|
||||||
self.showPortlet = True
|
self.showPortlet = True
|
||||||
|
# Number of translations for every page on a Translation object
|
||||||
|
self.translationsPerPage = 30
|
||||||
|
# Language that will be used as a basis for translating to other
|
||||||
|
# languages.
|
||||||
|
self.sourceLanguage = 'en'
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Special field "type" is mandatory for every class. If one class does not
|
# Special field "type" is mandatory for every class. If one class does not
|
||||||
|
|
|
@ -70,12 +70,22 @@ class ClassDescriptor(Descriptor):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getPhases(self):
|
def getPhases(self):
|
||||||
'''Gets the phases defined on fields of this class.'''
|
'''Lazy-gets the phases defined on fields of this class.'''
|
||||||
res = []
|
if not hasattr(self, 'phases') or (self.phases == None):
|
||||||
for fieldName, appyType, klass in self.getOrderedAppyAttributes():
|
self.phases = []
|
||||||
if appyType.page.phase not in res:
|
for fieldName, appyType, klass in self.getOrderedAppyAttributes():
|
||||||
res.append(appyType.page.phase)
|
if appyType.page.phase in self.phases: continue
|
||||||
return res
|
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
|
||||||
|
|
||||||
class WorkflowDescriptor(Descriptor):
|
class WorkflowDescriptor(Descriptor):
|
||||||
'''This class gives information about an Appy workflow.'''
|
'''This class gives information about an Appy workflow.'''
|
||||||
|
|
|
@ -146,8 +146,6 @@ class FieldDescriptor:
|
||||||
if self.appyType.indexed and \
|
if self.appyType.indexed and \
|
||||||
(self.fieldName not in ('title', 'description')):
|
(self.fieldName not in ('title', 'description')):
|
||||||
self.classDescr.addIndexMethod(self)
|
self.classDescr.addIndexMethod(self)
|
||||||
# - searchable ? TODO
|
|
||||||
#if self.appyType.searchable: self.fieldParams['searchable'] = True
|
|
||||||
# i18n labels
|
# i18n labels
|
||||||
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
|
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
|
||||||
# Create labels for generating them in i18n files.
|
# Create labels for generating them in i18n files.
|
||||||
|
@ -160,17 +158,20 @@ class FieldDescriptor:
|
||||||
if self.appyType.hasHelp:
|
if self.appyType.hasHelp:
|
||||||
helpId = i18nPrefix + '_help'
|
helpId = i18nPrefix + '_help'
|
||||||
messages.append(self.produceMessage(helpId, isLabel=False))
|
messages.append(self.produceMessage(helpId, isLabel=False))
|
||||||
# Create i18n messages linked to pages and phases
|
# Create i18n messages linked to pages and phases, only if there is more
|
||||||
messages = self.generator.labels
|
# than one page/phase for the class.
|
||||||
pageMsgId = '%s_page_%s' % (self.classDescr.name,
|
ppMsgs = []
|
||||||
self.appyType.page.name)
|
if len(self.classDescr.getPhases()) > 1:
|
||||||
phaseMsgId = '%s_phase_%s' % (self.classDescr.name,
|
# Create the message for the name of the phase
|
||||||
self.appyType.page.phase)
|
phaseName = self.appyType.page.phase
|
||||||
pagePoMsg = PoMessage(pageMsgId, '',
|
msgId = '%s_phase_%s' % (self.classDescr.name, phaseName)
|
||||||
produceNiceMessage(self.appyType.page.name))
|
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(phaseName)))
|
||||||
phasePoMsg = PoMessage(phaseMsgId, '',
|
if len(self.classDescr.getPages()) > 1:
|
||||||
produceNiceMessage(self.appyType.page.phase))
|
# Create the message for the name of the page
|
||||||
for poMsg in (pagePoMsg, phasePoMsg):
|
pageName = self.appyType.page.name
|
||||||
|
msgId = '%s_page_%s' % (self.classDescr.name, pageName)
|
||||||
|
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(pageName)))
|
||||||
|
for poMsg in ppMsgs:
|
||||||
if poMsg not in messages:
|
if poMsg not in messages:
|
||||||
messages.append(poMsg)
|
messages.append(poMsg)
|
||||||
self.classDescr.labelsToPropagate.append(poMsg)
|
self.classDescr.labelsToPropagate.append(poMsg)
|
||||||
|
@ -237,6 +238,9 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
self.name = getClassName(self.klass, generator.applicationName)
|
self.name = getClassName(self.klass, generator.applicationName)
|
||||||
self.predefined = False
|
self.predefined = False
|
||||||
self.customized = False
|
self.customized = False
|
||||||
|
# Phase and page names will be calculated later, when first required.
|
||||||
|
self.phases = None
|
||||||
|
self.pages = None
|
||||||
|
|
||||||
def getParents(self, allClasses):
|
def getParents(self, allClasses):
|
||||||
parentWrapper = 'AbstractWrapper'
|
parentWrapper = 'AbstractWrapper'
|
||||||
|
@ -365,11 +369,16 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
'self)\n' % n
|
'self)\n' % n
|
||||||
self.methods = m
|
self.methods = m
|
||||||
|
|
||||||
|
def addField(self, fieldName, fieldType):
|
||||||
|
'''Adds a new field to the Tool.'''
|
||||||
|
exec "self.modelClass.%s = fieldType" % fieldName
|
||||||
|
self.modelClass._appy_attributes.append(fieldName)
|
||||||
|
self.orderedAttributes.append(fieldName)
|
||||||
|
|
||||||
class ToolClassDescriptor(ClassDescriptor):
|
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.attributesByClass = klass._appy_classes
|
|
||||||
self.modelClass = self.klass
|
self.modelClass = self.klass
|
||||||
self.predefined = True
|
self.predefined = True
|
||||||
self.customized = False
|
self.customized = False
|
||||||
|
@ -393,13 +402,6 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
def generateSchema(self):
|
def generateSchema(self):
|
||||||
ClassDescriptor.generateSchema(self, configClass=True)
|
ClassDescriptor.generateSchema(self, configClass=True)
|
||||||
|
|
||||||
def addField(self, fieldName, fieldType, classDescr):
|
|
||||||
'''Adds a new field to the Tool.'''
|
|
||||||
exec "self.modelClass.%s = fieldType" % fieldName
|
|
||||||
self.modelClass._appy_attributes.append(fieldName)
|
|
||||||
self.orderedAttributes.append(fieldName)
|
|
||||||
self.modelClass._appy_classes[fieldName] = classDescr.name
|
|
||||||
|
|
||||||
def addOptionalField(self, fieldDescr):
|
def addOptionalField(self, fieldDescr):
|
||||||
className = fieldDescr.classDescr.name
|
className = fieldDescr.classDescr.name
|
||||||
fieldName = 'optionalFieldsFor%s' % className
|
fieldName = 'optionalFieldsFor%s' % className
|
||||||
|
@ -407,7 +409,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
if not fieldType:
|
if not fieldType:
|
||||||
fieldType = String(multiplicity=(0,None))
|
fieldType = String(multiplicity=(0,None))
|
||||||
fieldType.validator = []
|
fieldType.validator = []
|
||||||
self.addField(fieldName, fieldType, fieldDescr.classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
fieldType.validator.append(fieldDescr.fieldName)
|
fieldType.validator.append(fieldDescr.fieldName)
|
||||||
fieldType.page.name = 'data'
|
fieldType.page.name = 'data'
|
||||||
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
||||||
|
@ -416,7 +418,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
className = fieldDescr.classDescr.name
|
className = fieldDescr.classDescr.name
|
||||||
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
|
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
|
||||||
fieldType = fieldDescr.appyType.clone()
|
fieldType = fieldDescr.appyType.clone()
|
||||||
self.addField(fieldName, fieldType, fieldDescr.classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
fieldType.page.name = 'data'
|
fieldType.page.name = 'data'
|
||||||
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
||||||
|
|
||||||
|
@ -429,12 +431,12 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
# Add the field that will store the pod template.
|
# Add the field that will store the pod template.
|
||||||
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
|
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
|
||||||
fieldType = File(**pg)
|
fieldType = File(**pg)
|
||||||
self.addField(fieldName, fieldType, fieldDescr.classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
# Add the field that will store the output format(s)
|
# Add the field that will store the output format(s)
|
||||||
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
|
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
|
||||||
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
|
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
|
||||||
multiplicity=(1,None), default=('odt',), **pg)
|
multiplicity=(1,None), default=('odt',), **pg)
|
||||||
self.addField(fieldName, fieldType, fieldDescr.classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
|
|
||||||
def addQueryResultColumns(self, classDescr):
|
def addQueryResultColumns(self, classDescr):
|
||||||
'''Adds, for class p_classDescr, the attribute in the tool that allows
|
'''Adds, for class p_classDescr, the attribute in the tool that allows
|
||||||
|
@ -444,7 +446,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldType = String(multiplicity=(0,None), validator=Selection(
|
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||||
'_appy_getAllFields*%s' % className), page='userInterface',
|
'_appy_getAllFields*%s' % className), page='userInterface',
|
||||||
group=classDescr.klass.__name__)
|
group=classDescr.klass.__name__)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
|
|
||||||
def addSearchRelatedFields(self, classDescr):
|
def addSearchRelatedFields(self, classDescr):
|
||||||
'''Adds, for class p_classDescr, attributes related to the search
|
'''Adds, for class p_classDescr, attributes related to the search
|
||||||
|
@ -455,13 +457,13 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldName = 'enableAdvancedSearchFor%s' % className
|
fieldName = 'enableAdvancedSearchFor%s' % className
|
||||||
fieldType = Boolean(default=True, page='userInterface',
|
fieldType = Boolean(default=True, page='userInterface',
|
||||||
group=classDescr.klass.__name__)
|
group=classDescr.klass.__name__)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
# Field that defines how many columns are shown on the custom search
|
# Field that defines how many columns are shown on the custom search
|
||||||
# screen.
|
# screen.
|
||||||
fieldName = 'numberOfSearchColumnsFor%s' % className
|
fieldName = 'numberOfSearchColumnsFor%s' % className
|
||||||
fieldType = Integer(default=3, page='userInterface',
|
fieldType = Integer(default=3, page='userInterface',
|
||||||
group=classDescr.klass.__name__)
|
group=classDescr.klass.__name__)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
# Field that allows to select, among all indexed fields, what fields
|
# Field that allows to select, among all indexed fields, what fields
|
||||||
# must really be used in the search screen.
|
# must really be used in the search screen.
|
||||||
fieldName = 'searchFieldsFor%s' % className
|
fieldName = 'searchFieldsFor%s' % className
|
||||||
|
@ -470,7 +472,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldType = String(multiplicity=(0,None), validator=Selection(
|
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||||
'_appy_getSearchableFields*%s' % className), default=defaultValue,
|
'_appy_getSearchableFields*%s' % className), default=defaultValue,
|
||||||
page='userInterface', group=classDescr.klass.__name__)
|
page='userInterface', group=classDescr.klass.__name__)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
|
|
||||||
def addImportRelatedFields(self, classDescr):
|
def addImportRelatedFields(self, classDescr):
|
||||||
'''Adds, for class p_classDescr, attributes related to the import
|
'''Adds, for class p_classDescr, attributes related to the import
|
||||||
|
@ -481,7 +483,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
defValue = classDescr.getCreateMean('Import').path
|
defValue = classDescr.getCreateMean('Import').path
|
||||||
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
|
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
|
||||||
group=classDescr.klass.__name__)
|
group=classDescr.klass.__name__)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
|
|
||||||
def addWorkflowFields(self, classDescr):
|
def addWorkflowFields(self, classDescr):
|
||||||
'''Adds, for a given p_classDescr, the workflow-related fields.'''
|
'''Adds, for a given p_classDescr, the workflow-related fields.'''
|
||||||
|
@ -495,12 +497,12 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldName = 'showWorkflowFor%s' % className
|
fieldName = 'showWorkflowFor%s' % className
|
||||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||||
group=groupName)
|
group=groupName)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
# Adds the boolean field for showing or not the field "enter comments".
|
# Adds the boolean field for showing or not the field "enter comments".
|
||||||
fieldName = 'showWorkflowCommentFieldFor%s' % className
|
fieldName = 'showWorkflowCommentFieldFor%s' % className
|
||||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||||
group=groupName)
|
group=groupName)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
# Adds the boolean field for showing all states in current state or not.
|
# Adds the boolean field for showing all states in current state or not.
|
||||||
# If this boolean is True but the current phase counts only one state,
|
# If this boolean is True but the current phase counts only one state,
|
||||||
# we will not show the state at all: the fact of knowing in what phase
|
# we will not show the state at all: the fact of knowing in what phase
|
||||||
|
@ -512,7 +514,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldName = 'showAllStatesInPhaseFor%s' % className
|
fieldName = 'showAllStatesInPhaseFor%s' % className
|
||||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||||
group=groupName)
|
group=groupName)
|
||||||
self.addField(fieldName, fieldType, classDescr)
|
self.addField(fieldName, fieldType)
|
||||||
|
|
||||||
class UserClassDescriptor(ClassDescriptor):
|
class UserClassDescriptor(ClassDescriptor):
|
||||||
'''Represents an Archetypes-compliant class that corresponds to the User
|
'''Represents an Archetypes-compliant class that corresponds to the User
|
||||||
|
@ -534,10 +536,61 @@ class UserClassDescriptor(ClassDescriptor):
|
||||||
self.orderedAttributes += attributes
|
self.orderedAttributes += attributes
|
||||||
self.klass = klass
|
self.klass = klass
|
||||||
self.customized = True
|
self.customized = True
|
||||||
def isFolder(self, klass=None): return True
|
def isFolder(self, klass=None): return False
|
||||||
def generateSchema(self):
|
def generateSchema(self):
|
||||||
ClassDescriptor.generateSchema(self, configClass=True)
|
ClassDescriptor.generateSchema(self, configClass=True)
|
||||||
|
|
||||||
|
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',)
|
||||||
|
|
||||||
|
def generateSchema(self):
|
||||||
|
ClassDescriptor.generateSchema(self, configClass=True)
|
||||||
|
|
||||||
|
def addLabelField(self, messageId, page):
|
||||||
|
'''Adds a Computed field that will display, in the source language, the
|
||||||
|
content of the text to translate.'''
|
||||||
|
field = Computed(method=self.modelClass.computeLabel, plainText=False,
|
||||||
|
page=page, show=self.modelClass.showField, layouts='f')
|
||||||
|
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.showField}
|
||||||
|
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): 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
|
||||||
|
params['format'] = String.TEXT
|
||||||
|
params['height'] = height
|
||||||
|
self.addField(messageId, String(**params))
|
||||||
|
|
||||||
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 ?
|
||||||
|
|
|
@ -9,8 +9,9 @@ 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 appy.gen.utils import getClassName
|
from appy.gen.utils import getClassName
|
||||||
from descriptors import ClassDescriptor, WorkflowDescriptor, \
|
from descriptors import ClassDescriptor, WorkflowDescriptor, \
|
||||||
ToolClassDescriptor, UserClassDescriptor
|
ToolClassDescriptor, UserClassDescriptor, \
|
||||||
from model import ModelClass, User, Tool
|
TranslationClassDescriptor
|
||||||
|
from model import ModelClass, User, Tool, Translation
|
||||||
|
|
||||||
# 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 = '''
|
||||||
|
@ -32,16 +33,14 @@ class Generator(AbstractGenerator):
|
||||||
# Set our own Descriptor classes
|
# Set our own Descriptor classes
|
||||||
self.descriptorClasses['class'] = ClassDescriptor
|
self.descriptorClasses['class'] = ClassDescriptor
|
||||||
self.descriptorClasses['workflow'] = WorkflowDescriptor
|
self.descriptorClasses['workflow'] = WorkflowDescriptor
|
||||||
# Create our own Tool and User instances
|
# Create our own Tool, User and Translation instances
|
||||||
self.tool = ToolClassDescriptor(Tool, self)
|
self.tool = ToolClassDescriptor(Tool, self)
|
||||||
self.user = UserClassDescriptor(User, self)
|
self.user = UserClassDescriptor(User, self)
|
||||||
|
self.translation = TranslationClassDescriptor(Translation, self)
|
||||||
# i18n labels to generate
|
# i18n labels to generate
|
||||||
self.labels = [] # i18n labels
|
self.labels = [] # i18n labels
|
||||||
self.toolName = '%sTool' % self.applicationName
|
|
||||||
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
||||||
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.skinsFolder = 'skins/%s' % self.applicationName
|
self.skinsFolder = 'skins/%s' % self.applicationName
|
||||||
# The following dict, pre-filled in the abstract generator, contains a
|
# The following dict, pre-filled in the abstract generator, contains a
|
||||||
# series of replacements that need to be applied to file templates to
|
# series of replacements that need to be applied to file templates to
|
||||||
|
@ -49,10 +48,8 @@ class Generator(AbstractGenerator):
|
||||||
commonMethods = COMMON_METHODS % \
|
commonMethods = COMMON_METHODS % \
|
||||||
(self.toolInstanceName, self.applicationName)
|
(self.toolInstanceName, self.applicationName)
|
||||||
self.repls.update(
|
self.repls.update(
|
||||||
{'toolName': self.toolName, 'portletName': self.portletName,
|
{'toolInstanceName': self.toolInstanceName,
|
||||||
'queryName': self.queryName, 'userName': self.userName,
|
'commonMethods': commonMethods})
|
||||||
'toolInstanceName': self.toolInstanceName,
|
|
||||||
'commonMethods': commonMethods})
|
|
||||||
self.referers = {}
|
self.referers = {}
|
||||||
|
|
||||||
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
|
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
|
||||||
|
@ -150,10 +147,8 @@ class Generator(AbstractGenerator):
|
||||||
niceDefault=True))
|
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.generateInit()
|
self.generateInit()
|
||||||
self.generateWorkflows()
|
self.generateWorkflows()
|
||||||
self.generateWrappers()
|
|
||||||
self.generateTests()
|
self.generateTests()
|
||||||
if self.config.frontPage:
|
if self.config.frontPage:
|
||||||
self.generateFrontPage()
|
self.generateFrontPage()
|
||||||
|
@ -198,11 +193,22 @@ class Generator(AbstractGenerator):
|
||||||
fullName = os.path.join(self.outputFolder, 'i18n/%s' % potFileName)
|
fullName = os.path.join(self.outputFolder, 'i18n/%s' % potFileName)
|
||||||
potFile = PoFile(fullName)
|
potFile = PoFile(fullName)
|
||||||
self.i18nFiles[potFileName] = potFile
|
self.i18nFiles[potFileName] = potFile
|
||||||
|
# We update the POT file with our list of automatically managed labels.
|
||||||
removedLabels = potFile.update(self.labels, self.options.i18nClean,
|
removedLabels = potFile.update(self.labels, self.options.i18nClean,
|
||||||
not self.options.i18nSort)
|
not self.options.i18nSort)
|
||||||
if removedLabels:
|
if removedLabels:
|
||||||
print 'Warning: %d messages were removed from translation ' \
|
print 'Warning: %d messages were removed from translation ' \
|
||||||
'files: %s' % (len(removedLabels), str(removedLabels))
|
'files: %s' % (len(removedLabels), str(removedLabels))
|
||||||
|
# Before generating the POT file, we still need to add one label for
|
||||||
|
# every page for the Translation class. We've not done it yet because
|
||||||
|
# the number of pages depends on the total number of labels in the POT
|
||||||
|
# file.
|
||||||
|
pageLabels = []
|
||||||
|
nbOfPages = int(len(potFile.messages)/self.config.translationsPerPage)+1
|
||||||
|
for i in range(nbOfPages):
|
||||||
|
msgId = '%s_page_%d' % (self.translation.name, i+2)
|
||||||
|
pageLabels.append(msg(msgId, '', 'Page %d' % (i+2)))
|
||||||
|
potFile.update(pageLabels, keepExistingOrder=False)
|
||||||
potFile.generate()
|
potFile.generate()
|
||||||
# Generate i18n po files
|
# Generate i18n po files
|
||||||
for language in self.config.languages:
|
for language in self.config.languages:
|
||||||
|
@ -219,10 +225,27 @@ class Generator(AbstractGenerator):
|
||||||
poFile.update(potFile.messages, self.options.i18nClean,
|
poFile.update(potFile.messages, self.options.i18nClean,
|
||||||
not self.options.i18nSort)
|
not self.options.i18nSort)
|
||||||
poFile.generate()
|
poFile.generate()
|
||||||
|
# Generate corresponding fields on the Translation class
|
||||||
|
page = 'main'
|
||||||
|
i = 0
|
||||||
|
for message in potFile.messages:
|
||||||
|
i += 1
|
||||||
|
# A computed field is used for displaying the text to translate.
|
||||||
|
self.translation.addLabelField(message.id, page)
|
||||||
|
# A String field will hold the translation in itself.
|
||||||
|
self.translation.addMessageField(message.id, page, self.i18nFiles)
|
||||||
|
if (i % self.config.translationsPerPage) == 0:
|
||||||
|
# A new page must be defined.
|
||||||
|
if page == 'main':
|
||||||
|
page = '2'
|
||||||
|
else:
|
||||||
|
page = str(int(page)+1)
|
||||||
# Generate i18n po files for other potential files
|
# Generate i18n po files for other potential files
|
||||||
for poFile in self.i18nFiles.itervalues():
|
for poFile in self.i18nFiles.itervalues():
|
||||||
if not poFile.generated:
|
if not poFile.generated:
|
||||||
poFile.generate()
|
poFile.generate()
|
||||||
|
self.generateWrappers()
|
||||||
|
self.generateConfig()
|
||||||
|
|
||||||
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
|
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
|
||||||
'''Produces a list of all the roles used within all workflows and
|
'''Produces a list of all the roles used within all workflows and
|
||||||
|
@ -283,10 +306,14 @@ class Generator(AbstractGenerator):
|
||||||
|
|
||||||
def generateConfig(self):
|
def generateConfig(self):
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
|
# Get some lists of classes
|
||||||
|
classes = self.getClasses()
|
||||||
|
classesWithCustom = self.getClasses(include='custom')
|
||||||
|
classesButTool = self.getClasses(include='allButTool')
|
||||||
|
classesAll = self.getClasses(include='all')
|
||||||
# Compute imports
|
# Compute imports
|
||||||
imports = ['import %s' % self.applicationName]
|
imports = ['import %s' % self.applicationName]
|
||||||
classDescrs = self.getClasses(include='custom')
|
for classDescr in (classesWithCustom + self.workflows):
|
||||||
for classDescr in (classDescrs + self.workflows):
|
|
||||||
theImport = 'import %s' % classDescr.klass.__module__
|
theImport = 'import %s' % classDescr.klass.__module__
|
||||||
if theImport not in imports:
|
if theImport not in imports:
|
||||||
imports.append(theImport)
|
imports.append(theImport)
|
||||||
|
@ -296,31 +323,24 @@ class Generator(AbstractGenerator):
|
||||||
['"%s"' % r for r in self.config.defaultCreators])
|
['"%s"' % r for r in self.config.defaultCreators])
|
||||||
# Compute list of add permissions
|
# Compute list of add permissions
|
||||||
addPermissions = ''
|
addPermissions = ''
|
||||||
for classDescr in self.getClasses(include='allButTool'):
|
for classDescr in classesButTool:
|
||||||
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
||||||
self.applicationName, classDescr.name)
|
self.applicationName, classDescr.name)
|
||||||
repls['addPermissions'] = addPermissions
|
repls['addPermissions'] = addPermissions
|
||||||
# Compute root classes
|
# Compute root classes
|
||||||
rootClasses = ''
|
repls['rootClasses'] = ','.join(["'%s'" % c.name \
|
||||||
for classDescr in self.getClasses(include='allButTool'):
|
for c in classesButTool if c.isRoot()])
|
||||||
if classDescr.isRoot():
|
|
||||||
rootClasses += "'%s'," % classDescr.name
|
|
||||||
repls['rootClasses'] = rootClasses
|
|
||||||
# Compute list of class definitions
|
# Compute list of class definitions
|
||||||
appClasses = []
|
repls['appClasses'] = ','.join(['%s.%s' % (c.klass.__module__, \
|
||||||
for classDescr in self.classes:
|
c.klass.__name__) for c in classes])
|
||||||
k = classDescr.klass
|
|
||||||
appClasses.append('%s.%s' % (k.__module__, k.__name__))
|
|
||||||
repls['appClasses'] = "[%s]" % ','.join(appClasses)
|
|
||||||
# Compute lists of class names
|
# Compute lists of class names
|
||||||
allClassNames = '"%s",' % self.userName
|
repls['appClassNames'] = ','.join(['"%s"' % c.name \
|
||||||
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
|
for c in classes])
|
||||||
allClassNames += appClassNames
|
repls['allClassNames'] = ','.join(['"%s"' % c.name \
|
||||||
repls['allClassNames'] = allClassNames
|
for c in classesButTool])
|
||||||
repls['appClassNames'] = appClassNames
|
|
||||||
# Compute classes whose instances must not be catalogued.
|
# Compute classes whose instances must not be catalogued.
|
||||||
catalogMap = ''
|
catalogMap = ''
|
||||||
blackClasses = [self.toolName]
|
blackClasses = [self.tool.name]
|
||||||
for blackClass in blackClasses:
|
for blackClass in blackClasses:
|
||||||
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
||||||
catalogMap += "catalogMap['%s']['black'] = " \
|
catalogMap += "catalogMap['%s']['black'] = " \
|
||||||
|
@ -328,7 +348,7 @@ class Generator(AbstractGenerator):
|
||||||
repls['catalogMap'] = catalogMap
|
repls['catalogMap'] = catalogMap
|
||||||
# Compute workflows
|
# Compute workflows
|
||||||
workflows = ''
|
workflows = ''
|
||||||
for classDescr in self.getClasses(include='all'):
|
for classDescr in classesAll:
|
||||||
if hasattr(classDescr.klass, 'workflow'):
|
if hasattr(classDescr.klass, 'workflow'):
|
||||||
wfName = WorkflowDescriptor.getWorkflowName(
|
wfName = WorkflowDescriptor.getWorkflowName(
|
||||||
classDescr.klass.workflow)
|
classDescr.klass.workflow)
|
||||||
|
@ -357,7 +377,7 @@ class Generator(AbstractGenerator):
|
||||||
# inherited included) for every Appy class.
|
# inherited included) for every Appy class.
|
||||||
attributes = []
|
attributes = []
|
||||||
attributesDict = []
|
attributesDict = []
|
||||||
for classDescr in self.getClasses(include='all'):
|
for classDescr in classesAll:
|
||||||
titleFound = False
|
titleFound = False
|
||||||
attrs = []
|
attrs = []
|
||||||
attrNames = []
|
attrNames = []
|
||||||
|
@ -402,8 +422,8 @@ class Generator(AbstractGenerator):
|
||||||
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
|
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
|
||||||
repls['languageSelector'] = self.config.languageSelector
|
repls['languageSelector'] = self.config.languageSelector
|
||||||
repls['minimalistPlone'] = self.config.minimalistPlone
|
repls['minimalistPlone'] = self.config.minimalistPlone
|
||||||
|
|
||||||
repls['appFrontPage'] = bool(self.config.frontPage)
|
repls['appFrontPage'] = bool(self.config.frontPage)
|
||||||
|
repls['sourceLanguage'] = self.config.sourceLanguage
|
||||||
self.copyFile('config.py', repls)
|
self.copyFile('config.py', repls)
|
||||||
|
|
||||||
def generateInit(self):
|
def generateInit(self):
|
||||||
|
@ -470,21 +490,24 @@ class Generator(AbstractGenerator):
|
||||||
|
|
||||||
def getClasses(self, include=None):
|
def getClasses(self, include=None):
|
||||||
'''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:
|
||||||
for the config-related classes (tool, user, etc); if p_include is
|
* "all" it includes the descriptors for the config-related
|
||||||
"allButTool", it includes the same descriptors, the tool excepted;
|
classes (tool, user, translation)
|
||||||
if p_include is "custom", it includes descriptors for the
|
* "allButTool" it includes the same descriptors, the tool excepted
|
||||||
config-related classes for which the user has created a sub-class.'''
|
* "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.user]
|
configClasses = [self.tool, self.user, self.translation]
|
||||||
if include == 'all':
|
if include == 'all':
|
||||||
res += configClasses
|
res += configClasses
|
||||||
elif include == 'allButTool':
|
elif include == 'allButTool':
|
||||||
res += configClasses[1:]
|
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]
|
||||||
|
elif include == 'predefined':
|
||||||
|
res = configClasses
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getClassesInOrder(self, allClasses):
|
def getClassesInOrder(self, allClasses):
|
||||||
|
@ -569,8 +592,9 @@ class Generator(AbstractGenerator):
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
repls['imports'] = '\n'.join(imports)
|
repls['imports'] = '\n'.join(imports)
|
||||||
repls['wrappers'] = '\n'.join(wrappers)
|
repls['wrappers'] = '\n'.join(wrappers)
|
||||||
repls['toolBody'] = Tool._appy_getBody()
|
for klass in self.getClasses(include='predefined'):
|
||||||
repls['userBody'] = User._appy_getBody()
|
modelClass = klass.modelClass
|
||||||
|
repls['%s' % modelClass.__name__] = modelClass._appy_getBody()
|
||||||
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
|
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
|
||||||
|
|
||||||
def generateTests(self):
|
def generateTests(self):
|
||||||
|
@ -608,24 +632,31 @@ class Generator(AbstractGenerator):
|
||||||
Msg = PoMessage
|
Msg = PoMessage
|
||||||
# Create Tool-related i18n-related messages
|
# Create Tool-related i18n-related messages
|
||||||
self.labels += [
|
self.labels += [
|
||||||
Msg(self.toolName, '', Msg.CONFIG % self.applicationName),
|
Msg(self.tool.name, '', Msg.CONFIG % self.applicationName),
|
||||||
Msg('%s_edit_descr' % self.toolName, '', ' ')]
|
Msg('%s_edit_descr' % self.tool.name, '', ' ')]
|
||||||
|
|
||||||
# Tune the Ref field between Tool and User
|
# Tune the Ref field between Tool and User
|
||||||
Tool.users.klass = User
|
Tool.users.klass = User
|
||||||
if self.user.customized:
|
if self.user.customized:
|
||||||
Tool.users.klass = self.user.klass
|
Tool.users.klass = self.user.klass
|
||||||
|
|
||||||
# Generate the User class
|
# Generate the Tool-related classes (User, Translation)
|
||||||
self.user.generateSchema()
|
for klass in (self.user, self.translation):
|
||||||
self.labels += [ Msg(self.userName, '', Msg.USER),
|
klassType = klass.name[len(self.applicationName):]
|
||||||
Msg('%s_edit_descr' % self.userName, '', ' '),
|
klass.generateSchema()
|
||||||
Msg('%s_plural' % self.userName, '',self.userName+'s')]
|
self.labels += [ Msg(klass.name, '', klassType),
|
||||||
repls = self.repls.copy()
|
Msg('%s_edit_descr' % klass.name, '', ' '),
|
||||||
repls['fields'] = self.user.schema
|
Msg('%s_plural' % klass.name,'', klass.name+'s')]
|
||||||
repls['methods'] = self.user.methods
|
repls = self.repls.copy()
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
|
repls.update({'fields': klass.schema, 'methods': klass.methods,
|
||||||
self.copyFile('UserTemplate.py', repls,destName='%s.py' % self.userName)
|
'genClassName': klass.name, 'imports': '','baseMixin':'BaseMixin',
|
||||||
|
'baseSchema': 'BaseSchema', 'global_allow': 1,
|
||||||
|
'parents': 'BaseMixin, BaseContent', 'static': '',
|
||||||
|
'classDoc': 'User class for %s' % self.applicationName,
|
||||||
|
'implements': "(getattr(BaseContent,'__implements__',()),)",
|
||||||
|
'register': "registerType(%s, '%s')" % (klass.name,
|
||||||
|
self.applicationName)})
|
||||||
|
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
|
||||||
|
|
||||||
# Before generating the Tool class, finalize it with query result
|
# Before generating the Tool class, finalize it with query result
|
||||||
# columns, with fields to propagate, workflow-related fields.
|
# columns, with fields to propagate, workflow-related fields.
|
||||||
|
@ -634,7 +665,7 @@ class Generator(AbstractGenerator):
|
||||||
for childDescr in classDescr.getChildren():
|
for childDescr in classDescr.getChildren():
|
||||||
childFieldName = fieldName % childDescr.name
|
childFieldName = fieldName % childDescr.name
|
||||||
fieldType.group = childDescr.klass.__name__
|
fieldType.group = childDescr.klass.__name__
|
||||||
self.tool.addField(childFieldName, fieldType, childDescr)
|
self.tool.addField(childFieldName, fieldType)
|
||||||
if classDescr.isRoot():
|
if classDescr.isRoot():
|
||||||
# We must be able to configure query results from the tool.
|
# We must be able to configure query results from the tool.
|
||||||
self.tool.addQueryResultColumns(classDescr)
|
self.tool.addQueryResultColumns(classDescr)
|
||||||
|
@ -648,11 +679,22 @@ class Generator(AbstractGenerator):
|
||||||
|
|
||||||
# Generate the Tool class
|
# Generate the Tool class
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
repls['metaTypes'] = [c.name for c in self.classes]
|
repls.update({'fields': self.tool.schema, 'methods': self.tool.methods,
|
||||||
repls['fields'] = self.tool.schema
|
'genClassName': self.tool.name, 'imports':'', 'baseMixin':'ToolMixin',
|
||||||
repls['methods'] = self.tool.methods
|
'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0,
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.tool.name
|
'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder',
|
||||||
self.copyFile('ToolTemplate.py', repls, destName='%s.py'% self.toolName)
|
'classDoc': 'Tool class for %s' % self.applicationName,
|
||||||
|
'implements': "(getattr(UniqueObject,'__implements__',()),) + " \
|
||||||
|
"(getattr(OrderedBaseFolder,'__implements__',()),)",
|
||||||
|
'register': "registerType(%s, '%s')" % (self.tool.name,
|
||||||
|
self.applicationName),
|
||||||
|
'static': "left_slots = ['here/portlet_prefs/macros/portlet']\n " \
|
||||||
|
"right_slots = []\n " \
|
||||||
|
"def __init__(self, id=None):\n " \
|
||||||
|
" OrderedBaseFolder.__init__(self, '%s')\n " \
|
||||||
|
" self.setTitle('%s')\n" % (self.toolInstanceName,
|
||||||
|
self.applicationName)})
|
||||||
|
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
|
||||||
|
|
||||||
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
|
||||||
|
@ -694,11 +736,11 @@ class Generator(AbstractGenerator):
|
||||||
classDescr.generateSchema()
|
classDescr.generateSchema()
|
||||||
repls.update({
|
repls.update({
|
||||||
'imports': '\n'.join(imports), 'parents': parents,
|
'imports': '\n'.join(imports), 'parents': parents,
|
||||||
'className': classDescr.klass.__name__,
|
'className': classDescr.klass.__name__, 'global_allow': 1,
|
||||||
'genClassName': classDescr.name,
|
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
|
||||||
'classDoc': classDoc, 'applicationName': self.applicationName,
|
'classDoc': classDoc, 'applicationName': self.applicationName,
|
||||||
'fields': classDescr.schema, 'methods': classDescr.methods,
|
'fields': classDescr.schema, 'methods': classDescr.methods,
|
||||||
'implements': implements, 'baseSchema': baseSchema,
|
'implements': implements, 'baseSchema': baseSchema, 'static': '',
|
||||||
'register': register, 'toolInstanceName': self.toolInstanceName})
|
'register': register, 'toolInstanceName': self.toolInstanceName})
|
||||||
fileName = '%s.py' % classDescr.name
|
fileName = '%s.py' % classDescr.name
|
||||||
# Create i18n labels (class name, description and plural form)
|
# Create i18n labels (class name, description and plural form)
|
||||||
|
@ -726,7 +768,7 @@ class Generator(AbstractGenerator):
|
||||||
if poMsg not in self.labels:
|
if poMsg not in self.labels:
|
||||||
self.labels.append(poMsg)
|
self.labels.append(poMsg)
|
||||||
# Generate the resulting Archetypes class and schema.
|
# Generate the resulting Archetypes class and schema.
|
||||||
self.copyFile('ArchetypesTemplate.py', repls, destName=fileName)
|
self.copyFile('Class.py', repls, destName=fileName)
|
||||||
|
|
||||||
def generateWorkflow(self, wfDescr):
|
def generateWorkflow(self, wfDescr):
|
||||||
'''This method does not generate the workflow definition, which is done
|
'''This method does not generate the workflow definition, which is done
|
||||||
|
|
|
@ -7,8 +7,10 @@ from StringIO import StringIO
|
||||||
from sets import Set
|
from sets import Set
|
||||||
import appy
|
import appy
|
||||||
from appy.gen import Type, Ref
|
from appy.gen import Type, Ref
|
||||||
|
from appy.gen.po import PoParser
|
||||||
from appy.gen.utils import produceNiceMessage
|
from appy.gen.utils import produceNiceMessage
|
||||||
from appy.gen.plone25.utils import updateRolesForPermission
|
from appy.gen.plone25.utils import updateRolesForPermission
|
||||||
|
from appy.shared.data import languages
|
||||||
|
|
||||||
class ZCTextIndexInfo:
|
class ZCTextIndexInfo:
|
||||||
'''Silly class used for storing information about a ZCTextIndex.'''
|
'''Silly class used for storing information about a ZCTextIndex.'''
|
||||||
|
@ -313,6 +315,38 @@ class PloneInstaller:
|
||||||
'%sID' % self.toolName, 'site_icon.gif', # Icon in control_panel
|
'%sID' % self.toolName, 'site_icon.gif', # Icon in control_panel
|
||||||
self.productName, None)
|
self.productName, None)
|
||||||
|
|
||||||
|
def installTranslations(self):
|
||||||
|
'''Creates or updates the translation objects within the tool.'''
|
||||||
|
translations = [t.o.id for t in self.appyTool.translations]
|
||||||
|
# We browse the languages supported by this application and check
|
||||||
|
# whether we need to create the corresponding Translation objects.
|
||||||
|
for language in self.languages:
|
||||||
|
if language in translations: continue
|
||||||
|
# We will create, in the tool, the translation object for this
|
||||||
|
# language. Determine first its title.
|
||||||
|
langId, langEn, langNat = languages.get(language)
|
||||||
|
if langEn != langNat:
|
||||||
|
title = '%s (%s)' % (langEn, langNat)
|
||||||
|
else:
|
||||||
|
title = langEn
|
||||||
|
self.appyTool.create('translations', id=language, title=title)
|
||||||
|
self.appyTool.log('Translation object created for "%s".' % language)
|
||||||
|
# Now, we synchronise every Translation object with the corresponding
|
||||||
|
# "po" file on disk.
|
||||||
|
appFolder = self.config.diskFolder
|
||||||
|
appName = self.config.PROJECTNAME
|
||||||
|
dn = os.path.dirname
|
||||||
|
jn = os.path.join
|
||||||
|
i18nFolder = jn(jn(jn(dn(dn(dn(appFolder))),'Products'),appName),'i18n')
|
||||||
|
for translation in self.appyTool.translations:
|
||||||
|
# Get the "po" file
|
||||||
|
poName = '%s-%s.po' % (appName, translation.id)
|
||||||
|
poFile = PoParser(jn(i18nFolder, poName)).parse()
|
||||||
|
for message in poFile.messages:
|
||||||
|
setattr(translation, message.id, message.getMessage())
|
||||||
|
self.appyTool.log('Translation "%s" updated from "%s".' % \
|
||||||
|
(translation.id, poName))
|
||||||
|
|
||||||
def installRolesAndGroups(self):
|
def installRolesAndGroups(self):
|
||||||
'''Registers roles used by workflows and classes defined in this
|
'''Registers roles used by workflows and classes defined in this
|
||||||
application if they are not registered yet. Creates the corresponding
|
application if they are not registered yet. Creates the corresponding
|
||||||
|
@ -459,6 +493,7 @@ class PloneInstaller:
|
||||||
self.installRootFolder()
|
self.installRootFolder()
|
||||||
self.installTypes()
|
self.installTypes()
|
||||||
self.installTool()
|
self.installTool()
|
||||||
|
self.installTranslations()
|
||||||
self.installRolesAndGroups()
|
self.installRolesAndGroups()
|
||||||
self.installWorkflows()
|
self.installWorkflows()
|
||||||
self.installStyleSheet()
|
self.installStyleSheet()
|
||||||
|
|
|
@ -109,8 +109,8 @@ class ToolMixin(BaseMixin):
|
||||||
fileName = appyTool.normalize(fileName)
|
fileName = appyTool.normalize(fileName)
|
||||||
response = obj.REQUEST.RESPONSE
|
response = obj.REQUEST.RESPONSE
|
||||||
response.setHeader('Content-Type', mimeTypes[format])
|
response.setHeader('Content-Type', mimeTypes[format])
|
||||||
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"'\
|
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"' % \
|
||||||
% (fileName, format))
|
(fileName, format))
|
||||||
f.close()
|
f.close()
|
||||||
# Execute the related action if relevant
|
# Execute the related action if relevant
|
||||||
if doAction and podInfo['action']:
|
if doAction and podInfo['action']:
|
||||||
|
@ -710,8 +710,8 @@ class ToolMixin(BaseMixin):
|
||||||
fieldName, pageName = d2.split(':')
|
fieldName, pageName = d2.split(':')
|
||||||
sourceObj = self.uid_catalog(UID=d1)[0].getObject()
|
sourceObj = self.uid_catalog(UID=d1)[0].getObject()
|
||||||
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
||||||
res['backText'] = u'%s : %s' % (sourceObj.Title().decode('utf-8'),
|
res['backText'] = '%s : %s' % (sourceObj.Title(),
|
||||||
self.translate(label))
|
self.translate(label))
|
||||||
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
|
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
|
||||||
# Among, first, previous, next and last, which one do I need?
|
# Among, first, previous, next and last, which one do I need?
|
||||||
previousNeeded = False # Previous ?
|
previousNeeded = False # Previous ?
|
||||||
|
|
|
@ -384,8 +384,11 @@ class BaseMixin:
|
||||||
refType = refObject.o.getAppyType(fieldName)
|
refType = refObject.o.getAppyType(fieldName)
|
||||||
value = getattr(refObject, fieldName)
|
value = getattr(refObject, fieldName)
|
||||||
value = refType.getFormattedValue(refObject.o, value)
|
value = refType.getFormattedValue(refObject.o, value)
|
||||||
if (refType.type == 'String') and (refType.format == 2):
|
if refType.type == 'String':
|
||||||
value = self.xhtmlToText.sub(' ', value)
|
if refType.format == 2:
|
||||||
|
value = self.xhtmlToText.sub(' ', value)
|
||||||
|
elif type(value) in sequenceTypes:
|
||||||
|
value = ', '.join(value)
|
||||||
prefix = ''
|
prefix = ''
|
||||||
if res:
|
if res:
|
||||||
prefix = ' | '
|
prefix = ' | '
|
||||||
|
@ -816,12 +819,13 @@ class BaseMixin:
|
||||||
self.plone_utils.addPortalMessage(msg)
|
self.plone_utils.addPortalMessage(msg)
|
||||||
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||||
elif resultType == 'file':
|
elif resultType == 'file':
|
||||||
# msg does not contain a message, but a complete file to show as is.
|
# msg does not contain a message, but a file instance.
|
||||||
# (or, if your prefer, the message must be shown directly to the
|
response = self.REQUEST.RESPONSE
|
||||||
# user, not encapsulated in a Plone page).
|
response.setHeader('Content-Type',mimetypes.guess_type(msg.name)[0])
|
||||||
res = self.getProductConfig().File(msg.name, msg.name, msg,
|
response.setHeader('Content-Disposition', 'inline;filename="%s"' %\
|
||||||
content_type=mimetypes.guess_type(msg.name)[0])
|
msg.name)
|
||||||
return res.index_html(rq, rq.RESPONSE)
|
response.write(msg.read())
|
||||||
|
msg.close()
|
||||||
elif resultType == 'redirect':
|
elif resultType == 'redirect':
|
||||||
# msg does not contain a message, but the URL where to redirect
|
# msg does not contain a message, but the URL where to redirect
|
||||||
# the user.
|
# the user.
|
||||||
|
@ -1045,13 +1049,27 @@ class BaseMixin:
|
||||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||||
cfg = self.getProductConfig()
|
cfg = self.getProductConfig()
|
||||||
if not domain: domain = cfg.PROJECTNAME
|
if not domain: domain = cfg.PROJECTNAME
|
||||||
try:
|
if domain != cfg.PROJECTNAME:
|
||||||
res = self.Control_Panel.TranslationService.utranslate(
|
# We need to translate something that is in a standard Zope catalog
|
||||||
domain, label, mapping, self, default=default,
|
try:
|
||||||
target_language=language)
|
res = self.Control_Panel.TranslationService.utranslate(
|
||||||
except AttributeError:
|
domain, label, mapping, self, default=default,
|
||||||
# When run in test mode, Zope does not create the TranslationService
|
target_language=language)
|
||||||
res = label
|
except AttributeError:
|
||||||
|
# When run in test mode, Zope does not create the
|
||||||
|
# TranslationService
|
||||||
|
res = label
|
||||||
|
else:
|
||||||
|
# We will get the translation from a Translation object.
|
||||||
|
# In what language must we get the translation?
|
||||||
|
if not language: language = self.REQUEST['LANGUAGE']
|
||||||
|
tool = self.getTool()
|
||||||
|
translation = getattr(self.getTool(), language).appy()
|
||||||
|
res = getattr(translation, label, '')
|
||||||
|
# Perform replacements if needed
|
||||||
|
for name, repl in mapping.iteritems():
|
||||||
|
res = res.replace('${%s}' % name, repl)
|
||||||
|
# At present, there is no fallback machanism.
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getPageLayout(self, layoutType):
|
def getPageLayout(self, layoutType):
|
||||||
|
|
|
@ -21,23 +21,26 @@ class ModelClass:
|
||||||
# must not be given in the constructor (they are computed attributes).
|
# must not be given in the constructor (they are computed attributes).
|
||||||
_appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'isSelect',
|
_appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'isSelect',
|
||||||
'hasLabel', 'hasDescr', 'hasHelp', 'master_css',
|
'hasLabel', 'hasDescr', 'hasHelp', 'master_css',
|
||||||
'layouts', 'required', 'filterable', 'validable', 'backd',
|
'required', 'filterable', 'validable', 'backd', 'isBack',
|
||||||
'isBack', 'sync', 'pageName')
|
'sync', 'pageName')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_getTypeBody(klass, appyType):
|
def _appy_getTypeBody(klass, appyType):
|
||||||
'''This method returns the code declaration for p_appyType.'''
|
'''This method returns the code declaration for p_appyType.'''
|
||||||
typeArgs = ''
|
typeArgs = ''
|
||||||
for attrName, attrValue in appyType.__dict__.iteritems():
|
for attrName, attrValue in appyType.__dict__.iteritems():
|
||||||
if attrName in ModelClass._appy_notinit:
|
if attrName in ModelClass._appy_notinit: continue
|
||||||
continue
|
if attrName == 'layouts':
|
||||||
if isinstance(attrValue, basestring):
|
if klass.__name__ == 'Tool': continue
|
||||||
|
# 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.
|
||||||
|
attrValue = appyType.getInputLayouts()
|
||||||
|
elif isinstance(attrValue, basestring):
|
||||||
attrValue = '"%s"' % attrValue
|
attrValue = '"%s"' % attrValue
|
||||||
elif isinstance(attrValue, Ref):
|
elif isinstance(attrValue, Ref):
|
||||||
if attrValue.isBack:
|
if not attrValue.isBack: continue
|
||||||
attrValue = klass._appy_getTypeBody(attrValue)
|
attrValue = klass._appy_getTypeBody(attrValue)
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif type(attrValue) == type(ModelClass):
|
elif type(attrValue) == type(ModelClass):
|
||||||
moduleName = attrValue.__module__
|
moduleName = attrValue.__module__
|
||||||
if moduleName.startswith('appy.gen'):
|
if moduleName.startswith('appy.gen'):
|
||||||
|
@ -49,8 +52,8 @@ class ModelClass:
|
||||||
elif isinstance(attrValue, Group):
|
elif isinstance(attrValue, Group):
|
||||||
attrValue = 'Group("%s")' % attrValue.name
|
attrValue = 'Group("%s")' % attrValue.name
|
||||||
elif isinstance(attrValue, Page):
|
elif isinstance(attrValue, Page):
|
||||||
attrValue = 'Page("%s")' % attrValue.name
|
attrValue = 'pages["%s"]' % attrValue.name
|
||||||
elif type(attrValue) == types.FunctionType:
|
elif callable(attrValue):
|
||||||
attrValue = '%sWrapper.%s'% (klass.__name__, attrValue.__name__)
|
attrValue = '%sWrapper.%s'% (klass.__name__, attrValue.__name__)
|
||||||
typeArgs += '%s=%s,' % (attrName, attrValue)
|
typeArgs += '%s=%s,' % (attrName, attrValue)
|
||||||
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
||||||
|
@ -59,7 +62,25 @@ class ModelClass:
|
||||||
def _appy_getBody(klass):
|
def _appy_getBody(klass):
|
||||||
'''This method returns the code declaration of this class. We will dump
|
'''This method returns the code declaration of this class. We will dump
|
||||||
this in appyWrappers.py in the resulting product.'''
|
this in appyWrappers.py in the resulting product.'''
|
||||||
res = ''
|
res = 'class %s(%sWrapper):\n' % (klass.__name__, klass.__name__)
|
||||||
|
if klass.__name__ == 'Tool':
|
||||||
|
res += ' folder=True\n'
|
||||||
|
# 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 = {}
|
||||||
|
for attrName in klass._appy_attributes:
|
||||||
|
exec 'appyType = klass.%s' % attrName
|
||||||
|
if appyType.page.name not in pages:
|
||||||
|
pages[appyType.page.name] = appyType.page
|
||||||
|
res += ' pages = {'
|
||||||
|
for page in pages.itervalues():
|
||||||
|
# Determine page show
|
||||||
|
pageShow = page.show
|
||||||
|
if isinstance(pageShow, basestring): pageShow='"%s"' % pageShow
|
||||||
|
res += '"%s":Page("%s", show=%s),'% (page.name, page.name, pageShow)
|
||||||
|
res += '}\n'
|
||||||
|
# Secondly, dump every attribute
|
||||||
for attrName in klass._appy_attributes:
|
for attrName in klass._appy_attributes:
|
||||||
exec 'appyType = klass.%s' % attrName
|
exec 'appyType = klass.%s' % attrName
|
||||||
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
||||||
|
@ -86,25 +107,38 @@ class User(ModelClass):
|
||||||
gm['multiplicity'] = (0, None)
|
gm['multiplicity'] = (0, None)
|
||||||
roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
|
roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
|
||||||
|
|
||||||
# The Tool class ---------------------------------------------------------------
|
# The Translation class --------------------------------------------------------
|
||||||
|
class Translation(ModelClass):
|
||||||
|
_appy_attributes = ['po', 'title']
|
||||||
|
# All methods defined below are fake. Real versions are in the wrapper.
|
||||||
|
def getPoFile(self): pass
|
||||||
|
po = Action(action=getPoFile, page=Page('actions', show='view'),
|
||||||
|
result='file')
|
||||||
|
title = String(show=False, indexed=True)
|
||||||
|
def computeLabel(self): pass
|
||||||
|
def showField(self, name): pass
|
||||||
|
|
||||||
|
# The Tool class ---------------------------------------------------------------
|
||||||
# Here are the prefixes of the fields generated on the Tool.
|
# Here are the prefixes of the fields generated on the Tool.
|
||||||
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
||||||
'enableAdvancedSearch', 'numberOfSearchColumns',
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
||||||
'searchFields', 'optionalFields', 'showWorkflow',
|
'searchFields', 'optionalFields', 'showWorkflow',
|
||||||
'showWorkflowCommentField', 'showAllStatesInPhase')
|
'showWorkflowCommentField', 'showAllStatesInPhase')
|
||||||
defaultToolFields = ('users', 'enableNotifications', 'unoEnabledPython',
|
defaultToolFields = ('users', 'translations', 'enableNotifications',
|
||||||
'openOfficePort', 'numberOfResultsPerPage',
|
'unoEnabledPython', 'openOfficePort',
|
||||||
'listBoxesMaximumWidth')
|
'numberOfResultsPerPage', 'listBoxesMaximumWidth')
|
||||||
|
|
||||||
class Tool(ModelClass):
|
class Tool(ModelClass):
|
||||||
# The following dict allows us to remember the original classes related to
|
|
||||||
# the attributes we will add due to params in user attributes.
|
|
||||||
_appy_classes = {} # ~{s_attributeName: s_className}~
|
|
||||||
# In a ModelClass we need to declare attributes in the following list.
|
# In a ModelClass we need to declare attributes in the following list.
|
||||||
_appy_attributes = list(defaultToolFields)
|
_appy_attributes = list(defaultToolFields)
|
||||||
|
|
||||||
# Tool attributes
|
# Tool attributes
|
||||||
|
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
||||||
|
unoEnabledPython = String(group="connectionToOpenOffice",
|
||||||
|
validator=validPythonWithUno)
|
||||||
|
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
||||||
|
numberOfResultsPerPage = Integer(default=30)
|
||||||
|
listBoxesMaximumWidth = Integer(default=100)
|
||||||
# First arg of Ref field below is None because we don't know yet if it will
|
# First arg of Ref field below is None because we don't know yet if it will
|
||||||
# link to the predefined User class or a custom class defined in the
|
# link to the predefined User class or a custom class defined in the
|
||||||
# application.
|
# application.
|
||||||
|
@ -112,13 +146,10 @@ class Tool(ModelClass):
|
||||||
back=Ref(attribute='toTool'), page='users', queryable=True,
|
back=Ref(attribute='toTool'), page='users', queryable=True,
|
||||||
queryFields=('login',), showHeaders=True,
|
queryFields=('login',), showHeaders=True,
|
||||||
shownInfo=('login', 'title', 'roles'))
|
shownInfo=('login', 'title', 'roles'))
|
||||||
|
translations = Ref(Translation, multiplicity=(0,None), add=False,link=False,
|
||||||
|
back=Ref(attribute='trToTool', show=False), show='view',
|
||||||
|
page=Page('translations', show='view'))
|
||||||
enableNotifications = Boolean(default=True, page='notifications')
|
enableNotifications = Boolean(default=True, page='notifications')
|
||||||
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
|
||||||
unoEnabledPython = String(group="connectionToOpenOffice",
|
|
||||||
validator=validPythonWithUno)
|
|
||||||
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
|
||||||
numberOfResultsPerPage = Integer(default=30)
|
|
||||||
listBoxesMaximumWidth = Integer(default=100)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_clean(klass):
|
def _appy_clean(klass):
|
||||||
|
@ -130,5 +161,4 @@ class Tool(ModelClass):
|
||||||
for k in toClean:
|
for k in toClean:
|
||||||
exec 'del klass.%s' % k
|
exec 'del klass.%s' % k
|
||||||
klass._appy_attributes = list(defaultToolFields)
|
klass._appy_attributes = list(defaultToolFields)
|
||||||
klass._appy_classes = {}
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 415 B |
|
@ -3,8 +3,10 @@ from AccessControl import ClassSecurityInfo
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
from Products.Archetypes.atapi import *
|
from Products.Archetypes.atapi import *
|
||||||
import Products.<!applicationName!>.config
|
import Products.<!applicationName!>.config
|
||||||
from Extensions.appyWrappers import <!genClassName!>_Wrapper
|
from Products.CMFCore.utils import UniqueObject
|
||||||
from appy.gen.plone25.mixins import BaseMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
|
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
|
||||||
|
from Extensions.appyWrappers import <!genClassName!>_Wrapper
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
schema = Schema((<!fields!>
|
||||||
|
@ -18,19 +20,20 @@ class <!genClassName!>(<!parents!>):
|
||||||
archetype_name = '<!genClassName!>'
|
archetype_name = '<!genClassName!>'
|
||||||
meta_type = '<!genClassName!>'
|
meta_type = '<!genClassName!>'
|
||||||
portal_type = '<!genClassName!>'
|
portal_type = '<!genClassName!>'
|
||||||
allowed_content_types = []
|
allowed_content_types = ()
|
||||||
filter_content_types = 0
|
filter_content_types = 0
|
||||||
global_allow = 1
|
global_allow = <!global_allow!>
|
||||||
immediate_view = 'skyn/view'
|
immediate_view = 'skyn/view'
|
||||||
default_view = 'skyn/view'
|
default_view = 'skyn/view'
|
||||||
suppl_views = ()
|
suppl_views = ()
|
||||||
typeDescription = '<!genClassName!>'
|
typeDescription = '<!genClassName!>'
|
||||||
typeDescMsgId = '<!genClassName!>_edit_descr'
|
typeDescMsgId = '<!genClassName!>_edit_descr'
|
||||||
i18nDomain = '<!applicationName!>'
|
i18nDomain = '<!applicationName!>'
|
||||||
schema = fullSchema
|
|
||||||
wrapperClass = <!genClassName!>_Wrapper
|
wrapperClass = <!genClassName!>_Wrapper
|
||||||
for elem in dir(BaseMixin):
|
schema = fullSchema
|
||||||
|
for elem in dir(<!baseMixin!>):
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
if not elem.startswith('__'): security.declarePublic(elem)
|
||||||
|
<!static!>
|
||||||
<!commonMethods!>
|
<!commonMethods!>
|
||||||
<!methods!>
|
<!methods!>
|
||||||
<!register!>
|
<!register!>
|
|
@ -266,6 +266,13 @@ div.appyPopup {
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.translationLabel {
|
||||||
|
background-color: #EAEAEA;
|
||||||
|
border-bottom: 1px dashed grey;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Uncomment this if you want to hide breadcrumbs */
|
/* Uncomment this if you want to hide breadcrumbs */
|
||||||
/*
|
/*
|
||||||
#portal-breadcrumbs {
|
#portal-breadcrumbs {
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<!codeHeader!>
|
|
||||||
from AccessControl import ClassSecurityInfo
|
|
||||||
from DateTime import DateTime
|
|
||||||
from Products.Archetypes.atapi import *
|
|
||||||
from Products.CMFCore.utils import UniqueObject
|
|
||||||
import Products.<!applicationName!>.config
|
|
||||||
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
|
|
||||||
from Extensions.appyWrappers import AbstractWrapper, <!wrapperClass!>
|
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
|
||||||
),)
|
|
||||||
fullSchema = OrderedBaseFolderSchema.copy() + schema.copy()
|
|
||||||
|
|
||||||
class <!toolName!>(ToolMixin, UniqueObject, OrderedBaseFolder):
|
|
||||||
'''Tool for <!applicationName!>.'''
|
|
||||||
security = ClassSecurityInfo()
|
|
||||||
__implements__ = (getattr(UniqueObject,'__implements__',()),) + (getattr(OrderedBaseFolder,'__implements__',()),)
|
|
||||||
|
|
||||||
archetype_name = '<!toolName!>'
|
|
||||||
meta_type = '<!toolName!>'
|
|
||||||
portal_type = '<!toolName!>'
|
|
||||||
allowed_content_types = ()
|
|
||||||
filter_content_types = 0
|
|
||||||
global_allow = 0
|
|
||||||
#content_icon = '<!toolName!>.gif'
|
|
||||||
immediate_view = 'skyn/view'
|
|
||||||
default_view = 'skyn/view'
|
|
||||||
suppl_views = ()
|
|
||||||
typeDescription = "<!toolName!>"
|
|
||||||
typeDescMsgId = '<!toolName!>_edit_descr'
|
|
||||||
i18nDomain = '<!applicationName!>'
|
|
||||||
allMetaTypes = <!metaTypes!>
|
|
||||||
wrapperClass = <!wrapperClass!>
|
|
||||||
schema = fullSchema
|
|
||||||
schema["id"].widget.visible = False
|
|
||||||
schema["title"].widget.visible = False
|
|
||||||
# When browsing into the tool, the 'configure' portlet should be displayed.
|
|
||||||
left_slots = ['here/portlet_prefs/macros/portlet']
|
|
||||||
right_slots = []
|
|
||||||
for elem in dir(ToolMixin):
|
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
|
||||||
|
|
||||||
# Tool constructor has no id argument, the id is fixed.
|
|
||||||
def __init__(self, id=None):
|
|
||||||
OrderedBaseFolder.__init__(self, '<!toolInstanceName!>')
|
|
||||||
self.setTitle('<!applicationName!>')
|
|
||||||
<!commonMethods!>
|
|
||||||
<!methods!>
|
|
||||||
registerType(<!toolName!>, '<!applicationName!>')
|
|
|
@ -1,34 +0,0 @@
|
||||||
<!codeHeader!>
|
|
||||||
from AccessControl import ClassSecurityInfo
|
|
||||||
from Products.Archetypes.atapi import *
|
|
||||||
import Products.<!applicationName!>.config
|
|
||||||
from appy.gen.plone25.mixins import BaseMixin
|
|
||||||
from Extensions.appyWrappers import <!wrapperClass!>
|
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
|
||||||
),)
|
|
||||||
fullSchema = BaseSchema.copy() + schema.copy()
|
|
||||||
|
|
||||||
class <!applicationName!>User(BaseMixin, BaseContent):
|
|
||||||
'''User mixin.'''
|
|
||||||
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(BaseMixin):
|
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
|
||||||
<!commonMethods!>
|
|
||||||
<!methods!>
|
|
||||||
registerType(<!applicationName!>User, '<!applicationName!>')
|
|
|
@ -3,16 +3,13 @@ from appy.gen import *
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
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.UserWrapper import UserWrapper
|
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
|
||||||
|
from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper
|
||||||
from Globals import InitializeClass
|
from Globals import InitializeClass
|
||||||
from AccessControl import ClassSecurityInfo
|
from AccessControl import ClassSecurityInfo
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
class User(UserWrapper):
|
<!User!>
|
||||||
'''This class represents a user.'''
|
<!Translation!>
|
||||||
<!userBody!>
|
<!Tool!>
|
||||||
class Tool(ToolWrapper):
|
|
||||||
'''This class represents the tool for this application.'''
|
|
||||||
folder=True
|
|
||||||
<!toolBody!>
|
|
||||||
<!wrappers!>
|
<!wrappers!>
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -45,7 +45,7 @@ setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
|
||||||
|
|
||||||
# Applications classes, in various formats
|
# Applications classes, in various formats
|
||||||
rootClasses = [<!rootClasses!>]
|
rootClasses = [<!rootClasses!>]
|
||||||
appClasses = <!appClasses!>
|
appClasses = [<!appClasses!>]
|
||||||
appClassNames = [<!appClassNames!>]
|
appClassNames = [<!appClassNames!>]
|
||||||
allClassNames = [<!allClassNames!>]
|
allClassNames = [<!allClassNames!>]
|
||||||
# List of classes that must be hidden from the catalog
|
# List of classes that must be hidden from the catalog
|
||||||
|
@ -66,7 +66,7 @@ workflowInstances = {}
|
||||||
# In the following dict, we store, for every Appy class, the ordered list of
|
# In the following dict, we store, for every Appy class, the ordered list of
|
||||||
# appy types (included inherited ones).
|
# appy types (included inherited ones).
|
||||||
attributes = {<!attributes!>}
|
attributes = {<!attributes!>}
|
||||||
# In the followinf dict, we store, for every Appy class, a dict of appy types
|
# In the following dict, we store, for every Appy class, a dict of appy types
|
||||||
# keyed by their names.
|
# keyed by their names.
|
||||||
attributesDict = {<!attributesDict!>}
|
attributesDict = {<!attributesDict!>}
|
||||||
|
|
||||||
|
@ -81,4 +81,5 @@ languages = [<!languages!>]
|
||||||
languageSelector = <!languageSelector!>
|
languageSelector = <!languageSelector!>
|
||||||
minimalistPlone = <!minimalistPlone!>
|
minimalistPlone = <!minimalistPlone!>
|
||||||
appFrontPage = <!appFrontPage!>
|
appFrontPage = <!appFrontPage!>
|
||||||
|
sourceLanguage = '<!sourceLanguage!>'
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -105,4 +105,8 @@ class ToolWrapper(AbstractWrapper):
|
||||||
res = '%sFor%s' % (attributeType, fullClassName)
|
res = '%sFor%s' % (attributeType, fullClassName)
|
||||||
if attrName: res += '_%s' % attrName
|
if attrName: res += '_%s' % attrName
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getAvailableLanguages(self):
|
||||||
|
'''Returns the list of available languages for this application.'''
|
||||||
|
return [(t.id, t.title) for t in self.translations]
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
76
gen/plone25/wrappers/TranslationWrapper.py
Normal file
76
gen/plone25/wrappers/TranslationWrapper.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import os.path
|
||||||
|
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
|
from appy.gen.po import PoFile, PoMessage
|
||||||
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class TranslationWrapper(AbstractWrapper):
|
||||||
|
def computeLabel(self, field):
|
||||||
|
'''The label for a text to translate displays the text of the
|
||||||
|
corresponding message in the source translation.'''
|
||||||
|
tool = self.tool
|
||||||
|
sourceLanguage = self.o.getProductConfig().sourceLanguage
|
||||||
|
sourceTranslation = getattr(tool.o, sourceLanguage).appy()
|
||||||
|
# p_field is the Computed field. We need to get the name of the
|
||||||
|
# corresponding field holding the translation message.
|
||||||
|
fieldName = field.name[:-6]
|
||||||
|
# If we are showing the source translation, we do not repeat the message
|
||||||
|
# in the label.
|
||||||
|
if self.id == sourceLanguage:
|
||||||
|
sourceMsg = ''
|
||||||
|
else:
|
||||||
|
sourceMsg = getattr(sourceTranslation,fieldName)
|
||||||
|
# When editing the value, we don't want HTML code to be interpreted.
|
||||||
|
# This way, the translator sees the HTML tags and can reproduce them in
|
||||||
|
# the translation.
|
||||||
|
if self.request['URL'].endswith('/skyn/edit'):
|
||||||
|
sourceMsg = sourceMsg.replace('<','<').replace('>','>')
|
||||||
|
sourceMsg = sourceMsg.replace('\n', '<br/>')
|
||||||
|
return '<div class="translationLabel"><acronym title="%s">' \
|
||||||
|
'<img src="help.png"/></acronym>%s</div>' % \
|
||||||
|
(fieldName, sourceMsg)
|
||||||
|
|
||||||
|
def showField(self, field):
|
||||||
|
'''We show a field (or its label) only if the corresponding source
|
||||||
|
message is not empty.'''
|
||||||
|
tool = self.tool
|
||||||
|
if field.type == 'Computed': name = field.name[:-6]
|
||||||
|
else: name = field.name
|
||||||
|
# Get the source message
|
||||||
|
sourceLanguage = self.o.getProductConfig().sourceLanguage
|
||||||
|
sourceTranslation = getattr(tool.o, sourceLanguage).appy()
|
||||||
|
sourceMsg = getattr(sourceTranslation, name)
|
||||||
|
if field.isEmptyValue(sourceMsg): return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
poReplacements = ( ('\r\n', '<br/>'), ('\n', '<br/>'), ('"', '\\"') )
|
||||||
|
def getPoFile(self):
|
||||||
|
'''Computes and returns the PO file corresponding to this
|
||||||
|
translation.'''
|
||||||
|
tool = self.tool
|
||||||
|
fileName = os.path.join(getOsTempFolder(),
|
||||||
|
'%s-%s.po' % (tool.o.getAppName(), self.id))
|
||||||
|
poFile = PoFile(fileName)
|
||||||
|
for field in self.fields:
|
||||||
|
if (field.name == 'title') or (field.type != 'String'): continue
|
||||||
|
# Adds the PO message corresponding to this field
|
||||||
|
msg = field.getValue(self.o) or ''
|
||||||
|
for old, new in self.poReplacements:
|
||||||
|
msg = msg.replace(old, new)
|
||||||
|
poFile.addMessage(PoMessage(field.name, msg, ''))
|
||||||
|
poFile.generate()
|
||||||
|
return True, file(fileName)
|
||||||
|
|
||||||
|
def validate(self, new, errors):
|
||||||
|
# Call a custom "validate" if any.
|
||||||
|
self._callCustom('validate', new, errors)
|
||||||
|
|
||||||
|
def onEdit(self, created):
|
||||||
|
# Call a custom "onEdit" if any.
|
||||||
|
self._callCustom('onEdit', created)
|
||||||
|
|
||||||
|
def onDelete(self):
|
||||||
|
# Call a custom "onDelete" if any.
|
||||||
|
self._callCustom('onDelete')
|
||||||
|
# ------------------------------------------------------------------------------
|
|
@ -4,17 +4,6 @@ from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class UserWrapper(AbstractWrapper):
|
class UserWrapper(AbstractWrapper):
|
||||||
|
|
||||||
def _callCustom(self, methodName, *args, **kwargs):
|
|
||||||
'''This wrapper implements some methods like "validate" and "onEdit".
|
|
||||||
If the user has defined its own wrapper, its methods will not be
|
|
||||||
called. So this method allows, from the methods here, to call the
|
|
||||||
user versions.'''
|
|
||||||
if len(self.__class__.__bases__) > 1:
|
|
||||||
# There is a custom user class
|
|
||||||
customUser = self.__class__.__bases__[-1]
|
|
||||||
if customUser.__dict__.has_key(methodName):
|
|
||||||
customUser.__dict__[methodName](self, *args, **kwargs)
|
|
||||||
|
|
||||||
def showLogin(self):
|
def showLogin(self):
|
||||||
'''When must we show the login field?'''
|
'''When must we show the login field?'''
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
|
|
|
@ -33,6 +33,17 @@ class AbstractWrapper:
|
||||||
if other: return cmp(self.o, other.o)
|
if other: return cmp(self.o, other.o)
|
||||||
else: return 1
|
else: return 1
|
||||||
|
|
||||||
|
def _callCustom(self, methodName, *args, **kwargs):
|
||||||
|
'''This wrapper implements some methods like "validate" and "onEdit".
|
||||||
|
If the user has defined its own wrapper, its methods will not be
|
||||||
|
called. So this method allows, from the methods here, to call the
|
||||||
|
user versions.'''
|
||||||
|
if len(self.__class__.__bases__) > 1:
|
||||||
|
# There is a custom user class
|
||||||
|
customUser = self.__class__.__bases__[-1]
|
||||||
|
if customUser.__dict__.has_key(methodName):
|
||||||
|
customUser.__dict__[methodName](self, *args, **kwargs)
|
||||||
|
|
||||||
def get_tool(self): return self.o.getTool().appy()
|
def get_tool(self): return self.o.getTool().appy()
|
||||||
tool = property(get_tool)
|
tool = property(get_tool)
|
||||||
|
|
||||||
|
|
18
gen/po.py
18
gen/po.py
|
@ -42,7 +42,6 @@ class PoMessage:
|
||||||
'comment every time a transition is ' \
|
'comment every time a transition is ' \
|
||||||
'triggered'
|
'triggered'
|
||||||
MSG_showAllStatesInPhase = 'Show all states in phase'
|
MSG_showAllStatesInPhase = 'Show all states in phase'
|
||||||
USER = 'User'
|
|
||||||
POD_ASKACTION = 'Trigger related action'
|
POD_ASKACTION = 'Trigger related action'
|
||||||
REF_NO = 'No object.'
|
REF_NO = 'No object.'
|
||||||
REF_ADD = 'Add a new one'
|
REF_ADD = 'Add a new one'
|
||||||
|
@ -194,6 +193,10 @@ class PoMessage:
|
||||||
newId = '%s_%s' % (newPrefix, self.id.split('_', 1)[1])
|
newId = '%s_%s' % (newPrefix, self.id.split('_', 1)[1])
|
||||||
return PoMessage(newId, self.msg, self.default, comments=self.comments)
|
return PoMessage(newId, self.msg, self.default, comments=self.comments)
|
||||||
|
|
||||||
|
def getMessage(self):
|
||||||
|
'''Returns self.msg, but with some replacements.'''
|
||||||
|
return self.msg.replace('<br/>', '\n').replace('\\"', '"')
|
||||||
|
|
||||||
class PoHeader:
|
class PoHeader:
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -236,8 +239,11 @@ class PoFile:
|
||||||
self.generated = False # Is set to True during the generation process
|
self.generated = False # Is set to True during the generation process
|
||||||
# when this file has been generated.
|
# when this file has been generated.
|
||||||
|
|
||||||
def addMessage(self, newMsg):
|
def addMessage(self, newMsg, needsCopy=True):
|
||||||
res = copy.copy(newMsg)
|
if needsCopy:
|
||||||
|
res = copy.copy(newMsg)
|
||||||
|
else:
|
||||||
|
res = newMsg
|
||||||
self.messages.append(res)
|
self.messages.append(res)
|
||||||
self.messagesDict[res.id] = res
|
self.messagesDict[res.id] = res
|
||||||
return res
|
return res
|
||||||
|
@ -261,7 +267,9 @@ class PoFile:
|
||||||
i = len(self.messages)-1
|
i = len(self.messages)-1
|
||||||
while i >= 0:
|
while i >= 0:
|
||||||
oldId = self.messages[i].id
|
oldId = self.messages[i].id
|
||||||
if not oldId.startswith('custom_') and (oldId not in newIds):
|
if not oldId.startswith('custom_') and \
|
||||||
|
not oldId.startswith('%sTranslation_page_'%self.domain) and \
|
||||||
|
(oldId not in newIds):
|
||||||
del self.messages[i]
|
del self.messages[i]
|
||||||
del self.messagesDict[oldId]
|
del self.messagesDict[oldId]
|
||||||
removedIds.append(oldId)
|
removedIds.append(oldId)
|
||||||
|
@ -343,7 +351,7 @@ class PoParser:
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
'''Parses all i18n messages in the file, stores it in
|
'''Parses all i18n messages in the file, stores it in
|
||||||
self.res.messages and returns it also.'''
|
self.res.messages and returns self.res.'''
|
||||||
f = file(self.res.fileName)
|
f = file(self.res.fileName)
|
||||||
# Currently parsed values
|
# Currently parsed values
|
||||||
msgDefault = msgFuzzy = msgId = msgStr = None
|
msgDefault = msgFuzzy = msgId = msgStr = None
|
||||||
|
|
|
@ -197,6 +197,15 @@ class Languages:
|
||||||
'''Is p_code a valid 2-digits language/country code?'''
|
'''Is p_code a valid 2-digits language/country code?'''
|
||||||
return code in self.languageCodes
|
return code in self.languageCodes
|
||||||
|
|
||||||
|
def get(self, code):
|
||||||
|
'''Returns information about the language whose code is p_code.'''
|
||||||
|
try:
|
||||||
|
iCode = self.languageCodes.index(code)
|
||||||
|
return self.languageCodes[iCode], self.languageNames[iCode], \
|
||||||
|
self.nativeNames[iCode]
|
||||||
|
except ValueError:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
i = -1
|
i = -1
|
||||||
res = ''
|
res = ''
|
||||||
|
|
Loading…
Reference in a new issue