From dbcadc506d0e81c144275fc90471923338f6eaa5 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Thu, 12 Aug 2010 11:56:42 +0200 Subject: [PATCH] new.py now can create instances for Plone 2.5.5, Plone 3.0 to Plone 3.3.5. specificWritePermission and specificReadPermission can hold named (string) permissions instead of simple boolean values (which is still allowed). frontPage can call a custom macro. When launching generate.py with -c option, labels prefixed with custom_ are kept. --- bin/new.py | 102 ++++++++++-------- gen/__init__.py | 65 +++++++---- gen/layout.py | 4 +- gen/plone25/generator.py | 32 ++++-- gen/plone25/mixins/ToolMixin.py | 14 ++- gen/plone25/mixins/__init__.py | 24 ++--- gen/plone25/skin/page.pt | 2 +- gen/plone25/skin/portlet.pt | 6 +- gen/plone25/skin/widgets/boolean.pt | 4 +- gen/plone25/skin/widgets/date.pt | 10 +- gen/plone25/skin/widgets/show.pt | 3 +- gen/plone25/skin/widgets/string.pt | 17 +-- gen/plone25/templates/Portlet.pt | 2 +- gen/plone25/templates/Styles.css.dtml | 2 + gen/plone25/templates/frontPage.pt | 12 +-- gen/plone25/templates/global_statusmessage.pt | 5 - gen/po.py | 10 +- 17 files changed, 188 insertions(+), 126 deletions(-) delete mode 100644 gen/plone25/templates/global_statusmessage.pt diff --git a/bin/new.py b/bin/new.py index 01b2893..321f59c 100755 --- a/bin/new.py +++ b/bin/new.py @@ -27,14 +27,16 @@ WRONG_INSTANCE_PATH = '"%s" must be an existing folder for creating the ' \ class NewScript: '''usage: %prog ploneVersion plonePath instancePath - "ploneVersion" can be plone25 or plone3 + "ploneVersion" can be plone25, plone30, or plone3x + (plone3x can be Plone 3.2.x, Plone 3.3.5...) "plonePath" is the (absolute) path to you plone installation. - Plone 2.5 is typically installed in /opt/Plone-2.5.5, - while Plone 3 is typically installed in /usr/local/Plone. + Plone 2.5 and 3.0 are typically installed in + /opt/Plone-x.x.x, while Plone 3 > 3.0 is typically + installed in in /usr/local/Plone. "instancePath" is the (absolute) path where you want to create your instance (should not already exist).''' - ploneVersions = ('plone25', 'plone3') + ploneVersions = ('plone25', 'plone30', 'plone3x') def createInstance(self, linksForProducts): '''Calls the Zope script that allows to create a Zope instance and copy @@ -65,35 +67,44 @@ class NewScript: print cmd os.system(cmd) # Now, make the instance Plone-ready - productsFolder = os.path.join(self.instancePath, 'Products') - libFolder = os.path.join(self.instancePath, 'lib/python') - print 'Copying Plone stuff in the Zope instance...' - if self.ploneVersion == 'plone25': - self.installPlone25Stuff(productsFolder,libFolder,linksForProducts) - elif self.ploneVersion == 'plone3': - self.installPlone3Stuff(productsFolder, libFolder) + action = 'Copying' + if linksForProducts: + action = 'Symlinking' + print '%s Plone stuff in the Zope instance...' % action + if self.ploneVersion in ('plone25', 'plone30'): + self.installPlone25or30Stuff(linksForProducts) + elif self.ploneVersion == 'plone3x': + self.installPlone3Stuff() # Clean the copied folders - cleanFolder(productsFolder) - cleanFolder(libFolder) + cleanFolder(os.path.join(self.instancePath, 'Products')) + cleanFolder(os.path.join(self.instancePath, 'lib/python')) - def installPlone25Stuff(self, productsFolder, libFolder, linksForProducts): + def installPlone25or30Stuff(self, linksForProducts): '''Here, we will copy all Plone2-related stuff in the Zope instance we've created, to get a full Plone-ready Zope instance. If p_linksForProducts is True, we do not perform a real copy: we will create symlinks to products lying within Plone installer files.''' - installerProducts = os.path.join(self.plonePath, 'zeocluster/Products') - for name in os.listdir(installerProducts): - folderName = os.path.join(installerProducts, name) - if os.path.isdir(folderName): - destFolder = os.path.join(productsFolder, name) - # This is a Plone product. Copy it to the instance. - if (self.ploneVersion == 'plone25') and linksForProducts: - # Create a symlink to this product in the instance - cmd = 'ln -s %s %s' % (folderName, destFolder) - os.system(cmd) - else: - # Copy thre product into the instance - shutil.copytree(folderName, destFolder) + j = os.path.join + if self.ploneVersion == 'plone25': + sourceFolders = ('zeocluster/Products',) + else: + sourceFolders = ('zinstance/Products', 'zinstance/lib/python') + for sourceFolder in sourceFolders: + sourceBase = j(self.plonePath, sourceFolder) + destBase = j(self.instancePath, + sourceFolder[sourceFolder.find('/')+1:]) + for name in os.listdir(sourceBase): + folderName = j(sourceBase, name) + if os.path.isdir(folderName): + destFolder = j(destBase, name) + # This is a Plone product. Copy it to the instance. + if linksForProducts: + # Create a symlink to this product in the instance + cmd = 'ln -s %s %s' % (folderName, destFolder) + os.system(cmd) + else: + # Copy thre product into the instance + shutil.copytree(folderName, destFolder) uglyChunks = ('pkg_resources', '.declare_namespace(') def findPythonPackageInEgg(self, currentFolder): @@ -168,7 +179,7 @@ class NewScript: f.write(fileContent) f.close() - def installPlone3Stuff(self, productsFolder, libFolder): + def installPlone3Stuff(self): '''Here, we will copy all Plone3-related stuff in the Zope instance we've created, to get a full Plone-ready Zope instance.''' # All Plone 3 eggs are in buildout-cache/eggs. We will extract from @@ -179,14 +190,16 @@ class NewScript: # /lib/python (ie, like Appy applications) # - Zope products that will be copied in # /Products (ie, like Appy generated Zope products) - eggsFolder = os.path.join(self.plonePath, 'buildout-cache/eggs') + j = os.path.join + eggsFolder = j(self.plonePath, 'buildout-cache/eggs') + productsFolder = j(self.instancePath, 'Products') + libFolder = j(self.instancePath, 'lib/python') for name in os.listdir(eggsFolder): - eggMainFolder = os.path.join(eggsFolder, name) + eggMainFolder = j(eggsFolder, name) if name.startswith('Products.'): # A Zope product. Copy its content in Products. innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder)) - destFolder = os.path.join(productsFolder, - os.path.basename(innerFolder)) + destFolder = j(productsFolder, os.path.basename(innerFolder)) shutil.copytree(innerFolder, destFolder) else: # A standard Python package. Copy its content in lib/python. @@ -197,7 +210,7 @@ class NewScript: # Copy those files directly in libFolder. for fileName in os.listdir(eggMainFolder): if fileName.endswith('.py'): - fullFileName= os.path.join(eggMainFolder, fileName) + fullFileName= j(eggMainFolder, fileName) shutil.copy(fullFileName, libFolder) continue eggFolderName = os.path.basename(eggFolder) @@ -205,8 +218,7 @@ class NewScript: # Goddamned. This should go in productsFolder and not in # libFolder. innerFolder = self.getSubFolder(eggFolder) - destFolder = os.path.join(productsFolder, - os.path.basename(innerFolder)) + destFolder = j(productsFolder,os.path.basename(innerFolder)) shutil.copytree(innerFolder, destFolder) else: packageFolder = self.findPythonPackageInEgg(eggFolder) @@ -225,23 +237,22 @@ class NewScript: # before copying the Python package. baseFolder = libFolder for subFolder in destFolders: - subFolderPath=os.path.join(baseFolder,subFolder) + subFolderPath = j(baseFolder,subFolder) if not os.path.exists(subFolderPath): os.mkdir(subFolderPath) # Create an empty __init__.py in it. - init = os.path.join(subFolderPath,'__init__.py') + init = j(subFolderPath,'__init__.py') f = file(init, 'w') f.write('# Makes me a Python package.') f.close() baseFolder = subFolderPath destFolder = os.sep.join(destFolders) - destFolder = os.path.join(libFolder, destFolder) + destFolder = j(libFolder, destFolder) if not os.path.exists(destFolder): os.makedirs(destFolder) else: destFolder = libFolder - destFolder = os.path.join( - destFolder, os.path.basename(packageFolder)) + destFolder = j(destFolder, os.path.basename(packageFolder)) shutil.copytree(packageFolder, destFolder) self.patchPlone(productsFolder, libFolder) @@ -264,11 +275,12 @@ class NewScript: def run(self): optParser = OptionParser(usage=NewScript.__doc__) optParser.add_option("-l", "--links", action="store_true", - help="[Linux, plone25 only] Within the created instance, symlinks "\ - "to Products lying within the Plone installer files are " \ - "created instead of copying them into the instance. This " \ - "avoids duplicating the Products source code and is " \ - "interesting if you create a lot of Zope instances.") + help="[Linux, plone25 or plone30 only] Within the created " \ + "instance, symlinks to Products lying within the Plone " \ + "installer files are created instead of copying them into " \ + "the instance. This avoids duplicating the Products source " \ + "code and is interesting if you create a lot of Zope " \ + "instances.") (options, args) = optParser.parse_args() linksForProducts = options.links try: diff --git a/gen/__init__.py b/gen/__init__.py index 2e842b7..dee5fe1 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -306,9 +306,20 @@ class Type: self.searchable = searchable # Normally, permissions to read or write every attribute in a type are # granted if the user has the global permission to read or - # create/edit instances of the whole type. If you want a given attribute + # edit instances of the whole type. If you want a given attribute # to be protected by specific permissions, set one or the 2 next boolean - # values to "True". + # values to "True". In this case, you will create a new "field-only" + # read and/or write permission. If you need to protect several fields + # with the same read/write permission, you can avoid defining one + # specific permission for every field by specifying a "named" + # permission (string) instead of assigning "True" to the following + # 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 + # of the form: ": Write|Read xxx". If, for example, I want + # to define, for my application "MedicalFolder" a specific permission + # for a bunch of fields that can only be modified by a doctor, I can + # define a permission "MedicalFolder: Write medical information" and + # assign it to the "specificWritePermission" of every impacted field. self.specificReadPermission = specificReadPermission self.specificWritePermission = specificWritePermission # Widget width and height @@ -383,12 +394,18 @@ class Type: self.descrId = self.labelId + '_descr' self.helpId = self.labelId + '_help' # Determine read and write permissions for this field - if self.specificReadPermission: + rp = self.specificReadPermission + if rp and not isinstance(rp, basestring): self.readPermission = '%s: Read %s %s' % (appName, prefix, name) + elif rp and isinstance(rp, basestring): + self.readPermission = rp else: self.readPermission = 'View' - if self.specificWritePermission: + wp = self.specificWritePermission + if wp and not isinstance(wp, basestring): self.writePermission = '%s: Write %s %s' % (appName, prefix, name) + elif wp and isinstance(wp, basestring): + self.writePermission = wp else: self.writePermission = 'Modify portal content' if isinstance(self, Ref): @@ -508,7 +525,12 @@ class Type: value = getattr(obj, self.name, None) if (value == None): # If there is no value, get the default value if any - if not self.editDefault: return self.default + if not self.editDefault: + # Return self.default, of self.default() if it is a method + if type(self.default) == types.FunctionType: + return self.default(obj.appy()) + else: + return self.default # If value is editable, get the default value from the flavour portalTypeName = obj._appy_getPortalType(obj.REQUEST) tool = obj.getTool() @@ -577,14 +599,14 @@ class Type: return obj.translate('%s_valid' % self.labelId) elif type(self.validator) == validatorTypes[1]: # It is a regular expression - if not validator.match(value): + if not self.validator.match(value): # If the regular expression is among the default ones, we # generate a specific error message. - if validator == String.EMAIL: + if self.validator == String.EMAIL: return obj.translate('bad_email') - elif validator == String.URL: + elif self.validator == String.URL: return obj.translate('bad_url') - elif validator == String.ALPHANUMERIC: + elif self.validator == String.ALPHANUMERIC: return obj.translate('bad_alphanumeric') else: return obj.translate('%s_valid' % self.labelId) @@ -771,19 +793,20 @@ class String(Type): # CSS property: "none" (default), "uppercase", "capitalize" or # "lowercase". self.transform = transform - # Default width and height vary according to String format - if width == None: - if format == String.TEXT: width = 60 - else: width = 30 - if height == None: - if format == String.TEXT: height = 5 - else: height = 1 Type.__init__(self, validator, multiplicity, index, default, optional, editDefault, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, colspan, master, masterValue, focus, historized) self.isSelect = self.isSelection() + # Default width and height vary according to String format + if width == None: + if format == String.TEXT: self.width = 60 + else: self.width = 30 + if height == None: + if format == String.TEXT: self.height = 5 + elif self.isSelect: self.height = 4 + else: self.height = 1 self.filterable = self.indexed and (self.format == String.LINE) and \ not self.isSelect @@ -1139,6 +1162,7 @@ class Ref(Type): self.validable = self.link def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} + def isShowable(self, obj, layoutType): if (layoutType == 'edit') and self.add: return False if self.isBack: @@ -1438,9 +1462,14 @@ class Permission: defining a workflow, for example, you need to use instances of "ReadPermission" and "WritePermission", the 2 children classes of this class. For example, if you need to refer to write permission of - attribute "t1" of class A, write: "WritePermission("A.t1") or + attribute "t1" of class A, write: WritePermission("A.t1") or WritePermission("x.y.A.t1") if class A is not in the same module as - where you instantiate the class.''' + where you instantiate the class. + + Note that this holds only if you use attributes "specificReadPermission" + and "specificWritePermission" as booleans. When defining named + (string) permissions, for referring to it you simply use those strings, + you do not create instances of ReadPermission or WritePermission.''' def __init__(self, fieldDescriptor): self.fieldDescriptor = fieldDescriptor diff --git a/gen/layout.py b/gen/layout.py index ab7ecc2..818ae91 100644 --- a/gen/layout.py +++ b/gen/layout.py @@ -14,7 +14,9 @@ # w - The widgets of the current page/class # n - The navigation panel (inter-objects navigation) # b - The range of buttons (intra-object navigation, save, edit, delete...) -# m - The global status message sometimes shown. +# m - The global status message sometimes shown. If you specify this in a +# layout, ensure that you have hidden the global_statusmessage zone as +# proposed by Plone. Else, the message will appear twice. # Layout elements for a field -------------------------------------------------- # l - "label" The field label diff --git a/gen/plone25/generator.py b/gen/plone25/generator.py index ca370b1..3e6995f 100644 --- a/gen/plone25/generator.py +++ b/gen/plone25/generator.py @@ -156,11 +156,8 @@ class Generator(AbstractGenerator): self.generateWorkflows() self.generateWrappers() self.generateTests() - if self.config.frontPage == True: - self.labels.append(msg('front_page_text', '', msg.FRONT_PAGE_TEXT)) - self.copyFile('frontPage.pt', self.repls, - destFolder=self.skinsFolder, - destName='%sFrontPage.pt' % self.applicationName) + if self.config.frontPage: + self.generateFrontPage() self.copyFile('configure.zcml', self.repls) self.copyFile('import_steps.xml', self.repls, destFolder='profiles/default') @@ -169,8 +166,6 @@ class Generator(AbstractGenerator): self.copyFile('Portlet.pt', self.repls, destName='%s.pt' % self.portletName, destFolder=self.skinsFolder) self.copyFile('tool.gif', {}) - self.copyFile( - 'global_statusmessage.pt', {}, destFolder=self.skinsFolder) self.copyFile('Styles.css.dtml',self.repls, destFolder=self.skinsFolder, destName = '%s.css.dtml' % self.applicationName) self.copyFile('IEFixes.css.dtml',self.repls,destFolder=self.skinsFolder) @@ -418,7 +413,7 @@ class Generator(AbstractGenerator): repls['appClasses'] = "[%s]" % ','.join(appClasses) repls['minimalistPlone'] = self.config.minimalistPlone repls['showPortlet'] = self.config.showPortlet - repls['appFrontPage'] = self.config.frontPage == True + repls['appFrontPage'] = bool(self.config.frontPage) repls['workflows'] = workflows self.copyFile('Install.py', repls, destFolder='Extensions') @@ -576,6 +571,27 @@ class Generator(AbstractGenerator): repls['modulesWithTests'] = ','.join(modules) self.copyFile('testAll.py', repls, destFolder='tests') + def generateFrontPage(self): + fp = self.config.frontPage + repls = self.repls.copy() + if fp == True: + # We need a front page, but no specific one has been given. + # So we will create a basic one that will simply display + # some translated text. + self.labels.append(msg('front_page_text', '', msg.FRONT_PAGE_TEXT)) + repls['pageContent'] = '' + else: + # The user has specified a macro to show. So in the generated front + # page, we will call this macro. The user will need to add itself + # a .pt file containing this macro in the skins folder of the + # generated Plone product. + page, macro = fp.split('/') + repls['pageContent'] = '' % (page, macro) + self.copyFile('frontPage.pt', repls, destFolder=self.skinsFolder, + destName='%sFrontPage.pt' % self.applicationName) + def generateTool(self): '''Generates the Plone tool that corresponds to this application.''' # Generate the tool class in itself and related i18n messages diff --git a/gen/plone25/mixins/ToolMixin.py b/gen/plone25/mixins/ToolMixin.py index 67581a2..0bf79cc 100644 --- a/gen/plone25/mixins/ToolMixin.py +++ b/gen/plone25/mixins/ToolMixin.py @@ -74,8 +74,16 @@ class ToolMixin(AbstractMixin): '''Returns the list of root classes for this application.''' return self.getProductConfig().rootClasses - def showPortlet(self): - return not self.portal_membership.isAnonymousUser() + def showPortlet(self, context): + if self.portal_membership.isAnonymousUser(): return False + if context.id == 'skyn': context = context.getParentNode() + res = True + if not self.getRootClasses(): + res = False + # If there is no root class, show the portlet only if we are within + # the configuration. + if (self.id in context.absolute_url()): res = True + return res def getObject(self, uid, appy=False): '''Allows to retrieve an object from its p_uid.''' @@ -630,7 +638,7 @@ class ToolMixin(AbstractMixin): contentType, flavourNumber = d1.split(':') flavourNumber = int(flavourNumber) searchName = keySuffix = d2 - batchSize = self.getNumberOfResultsPerPage() + batchSize = self.appy().numberOfResultsPerPage if not searchName: keySuffix = contentType s = self.REQUEST.SESSION searchKey = 'search_%s_%s' % (flavourNumber, keySuffix) diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py index e622093..240a38e 100644 --- a/gen/plone25/mixins/__init__.py +++ b/gen/plone25/mixins/__init__.py @@ -285,23 +285,23 @@ class AbstractMixin: '''Returns the method named p_methodName.''' return getattr(self, methodName, None) - def getFormattedValue(self, name, useParamValue=False, value=None, - forMasterId=False): + def getFieldValue(self, name, useParamValue=False, value=None, + formatted=True): '''Returns the value of field named p_name for this object (p_self). If p_useParamValue is True, the method uses p_value instead of the real field value (useful for rendering a value from the object history, for example). - If p_forMasterId is True, it returns the value as will be needed to - produce an identifier used within HTML pages for master/slave - relationships.''' + If p_formatted is False, it will return the true database + (or default) value. Else, it will produce a nice, string and + potentially translated value.''' appyType = self.getAppyType(name) # Which value will we use ? if not useParamValue: value = appyType.getValue(self) # Return the value as is if it is None or forMasterId - if forMasterId: return value + if not formatted: return value # Return the formatted value else return appyType.getFormattedValue(self, value) @@ -741,7 +741,7 @@ class AbstractMixin: res = brains return res - def fieldValueSelected(self, fieldName, vocabValue): + def fieldValueSelected(self, fieldName, vocabValue, dbValue): '''When displaying a selection box (ie a String with a validator being a list), must the _vocabValue appear as selected?''' rq = self.REQUEST @@ -749,14 +749,14 @@ class AbstractMixin: if rq.has_key(fieldName): compValue = rq.get(fieldName) else: - compValue = self.getAppyType(fieldName).getValue(self) + compValue = dbValue # Compare the value if type(compValue) in sequenceTypes: if vocabValue in compValue: return True else: if vocabValue == compValue: return True - def checkboxChecked(self, fieldName): + def checkboxChecked(self, fieldName, dbValue): '''When displaying a checkbox, must it be checked or not?''' rq = self.REQUEST # Get the value we must compare (from request or from database) @@ -764,11 +764,11 @@ class AbstractMixin: compValue = rq.get(fieldName) compValue = compValue in ('True', 1, '1') else: - compValue = self.getAppyType(fieldName).getValue(self) + compValue = dbValue # Compare the value return compValue - def dateValueSelected(self, fieldName, fieldPart, dateValue): + def dateValueSelected(self, fieldName, fieldPart, dateValue, dbValue): '''When displaying a date field, must the particular p_dateValue be selected in the field corresponding to the date part?''' # Get the value we must compare (from request or from database) @@ -779,7 +779,7 @@ class AbstractMixin: if compValue.isdigit(): compValue = int(compValue) else: - compValue = self.getAppyType(fieldName).getValue(self) + compValue = dbValue if compValue: compValue = getattr(compValue, fieldPart)() # Compare the value diff --git a/gen/plone25/skin/page.pt b/gen/plone25/skin/page.pt index 1ec4c9e..ae2708f 100644 --- a/gen/plone25/skin/page.pt +++ b/gen/plone25/skin/page.pt @@ -455,7 +455,7 @@ - diff --git a/gen/plone25/skin/portlet.pt b/gen/plone25/skin/portlet.pt index 8a0d6be..59e84c5 100644 --- a/gen/plone25/skin/portlet.pt +++ b/gen/plone25/skin/portlet.pt @@ -13,10 +13,10 @@
- - @@ -159,7 +159,7 @@
+ tal:attributes="class python: (len(phases) > 1) and ('appyPhase step%s' % phase['phaseStatus']) or 'appyPhase'">   diff --git a/gen/plone25/skin/widgets/date.pt b/gen/plone25/skin/widgets/date.pt index bd174be..c67856b 100644 --- a/gen/plone25/skin/widgets/date.pt +++ b/gen/plone25/skin/widgets/date.pt @@ -15,7 +15,7 @@ @@ -28,7 +28,7 @@ @@ -39,7 +39,7 @@ The icon for displaying the date chooser @@ -53,7 +53,7 @@ : @@ -64,7 +64,7 @@ diff --git a/gen/plone25/skin/widgets/show.pt b/gen/plone25/skin/widgets/show.pt index 68e445d..09213cf 100644 --- a/gen/plone25/skin/widgets/show.pt +++ b/gen/plone25/skin/widgets/show.pt @@ -54,7 +54,8 @@ tal:define="contextMacro python: portal.skyn.widgets; layout python: widget['layouts'][layoutType]; name widget/name; - value python: contextObj.getFormattedValue(name); + value python: contextObj.getFieldValue(name); + rawValue python: contextObj.getFieldValue(name, formatted=False); requestValue python: request.get(name, None); inRequest python: request.has_key(name); errors errors | python: (); diff --git a/gen/plone25/skin/widgets/string.pt b/gen/plone25/skin/widgets/string.pt index 516a0e6..cb37708 100644 --- a/gen/plone25/skin/widgets/string.pt +++ b/gen/plone25/skin/widgets/string.pt @@ -4,8 +4,7 @@ maxMult python: widget['multiplicity'][1]; severalValues python: (maxMult == None) or (maxMult > 1)"> + tal:attributes="class widget/master_css; id rawValue">
@@ -29,15 +28,17 @@ isOneLine python: fmt in (0,3)"> - @@ -83,7 +84,7 @@ validator defines a list of values, with a "AND/OR" checkbox. The "and" / "or" radio buttons - diff --git a/gen/plone25/templates/Portlet.pt b/gen/plone25/templates/Portlet.pt index 86e9154..f94e215 100644 --- a/gen/plone25/templates/Portlet.pt +++ b/gen/plone25/templates/Portlet.pt @@ -5,7 +5,7 @@
+ tal:condition="python: tool.showPortlet(context)">
(not this either :) */ +textarea { width: 99%; } + #portal-breadcrumbs { display: none; } #importedElem { color: grey; font-style: italic; } label { font-weight: bold; font-style: italic; } diff --git a/gen/plone25/templates/frontPage.pt b/gen/plone25/templates/frontPage.pt index 25cda4a..ec7a5ed 100644 --- a/gen/plone25/templates/frontPage.pt +++ b/gen/plone25/templates/frontPage.pt @@ -1,15 +1,11 @@ - -
- -
-
- + -
- +
+
diff --git a/gen/plone25/templates/global_statusmessage.pt b/gen/plone25/templates/global_statusmessage.pt deleted file mode 100644 index e38289c..0000000 --- a/gen/plone25/templates/global_statusmessage.pt +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/gen/po.py b/gen/po.py index 42209dd..e91eed0 100644 --- a/gen/po.py +++ b/gen/po.py @@ -248,10 +248,10 @@ class PoFile: keepExistingOrder=True): '''Updates the existing messages with p_newMessages. If p_removeNotNewMessages is True, all messages in self.messages - that are not in newMessages will be removed. If p_keepExistingOrder - is False, self.messages will be sorted according to p_newMessages. - Else, newMessages that are not yet in self.messages will be appended - to the end of self.messages.''' + that are not in newMessages will be removed, excepted if they start + with "custom_". If p_keepExistingOrder is False, self.messages will + be sorted according to p_newMessages. Else, newMessages that are not + yet in self.messages will be appended to the end of self.messages.''' # First, remove not new messages if necessary newIds = [m.id for m in newMessages] removedIds = [] @@ -259,7 +259,7 @@ class PoFile: i = len(self.messages)-1 while i >= 0: oldId = self.messages[i].id - if oldId not in newIds: + if not oldId.startswith('custom_') and (oldId not in newIds): del self.messages[i] del self.messagesDict[oldId] removedIds.append(oldId)