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.

This commit is contained in:
Gaetan Delannay 2010-08-12 11:56:42 +02:00
parent bfd2357f69
commit dbcadc506d
17 changed files with 188 additions and 126 deletions

View file

@ -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,29 +67,38 @@ 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)
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 = os.path.join(productsFolder, name)
destFolder = j(destBase, name)
# This is a Plone product. Copy it to the instance.
if (self.ploneVersion == 'plone25') and linksForProducts:
if linksForProducts:
# Create a symlink to this product in the instance
cmd = 'ln -s %s %s' % (folderName, destFolder)
os.system(cmd)
@ -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:
# <zopeInstance>/lib/python (ie, like Appy applications)
# - Zope products that will be copied in
# <zopeInstance>/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:

View file

@ -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: "<yourAppName>: 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

View file

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

View file

@ -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'] = '<span tal:replace="structure python: ' \
'tool.translateWithMapping(\'front_page_text\')"/>'
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'] = '<metal:call use-macro=' \
'"context/%s/macros/%s"/>' % (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

View file

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

View file

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

View file

@ -455,7 +455,7 @@
</tr>
<tr tal:repeat="change event/changes/items" valign="top">
<td tal:content="python: tool.translate(change[1][1])"></td>
<td tal:define="appyValue python: contextObj.getFormattedValue(change[0], useParamValue=True, value=change[1][0]);
<td tal:define="appyValue python: contextObj.getFieldValue(change[0], useParamValue=True, value=change[1][0]);
appyType python:contextObj.getAppyType(change[0], asDict=True);
severalValues python: (appyType['multiplicity'][1] &gt; 1) or (appyType['multiplicity'][1] == None)">
<span tal:condition="not: severalValues" tal:replace="appyValue"></span>

View file

@ -13,10 +13,10 @@
<table cellpadding="0" cellspacing="0" width="100%">
<tr>
<td>
<a tal:condition="python: len(flavours)==1"
<a tal:condition="python: len(flavours)==1 and rootClasses"
tal:attributes="href python:'%s?type_name=%s&flavourNumber=1' % (queryUrl, ','.join(rootClasses))"
tal:content="python: tool.translate(appName)"></a>
<span tal:condition="python: len(flavours)&gt;1"
<span tal:condition="python: len(flavours)&gt;1 or not rootClasses"
tal:replace="python: tool.translate(appName)"/>
</td>
<td align="right">
@ -159,7 +159,7 @@
<tr>
<td tal:define="label python:'%s_phase_%s' % (contextObj.meta_type, phase['name']);
displayLink python: (phase['phaseStatus'] != 'Future') and ('/portal_factory' not in contextObj.absolute_url()) and (len(phase['pages']) == 1)"
tal:attributes="class python: 'appyPhase step' + phase['phaseStatus']">
tal:attributes="class python: (len(phases) &gt; 1) and ('appyPhase step%s' % phase['phaseStatus']) or 'appyPhase'">
<span class="portletGroup" tal:condition="python: len(phases) &gt; 1">
<a tal:attributes="href python: '%s?page=%s' % (contextObj.getUrl(), phase['pages'][0]);"
tal:condition="displayLink"

View file

@ -6,12 +6,12 @@
<input type="checkbox"
tal:attributes="name python: name + '_visible';
id name;
checked python:contextObj.checkboxChecked(name);
checked python:contextObj.checkboxChecked(name, rawValue);
onClick python:'toggleCheckbox(\'%s\', \'%s_hidden\');;updateSlaves(getMasterValue(this), \'%s\')' % (name, name, widget['id']);
class python: 'noborder ' + widget['master_css']"/>
<input tal:attributes="name name;
id string:${name}_hidden;
value python: test(contextObj.checkboxChecked(name), 'True', 'False')"
value python: test(contextObj.checkboxChecked(name, rawValue), 'True', 'False')"
type="hidden" />&nbsp;
</metal:edit>

View file

@ -15,7 +15,7 @@
<tal:days repeat="day days">
<option tal:define="zDay python: str(day).zfill(2)"
tal:attributes="value zDay;
selected python:contextObj.dateValueSelected(name, 'day', day)"
selected python:contextObj.dateValueSelected(name, 'day', day, rawValue)"
tal:content="zDay"></option>
</tal:days>
</select>
@ -28,7 +28,7 @@
<tal:months repeat="month months">
<option tal:define="zMonth python: str(month).zfill(2)"
tal:attributes="value zMonth;
selected python:contextObj.dateValueSelected(name, 'month', month)"
selected python:contextObj.dateValueSelected(name, 'month', month, rawValue)"
tal:content="zMonth"></option>
</tal:months>
</select>
@ -39,7 +39,7 @@
<option value="">-</option>
<option tal:repeat="year years"
tal:attributes="value year;
selected python:contextObj.dateValueSelected(name, 'year', year)"
selected python:contextObj.dateValueSelected(name, 'year', year, rawValue)"
tal:content="year"></option>
</select>
<tal:comment replace="nothing">The icon for displaying the date chooser</tal:comment>
@ -53,7 +53,7 @@
<tal:hours repeat="hour hours">
<option tal:define="zHour python: str(hour).zfill(2)"
tal:attributes="value zHour;
selected python:contextObj.dateValueSelected(name, 'hour', hour)"
selected python:contextObj.dateValueSelected(name, 'hour', hour, rawValue)"
tal:content="zHour"></option>
</tal:hours>
</select> :
@ -64,7 +64,7 @@
<tal:minutes repeat="minute minutes">
<option tal:define="zMinute python: str(minute).zfill(2)"
tal:attributes="value zMinute;
selected python:contextObj.dateValueSelected(name, 'minute', minute)"
selected python:contextObj.dateValueSelected(name, 'minute', minute, rawValue)"
tal:content="zMinute"></option>
</tal:minutes>
</select>

View file

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

View file

@ -4,8 +4,7 @@
maxMult python: widget['multiplicity'][1];
severalValues python: (maxMult == None) or (maxMult &gt; 1)">
<span tal:condition="python: fmt in (0, 3)"
tal:attributes="class widget/master_css;
id python: contextObj.getFormattedValue(name, forMasterId=True)">
tal:attributes="class widget/master_css; id rawValue">
<ul class="appyList" tal:condition="python: value and severalValues">
<li class="appyBullet" tal:repeat="sv value"><i tal:content="structure sv"></i></li>
</ul>
@ -29,15 +28,17 @@
isOneLine python: fmt in (0,3)">
<tal:choice condition="isSelect">
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True)"
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True);
multiValued python: (widget['multiplicity'][1] != 1) and True or False"
tal:attributes="name name;
id name;
multiple python: test(widget['multiplicity'][1] != 1, 'multiple', '');
onchange python: test(isMaster, 'javascript:updateSlaves(getMasterValue(this), \'%s\')' % widget['id'], '');
class widget/master_css">
multiple python: multiValued and 'multiple' or '';
onchange python: isMaster and ('javascript:updateSlaves(getMasterValue(this), \'%s\')' % widget['id']) or '';
class widget/master_css;
size python: multiValued and widget['height'] or 1">
<option tal:repeat="possibleValue possibleValues"
tal:attributes="value python: possibleValue[0];
selected python:contextObj.fieldValueSelected(name, possibleValue[0])"
selected python:contextObj.fieldValueSelected(name, possibleValue[0], rawValue)"
tal:content="python:tool.truncateValue(possibleValue[1], widget)"></option>
</select>
</tal:choice>
@ -83,7 +84,7 @@
validator defines a list of values, with a "AND/OR" checkbox.</tal:comment>
<tal:selectSearch condition="widget/isSelect">
<tal:comment replace="nothing">The "and" / "or" radio buttons</tal:comment>
<tal:operator define="operName python: 'o_%s' % fieldName;
<tal:operator define="operName python: 'o_%s' % name;
orName python: '%s_or' % operName;
andName python: '%s_and' % operName;"
condition="python: widget['multiplicity'][1]!=1">

View file

@ -5,7 +5,7 @@
<div metal:define-macro="portlet"
tal:define="tool python: context.<!toolInstanceName!>;
flavour python: tool.getFlavour(tool);"
tal:condition="tool/showPortlet">
tal:condition="python: tool.showPortlet(context)">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<dl tal:define="rootClasses tool/getRootClasses;

View file

@ -2,6 +2,8 @@
/* <dtml-with base_properties> (do not remove this :) */
/* <dtml-call "REQUEST.set('portal_url', portal_url())"> (not this either :) */
textarea { width: 99%; }
#portal-breadcrumbs { display: none; }
#importedElem { color: grey; font-style: italic; }
label { font-weight: bold; font-style: italic; }

View file

@ -1,15 +1,11 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
metal:use-macro="here/main_template/macros/master" i18n:domain="<!applicationName!>">
<!-- Disable standard Plone green tabs -->
<div metal:fill-slot="top_slot">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<metal:disable fill-slot="top_slot"
tal:define="dummy python:request.set('disable_border',1)" />
<body>
<div metal:fill-slot="main">
<span tal:replace="structure python: context.<!toolInstanceName!>.translateWithMapping('front_page_text')"/>
<div metal:fill-slot="main" tal:define="tool python: context.<!toolInstanceName!>">
<!pageContent!>
</div>
</body>
</html>

View file

@ -1,5 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" i18n:domain="plone">
<body>
<tal:message metal:define-macro="portal_message"></tal:message>
</body>
</html>

View file

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