diff --git a/gen/__init__.py b/gen/__init__.py index aa79818..8d4d5ff 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -361,7 +361,7 @@ class Type: editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, focus, - historized, sync, mapping): + historized, sync, mapping, label): # The validator restricts which values may be defined. It can be an # interval (1,None), a list of string values ['choice1', 'choice2'], # a regular expression, a custom function, a Selection instance, etc. @@ -465,10 +465,18 @@ class Type: self.filterable = False # Can this field have values that can be edited and validated? self.validable = True + # The base label for translations is normally generated automatically. + # It is made of 2 parts: the prefix, based on class name, and the name, + # which is the field name by default. You can change this by specifying + # a value for param "label". If this value is a string, it will be + # understood as a new prefix. If it is a tuple, it will represent the + # prefix and another name. If you want to specify a new name only, and + # not a prefix, write (None, newName). + self.label = label def init(self, name, klass, appName): '''When the application server starts, this secondary constructor is - called for storing the names of the Appy field (p_name) and other + called for storing the name of the Appy field (p_name) and other attributes that are based on the name of the Appy p_klass, and the application name (p_appName).''' self.name = name @@ -477,9 +485,18 @@ class Type: self.id = id(self) if self.slaves: self.master_css = 'appyMaster master_%s' % self.id # Determine ids of i18n labels for this field - if not klass: prefix = appName - else: prefix = getClassName(klass, appName) - self.labelId = '%s_%s' % (prefix, name) + labelName = name + prefix = None + if self.label: + if isinstance(self.label, basestring): prefix = self.label + else: # It is a tuple (prefix, name) + if self.label[1]: labelName = self.label[1] + if self.label[0]: prefix = self.label[0] + if not prefix: + if not klass: prefix = appName + else: prefix = getClassName(klass, appName) + # Determine name to use for i18n + self.labelId = '%s_%s' % (prefix, labelName) self.descrId = self.labelId + '_descr' self.helpId = self.labelId + '_help' # Determine read and write permissions for this field @@ -932,12 +949,13 @@ class Integer(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=6, height=None, maxChars=13, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None): + focus=False, historized=False, mapping=None, label=None): Type.__init__(self, validator, multiplicity, index, default, optional, editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, - master, masterValue, focus, historized, True, mapping) + master, masterValue, focus, historized, True, mapping, + label) self.pythonType = long def validateValue(self, obj, value): @@ -961,8 +979,8 @@ class Float(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=6, height=None, maxChars=13, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, precision=None, - sep=(',', '.')): + focus=False, historized=False, mapping=None, label=None, + precision=None, sep=(',', '.')): # The precision is the number of decimal digits. This number is used # for rendering the float, but the internal float representation is not # rounded. @@ -981,7 +999,7 @@ class Float(Type): editDefault, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, - focus, historized, True, mapping) + focus, historized, True, mapping, label) self.pythonType = float def getFormattedValue(self, obj, value): @@ -1130,7 +1148,8 @@ class String(Type): indexed=False, searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, transform='none'): + focus=False, historized=False, mapping=None, label=None, + transform='none'): self.format = format # The following field has a direct impact on the text entered by the # user. It applies a transformation on it, exactly as does the CSS @@ -1142,7 +1161,8 @@ class String(Type): editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, - master, masterValue, focus, historized, True, mapping) + master, masterValue, focus, historized, True, mapping, + label) self.isSelect = self.isSelection() # Default width, height and maxChars vary according to String format if width == None: @@ -1374,12 +1394,13 @@ class Boolean(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None): + focus=False, historized=False, mapping=None, label=None): Type.__init__(self, validator, multiplicity, index, default, optional, editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, None, colspan, - master, masterValue, focus, historized, True, mapping) + master, masterValue, focus, historized, True, mapping, + label) self.pythonType = bool def getDefaultLayouts(self): @@ -1417,7 +1438,7 @@ class Date(Type): indexed=False, searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None): + focus=False, historized=False, mapping=None, label=None): self.format = format self.calendar = calendar self.startYear = startYear @@ -1429,7 +1450,8 @@ class Date(Type): editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, None, colspan, - master, masterValue, focus, historized, True, mapping) + master, masterValue, focus, historized, True, mapping, + label) def getCss(self, layoutType): if (layoutType == 'edit') and self.calendar: @@ -1490,13 +1512,14 @@ class File(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, isImage=False): + focus=False, historized=False, mapping=None, label=None, + isImage=False): self.isImage = isImage Type.__init__(self, validator, multiplicity, index, default, optional, editDefault, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, True, mapping) + historized, True, mapping, label) @staticmethod def getFileObject(filePath, fileName=None, zope=False): @@ -1640,8 +1663,8 @@ class Ref(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=5, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, queryable=False, - queryFields=None, queryNbCols=1): + focus=False, historized=False, mapping=None, label=None, + queryable=False, queryFields=None, queryNbCols=1): self.klass = klass self.attribute = attribute # May the user add new objects through this ref ? @@ -1656,6 +1679,7 @@ class Ref(Type): self.link = link # May the user unlink existing objects? self.unlink = unlink + self.back = None if back: # It is a forward reference self.isBack = False @@ -1691,7 +1715,7 @@ class Ref(Type): editDefault, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, sync, mapping) + historized, sync, mapping, label) self.validable = self.link def getDefaultLayouts(self): return {'view': Table('l-f'), 'edit': 'lrv-f'} @@ -1858,7 +1882,7 @@ class Computed(Type): specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, method=None, plainText=True, master=None, masterValue=None, focus=False, historized=False, - sync=True, mapping=None, context={}): + sync=True, mapping=None, label=None, context={}): # The Python method used for computing the field value self.method = method # Does field computation produce plain text or XHTML? @@ -1875,7 +1899,7 @@ class Computed(Type): False, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, sync, mapping) + historized, sync, mapping, label) self.validable = False def callMacro(self, obj, macroPath): @@ -1925,7 +1949,7 @@ class Action(Type): specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, action=None, result='computation', confirm=False, master=None, masterValue=None, focus=False, - historized=False, mapping=None): + historized=False, mapping=None, label=None): # Can be a single method or a list/tuple of methods self.action = action # For the 'result' param: @@ -1946,7 +1970,7 @@ class Action(Type): False, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, False, mapping) + historized, False, mapping, label) self.validable = False def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} @@ -1994,12 +2018,12 @@ class Info(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None): + focus=False, historized=False, mapping=None, label=None): Type.__init__(self, None, (0,1), index, default, optional, False, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, False, mapping) + historized, False, mapping, label) self.validable = False class Pod(Type): @@ -2015,9 +2039,9 @@ class Pod(Type): searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, template=None, - context=None, action=None, askAction=False, stylesMapping={}, - freezeFormat='pdf'): + focus=False, historized=False, mapping=None, label=None, + template=None, context=None, action=None, askAction=False, + stylesMapping={}, freezeFormat='pdf'): # The following param stores the path to a POD template self.template = template # The context is a dict containing a specific pod context, or a method @@ -2037,7 +2061,8 @@ class Pod(Type): False, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, None, colspan, - master, masterValue, focus, historized, False, mapping) + master, masterValue, focus, historized, False, mapping, + label) self.validable = False def isFrozen(self, obj): diff --git a/gen/plone25/descriptors.py b/gen/plone25/descriptors.py index d7e8135..d79eaf8 100644 --- a/gen/plone25/descriptors.py +++ b/gen/plone25/descriptors.py @@ -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 diff --git a/gen/plone25/generator.py b/gen/plone25/generator.py index 18539ba..f862252 100644 --- a/gen/plone25/generator.py +++ b/gen/plone25/generator.py @@ -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() diff --git a/gen/plone25/model.py b/gen/plone25/model.py index 3e6d66d..2194b7c 100644 --- a/gen/plone25/model.py +++ b/gen/plone25/model.py @@ -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. diff --git a/gen/plone25/skin/page.pt b/gen/plone25/skin/page.pt index 851a525..f5a3cfa 100644 --- a/gen/plone25/skin/page.pt +++ b/gen/plone25/skin/page.pt @@ -564,11 +564,6 @@ -