New translation system, that generates screens for updating translations through the web, within the configuration.

This commit is contained in:
Gaetan Delannay 2011-01-14 09:06:25 +01:00
parent f3604624de
commit ead9f7c2de
22 changed files with 525 additions and 278 deletions

View file

@ -1 +1 @@
0.6.1 0.6.2

View file

@ -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

View file

@ -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.'''

View file

@ -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 ?

View file

@ -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

View file

@ -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()

View file

@ -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 ?

View file

@ -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):

View file

@ -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

View file

@ -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!>

View file

@ -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 {

View file

@ -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!>')

View file

@ -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!>')

View file

@ -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!>
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -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!>'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -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]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View 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('<','&lt;').replace('>','&gt;')
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')
# ------------------------------------------------------------------------------

View file

@ -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'

View file

@ -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)

View file

@ -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

View file

@ -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 = ''