appy.gen: added, for every Type, param 'label' allowing to specify an existing i18n label (the one from another field for instance), thus avoiding to generate i18n labels for this Type; optimized generation of appyWrappers.py (more than twice less code).

This commit is contained in:
Gaetan Delannay 2011-09-06 21:46:57 +02:00
parent 813b47843c
commit 3c95ac083d
8 changed files with 162 additions and 103 deletions

View file

@ -147,17 +147,18 @@ class FieldDescriptor:
(self.fieldName not in ('title', 'description')):
self.classDescr.addIndexMethod(self)
# i18n labels
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
# Create labels for generating them in i18n files.
messages = self.generator.labels
if self.appyType.hasLabel:
messages.append(self.produceMessage(i18nPrefix))
if self.appyType.hasDescr:
descrId = i18nPrefix + '_descr'
messages.append(self.produceMessage(descrId,isLabel=False))
if self.appyType.hasHelp:
helpId = i18nPrefix + '_help'
messages.append(self.produceMessage(helpId, isLabel=False))
if not self.appyType.label:
# Create labels for generating them in i18n files, only if required.
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
if self.appyType.hasLabel:
messages.append(self.produceMessage(i18nPrefix))
if self.appyType.hasDescr:
descrId = i18nPrefix + '_descr'
messages.append(self.produceMessage(descrId,isLabel=False))
if self.appyType.hasHelp:
helpId = i18nPrefix + '_help'
messages.append(self.produceMessage(helpId, isLabel=False))
# Create i18n messages linked to pages and phases, only if there is more
# than one page/phase for the class.
ppMsgs = []
@ -561,8 +562,8 @@ class TranslationClassDescriptor(ClassDescriptor):
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')
field = Computed(method=self.modelClass.label, plainText=False,
page=page, show=self.modelClass.show, layouts='f')
self.addField('%s_label' % messageId, field)
def addMessageField(self, messageId, page, i18nFiles):
@ -570,7 +571,7 @@ class TranslationClassDescriptor(ClassDescriptor):
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}
params = {'page':page, 'layouts':'f', 'show': self.modelClass.show}
appName = self.generator.applicationName
# Scan all messages corresponding to p_messageId from all translation
# files. We will define field length from the longer found message

View file

@ -590,9 +590,8 @@ class Generator(AbstractGenerator):
'''Generates the Plone tool that corresponds to this application.'''
Msg = PoMessage
# Create Tool-related i18n-related messages
self.labels += [
Msg(self.tool.name, '', Msg.CONFIG % self.applicationName),
Msg('%s_edit_descr' % self.tool.name, '', ' ')]
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName)
self.labels.append(msg)
# Tune the Ref field between Tool and User
Tool.users.klass = User
@ -604,7 +603,6 @@ class Generator(AbstractGenerator):
klassType = klass.name[len(self.applicationName):]
klass.generateSchema()
self.labels += [ Msg(klass.name, '', klassType),
Msg('%s_edit_descr' % klass.name, '', ' '),
Msg('%s_plural' % klass.name,'', klass.name+'s')]
repls = self.repls.copy()
repls.update({'fields': klass.schema, 'methods': klass.methods,
@ -702,12 +700,10 @@ class Generator(AbstractGenerator):
'implements': implements, 'baseSchema': baseSchema, 'static': '',
'register': register, 'toolInstanceName': self.toolInstanceName})
fileName = '%s.py' % classDescr.name
# Create i18n labels (class name, description and plural form)
# Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
poMsgDescr = PoMessage('%s_edit_descr' % classDescr.name, '', ' ')
self.labels.append(poMsgDescr)
poMsgPl = PoMessage('%s_plural' % classDescr.name, '',
classDescr.klass.__name__+'s')
poMsgPl.produceNiceDefault()

View file

@ -9,6 +9,33 @@
import types
from appy.gen import *
# Prototypical instances of every type -----------------------------------------
class Protos:
protos = {}
# List of attributes that can't be given to a Type constructor
notInit = ('id', 'type', 'pythonType', 'slaves', 'isSelect', 'hasLabel',
'hasDescr', 'hasHelp', 'master_css', 'required', 'filterable',
'validable', 'backd', 'isBack', 'sync', 'pageName')
@classmethod
def get(self, appyType):
'''Returns a prototype instance for p_appyType.'''
className = appyType.__class__.__name__
isString = (className == 'String')
if isString:
# For Strings, we create one prototype per format, because default
# values may change according to format.
className += str(appyType.format)
if className in self.protos: return self.protos[className]
# The prototype does not exist yet: create it
if isString:
proto = appyType.__class__(format=appyType.format)
# Now, we fake to be able to detect default values
proto.format = 0
else:
proto = appyType.__class__()
self.protos[className] = proto
return proto
# ------------------------------------------------------------------------------
class ModelClass:
'''This class is the abstract class of all predefined application classes
@ -17,60 +44,73 @@ class ModelClass:
in order to avoid name conflicts with user-defined parts of the
application model.'''
_appy_attributes = [] # We need to keep track of attributes order.
# When creating a new instance of a ModelClass, the following attributes
# must not be given in the constructor (they are computed attributes).
_appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'isSelect',
'hasLabel', 'hasDescr', 'hasHelp', 'master_css',
'required', 'filterable', 'validable', 'backd', 'isBack',
'sync', 'pageName')
@classmethod
def _appy_getTypeBody(klass, appyType):
def _appy_getTypeBody(klass, appyType, wrapperName):
'''This method returns the code declaration for p_appyType.'''
typeArgs = ''
for attrName, attrValue in appyType.__dict__.iteritems():
if attrName in ModelClass._appy_notinit: continue
if attrName == 'layouts':
if klass.__name__ == 'Tool': continue
proto = Protos.get(appyType)
for name, value in appyType.__dict__.iteritems():
# Some attrs can't be given to the constructor
if name in Protos.notInit: continue
# If the given value corresponds to the default value, don't give it
if value == getattr(proto, name): continue
if name == 'layouts':
# 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
elif isinstance(attrValue, Ref):
if not attrValue.isBack: continue
attrValue = klass._appy_getTypeBody(attrValue)
elif type(attrValue) == type(ModelClass):
moduleName = attrValue.__module__
if moduleName.startswith('appy.gen'):
attrValue = attrValue.__name__
if klass.__name__ == 'Tool': continue
layouts = appyType.getInputLayouts()
# For the Translation class that has potentially thousands of
# attributes, the most used layout is cached in a global var in
# named "tfw" in appyWrappers.py.
if (klass.__name__ == 'Translation') and \
(layouts == '{"edit":"f","cell":"f","view":"f",}'):
value = 'tfw'
else:
attrValue = '%s.%s' % (moduleName, attrValue.__name__)
elif isinstance(attrValue, Selection):
attrValue = 'Selection("%s")' % attrValue.methodName
elif isinstance(attrValue, Group):
attrValue = 'Group("%s")' % attrValue.name
elif isinstance(attrValue, Page):
attrValue = 'pages["%s"]' % attrValue.name
elif callable(attrValue):
attrValue = '%sWrapper.%s'% (klass.__name__, attrValue.__name__)
typeArgs += '%s=%s,' % (attrName, attrValue)
value = appyType.getInputLayouts()
elif isinstance(value, basestring):
value = '"%s"' % value
elif isinstance(value, Ref):
if not value.isBack: continue
value = klass._appy_getTypeBody(value, wrapperName)
elif type(value) == type(ModelClass):
moduleName = value.__module__
if moduleName.startswith('appy.gen'):
value = value.__name__
else:
value = '%s.%s' % (moduleName, value.__name__)
elif isinstance(value, Selection):
value = 'Selection("%s")' % value.methodName
elif isinstance(value, Group):
value = 'Group("%s")' % value.name
elif isinstance(value, Page):
value = 'pages["%s"]' % value.name
elif callable(value):
value = '%s.%s' % (wrapperName, value.__name__)
typeArgs += '%s=%s,' % (name, value)
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
@classmethod
def _appy_getBody(klass):
'''This method returns the code declaration of this class. We will dump
this in appyWrappers.py in the resulting product.'''
res = 'class %s(%sWrapper):\n' % (klass.__name__, klass.__name__)
if klass.__name__ == 'Tool':
res += ' folder=True\n'
className = klass.__name__
# Determine the name of the class and its wrapper. Because so much
# attributes can be generated on a TranslationWrapper, shortcutting it
# to 'TW' may reduce the generated file from several kilobytes.
if className == 'Translation': wrapperName = 'WT'
else: wrapperName = 'W%s' % className
res = 'class %s(%s):\n' % (className, wrapperName)
# Tool must be folderish
if className == '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
layouts = []
for name in klass._appy_attributes:
exec 'appyType = klass.%s' % name
if appyType.page.name not in pages:
pages[appyType.page.name] = appyType.page
res += ' pages = {'
@ -81,9 +121,10 @@ class ModelClass:
res += '"%s":Page("%s", show=%s),'% (page.name, page.name, pageShow)
res += '}\n'
# Secondly, dump every attribute
for attrName in klass._appy_attributes:
exec 'appyType = klass.%s' % attrName
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
for name in klass._appy_attributes:
exec 'appyType = klass.%s' % name
typeBody = klass._appy_getTypeBody(appyType, wrapperName)
res += ' %s=%s\n' % (name, typeBody)
return res
# The User class ---------------------------------------------------------------
@ -115,8 +156,8 @@ class Translation(ModelClass):
po = Action(action=getPoFile, page=Page('actions', show='view'),
result='filetmp')
title = String(show=False, indexed=True)
def computeLabel(self): pass
def showField(self, name): pass
def label(self): pass
def show(self, name): pass
# The Tool class ---------------------------------------------------------------
# Here are the prefixes of the fields generated on the Tool.

View file

@ -564,11 +564,6 @@
<b class="appyTitle" tal:content="contextObj/title_or_id"></b>
</td>
</tr>
<tr tal:define="descrLabel python: contextObj.translate('%s_edit_descr' % contextObj.portal_type)"
tal:condition="descrLabel/strip" align="left">
<tal:comment replace="nothing">Content type description</tal:comment>
<td colspan="2" class="discreet" tal:content="descrLabel"/>
</tr>
<tr align="left">
<td class="documentByLine" colspan="2">
<tal:comment replace="nothing">Creator and last modification date</tal:comment>

View file

@ -27,7 +27,7 @@ class <!genClassName!>(<!parents!>):
default_view = 'skyn/view'
suppl_views = ()
typeDescription = '<!genClassName!>'
typeDescMsgId = '<!genClassName!>_edit_descr'
typeDescMsgId = '<!genClassName!>'
i18nDomain = '<!applicationName!>'
wrapperClass = <!genClassName!>_Wrapper
schema = fullSchema

View file

@ -1,11 +1,12 @@
# ------------------------------------------------------------------------------
from appy.gen import *
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper as WTool
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper as WUser
from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper as WT
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields
<!imports!>
<!User!>

View file

@ -6,7 +6,7 @@ from appy.shared.utils import getOsTempFolder
# ------------------------------------------------------------------------------
class TranslationWrapper(AbstractWrapper):
def computeLabel(self, field):
def label(self, field):
'''The label for a text to translate displays the text of the
corresponding message in the source translation.'''
tool = self.tool
@ -32,7 +32,7 @@ class TranslationWrapper(AbstractWrapper):
'<img src="help.png"/></acronym>%s</div>' % \
(fieldName, sourceMsg)
def showField(self, field):
def show(self, field):
'''We show a field (or its label) only if the corresponding source
message is not empty.'''
tool = self.tool