Two important bugfixes: one security-related, one linked to Ref fields with link=True.

This commit is contained in:
Gaetan Delannay 2010-08-27 08:59:53 +02:00
parent dbcadc506d
commit fa974239f3
12 changed files with 149 additions and 138 deletions

1
bin/new.py Executable file → Normal file
View file

@ -1,4 +1,3 @@
#!/usr/bin/python2.4.4
'''This script allows to create a brand new read-to-use Plone/Zone instance. '''This script allows to create a brand new read-to-use Plone/Zone instance.
As prerequisite, you must have installed Plone through the Unifier installer As prerequisite, you must have installed Plone through the Unifier installer
available at http://plone.org.''' available at http://plone.org.'''

View file

@ -30,7 +30,7 @@ class Group:
'''Used for describing a group of widgets within a page.''' '''Used for describing a group of widgets within a page.'''
def __init__(self, name, columns=['100%'], wide=True, style='fieldset', def __init__(self, name, columns=['100%'], wide=True, style='fieldset',
hasLabel=True, hasDescr=False, hasHelp=False, hasLabel=True, hasDescr=False, hasHelp=False,
hasHeaders=False, group=None, colspan=1): hasHeaders=False, group=None, colspan=1, valign='top'):
self.name = name self.name = name
# In its simpler form, field "columns" below can hold a list or tuple # In its simpler form, field "columns" below can hold a list or tuple
# of column widths expressed as strings, that will be given as is in # of column widths expressed as strings, that will be given as is in
@ -65,6 +65,7 @@ class Group:
# If the group is rendered into another group, we can specify the number # If the group is rendered into another group, we can specify the number
# of columns that this group will span. # of columns that this group will span.
self.colspan = colspan self.colspan = colspan
self.valign = valign
if style == 'tabs': if style == 'tabs':
# Group content will be rendered as tabs. In this case, some # Group content will be rendered as tabs. In this case, some
# param combinations have no sense. # param combinations have no sense.
@ -987,7 +988,8 @@ class Date(Type):
hourParts = ('hour', 'minute') hourParts = ('hour', 'minute')
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, default=None, optional=False, editDefault=False,
format=WITH_HOUR, startYear=time.localtime()[0]-10, format=WITH_HOUR, calendar=True,
startYear=time.localtime()[0]-10,
endYear=time.localtime()[0]+10, show=True, page='main', endYear=time.localtime()[0]+10, show=True, page='main',
group=None, layouts=None, move=0, indexed=False, group=None, layouts=None, move=0, indexed=False,
searchable=False, specificReadPermission=False, searchable=False, specificReadPermission=False,
@ -995,6 +997,7 @@ class Date(Type):
colspan=1, master=None, masterValue=None, focus=False, colspan=1, master=None, masterValue=None, focus=False,
historized=False): historized=False):
self.format = format self.format = format
self.calendar = calendar
self.startYear = startYear self.startYear = startYear
self.endYear = endYear self.endYear = endYear
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, index, default, optional,
@ -1164,12 +1167,14 @@ class Ref(Type):
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
def isShowable(self, obj, layoutType): def isShowable(self, obj, layoutType):
res = Type.isShowable(self, obj, layout)
if not res: return res
if (layoutType == 'edit') and self.add: return False if (layoutType == 'edit') and self.add: return False
if self.isBack: if self.isBack:
if layoutType == 'edit': return False if layoutType == 'edit': return False
else: else:
return obj.getBRefs(self.relationship) return obj.getBRefs(self.relationship)
return Type.isShowable(self, obj, layout) return True
def getValue(self, obj): def getValue(self, obj):
if self.isBack: if self.isBack:

View file

@ -148,6 +148,9 @@ class Generator(AbstractGenerator):
msg('file_required', '', msg.FILE_REQUIRED), msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED), msg('image_required', '', msg.IMAGE_REQUIRED),
] ]
# Create a label for every role added by this application
for role in self.getAllUsedRoles(appOnly=True):
self.labels.append(msg('role_%s' % role,'', role, 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.generateConfig()
@ -224,7 +227,7 @@ class Generator(AbstractGenerator):
if not poFile.generated: if not poFile.generated:
poFile.generate() poFile.generate()
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer') ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous')
def getAllUsedRoles(self, appOnly=False): def getAllUsedRoles(self, appOnly=False):
'''Produces a list of all the roles used within all workflows and '''Produces a list of all the roles used within all workflows and
classes defined in this application. If p_appOnly is True, it classes defined in this application. If p_appOnly is True, it

View file

@ -133,7 +133,6 @@ class PloneInstaller:
if not hasattr(site.portal_types, self.appyFolderType): if not hasattr(site.portal_types, self.appyFolderType):
self.registerAppyFolderType() self.registerAppyFolderType()
# Create the folder # Create the folder
if not hasattr(site.aq_base, self.productName): if not hasattr(site.aq_base, self.productName):
# Temporarily allow me to create Appy large plone folders # Temporarily allow me to create Appy large plone folders
getattr(site.portal_types, self.appyFolderType).global_allow = 1 getattr(site.portal_types, self.appyFolderType).global_allow = 1
@ -144,27 +143,29 @@ class PloneInstaller:
title=self.productName) title=self.productName)
getattr(site.portal_types, self.appyFolderType).global_allow = 0 getattr(site.portal_types, self.appyFolderType).global_allow = 0
appFolder = getattr(site, self.productName) appFolder = getattr(site, self.productName)
# All roles defined as creators should be able to create the # All roles defined as creators should be able to create the
# corresponding root content types in this folder. # corresponding root content types in this folder.
i = -1 i = -1
allCreators = set() allCreators = set()
for klass in self.appClasses: for klass in self.appClasses:
i += 1 i += 1
if klass.__dict__.has_key('root') and klass.__dict__['root']: if not klass.__dict__.has_key('root') or not klass.__dict__['root']:
# It is a root class. continue # It is not a root class
creators = getattr(klass, 'creators', None) creators = getattr(klass, 'creators', None)
if not creators: creators = self.defaultAddRoles if not creators: creators = self.defaultAddRoles
allCreators = allCreators.union(creators) allCreators = allCreators.union(creators)
className = self.appClassNames[i] className = self.appClassNames[i]
updateRolesForPermission(self.getAddPermission(className), permission = self.getAddPermission(className)
tuple(creators), appFolder) updateRolesForPermission(permission, tuple(creators), appFolder)
# Beyond content-type-specific "add" permissions, creators must also # Beyond content-type-specific "add" permissions, creators must also
# have the main permission "Add portal content". # have the main permission "Add portal content".
updateRolesForPermission('Add portal content', tuple(allCreators), permission = 'Add portal content'
appFolder) updateRolesForPermission(permission, tuple(allCreators), appFolder)
# Creates the "appy" Directory view # Creates the "appy" Directory view
if not hasattr(site.aq_base, 'skyn'): if hasattr(site.aq_base, 'skyn'):
site.manage_delObjects(['skyn'])
# This way, if Appy has moved from one place to the other, the
# directory view will always refer to the correct place.
addDirView = self.ploneStuff['manage_addDirectoryView'] addDirView = self.ploneStuff['manage_addDirectoryView']
addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn') addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn')

View file

@ -263,39 +263,6 @@ class ToolMixin(AbstractMixin):
return value[:maxWidth] + '...' return value[:maxWidth] + '...'
return value return value
xhtmlToText = re.compile('<.*?>', re.S)
def getReferenceLabel(self, brain, appyType):
'''p_appyType is a Ref with link=True. I need to display, on an edit
view, the referenced object p_brain in the listbox that will allow
the user to choose which object(s) to link through the Ref.
According to p_appyType, the label may only be the object title,
or more if parameter appyType.shownInfo is used.'''
res = brain.Title
if 'title' in appyType['shownInfo']:
# We may place it at another place
res = ''
appyObj = brain.getObject().appy()
for fieldName in appyType['shownInfo']:
value = getattr(appyObj, fieldName)
if isinstance(value, AbstractWrapper):
value = value.title.decode('utf-8')
elif isinstance(value, basestring):
value = value.decode('utf-8')
refAppyType = appyObj.o.getAppyType(fieldName)
if refAppyType and (refAppyType.type == 'String') and \
(refAppyType.format == 2):
value = self.xhtmlToText.sub(' ', value)
else:
value = str(value)
prefix = ''
if res:
prefix = ' | '
res += prefix + value.encode('utf-8')
maxWidth = self.getListBoxesMaximumWidth()
if len(res) > maxWidth:
res = res[:maxWidth-2] + '...'
return res
translationMapping = {'portal_path': ''} translationMapping = {'portal_path': ''}
def translateWithMapping(self, label): def translateWithMapping(self, label):
'''Translates p_label in the application domain, with a default '''Translates p_label in the application domain, with a default

View file

@ -351,17 +351,61 @@ class AbstractMixin:
res.objects = res.objects[0] res.objects = res.objects[0]
return res return res
def getAppyRefs(self, appyType, startNumber=None): def getAppyRefs(self, name, startNumber=None):
'''Gets the objects linked to me through Ref p_appyType. '''Gets the objects linked to me through Ref field named p_name.
If p_startNumber is None, this method returns all referred objects. If p_startNumber is None, this method returns all referred objects.
If p_startNumber is a number, this method will return x objects, If p_startNumber is a number, this method will return x objects,
starting at p_startNumber, x being appyType.maxPerPage.''' starting at p_startNumber, x being appyType.maxPerPage.'''
if not appyType['isBack']: appyType = self.getAppyType(name)
return self._appy_getRefs(appyType['name'], ploneObjects=True, if not appyType.isBack:
return self._appy_getRefs(name, ploneObjects=True,
startNumber=startNumber).__dict__ startNumber=startNumber).__dict__
else: else:
# Note Pagination is not yet implemented for backward refs. # Note that pagination is not yet implemented for backward refs.
return SomeObjects(self.getBRefs(appyType['relationship'])).__dict__ return SomeObjects(self.getBRefs(appyType.relationship)).__dict__
def getSelectableAppyRefs(self, name):
'''p_name is the name of a Ref field. This method returns the list of
all objects that can be selected to be linked as references to p_self
through field p_name.'''
appyType = self.getAppyType(name)
if not appyType.select:
# No select method has been defined: we must retrieve all objects
# of the referred type that the user is allowed to access.
return self.appy().search(appyType.klass)
else:
return appyType.select(self.appy())
xhtmlToText = re.compile('<.*?>', re.S)
def getReferenceLabel(self, name, refObject):
'''p_name is the name of a Ref field with link=True. I need to display,
on an edit view, the p_refObject in the listbox that will allow
the user to choose which object(s) to link through the Ref.
The information to display may only be the object title or more if
field.shownInfo is used.'''
appyType = self.getAppyType(name)
res = refObject.title
if 'title' in appyType.shownInfo:
# We may place it at another place
res = ''
for fieldName in appyType.shownInfo:
refType = refObject.o.getAppyType(fieldName)
value = getattr(refObject, fieldName)
value = refType.getFormattedValue(refObject.o, value)
if (refType.type == 'String') and (refType.format == 2):
value = self.xhtmlToText.sub(' ', value)
prefix = ''
if res:
prefix = ' | '
res += prefix + value
maxWidth = appyType.width or 30
if len(res) > maxWidth:
res = res[:maxWidth-2] + '...'
return res
def getReferenceUid(self, refObject):
'''Returns the UID of referred object p_refObject.'''
return refObject.o.UID()
def getAppyRefIndex(self, fieldName, obj): def getAppyRefIndex(self, fieldName, obj):
'''Gets the position of p_obj within Ref field named p_fieldName.''' '''Gets the position of p_obj within Ref field named p_fieldName.'''
@ -726,20 +770,9 @@ class AbstractMixin:
self.reindexObject() self.reindexObject()
return self.goto(urlBack) return self.goto(urlBack)
def callAppySelect(self, selectMethod, brains): def getFlavour(self):
'''Selects objects from a Reference field.''' '''Returns the flavour corresponding to this object.'''
if selectMethod: return self.getTool().getFlavour(self.portal_type)
obj = self.appy()
allObjects = [b.getObject().appy() for b in brains]
filteredObjects = selectMethod(obj, allObjects)
filteredUids = [o.o.UID() for o in filteredObjects]
res = []
for b in brains:
if b.UID in filteredUids:
res.append(b)
else:
res = brains
return res
def fieldValueSelected(self, fieldName, vocabValue, dbValue): def fieldValueSelected(self, fieldName, vocabValue, dbValue):
'''When displaying a selection box (ie a String with a validator being a '''When displaying a selection box (ie a String with a validator being a
@ -888,31 +921,38 @@ class AbstractMixin:
folder = self.getParentNode() folder = self.getParentNode()
# On this folder, set "add" permissions for every content type that will # On this folder, set "add" permissions for every content type that will
# be created through reference fields # be created through reference fields
allCreators = set() allCreators = {} # One key for every add permission
addPermissions = self.getProductConfig().ADD_CONTENT_PERMISSIONS
for appyType in self.getAllAppyTypes(): for appyType in self.getAllAppyTypes():
if appyType.type == 'Ref': if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: continue
# Indeed, no possibility to create objects with such Ref
refContentTypeName = self.getAppyRefPortalType(appyType.name) refContentTypeName = self.getAppyRefPortalType(appyType.name)
refContentType = getattr(self.portal_types, refContentTypeName) refContentType = getattr(self.portal_types, refContentTypeName)
refMetaType = refContentType.content_meta_type refMetaType = refContentType.content_meta_type
if refMetaType in self.getProductConfig(\ if refMetaType not in addPermissions: continue
).ADD_CONTENT_PERMISSIONS: # Indeed, there is no specific "add" permission is defined for tool
# No specific "add" permission is defined for tool and # and flavour, for example.
# flavour, for example.
appyClass = refContentType.wrapperClass.__bases__[-1] appyClass = refContentType.wrapperClass.__bases__[-1]
# Get roles that may add this content type # Get roles that may add this content type
creators = getattr(appyClass, 'creators', None) creators = getattr(appyClass, 'creators', None)
if not creators: if not creators:
creators = self.getProductConfig().defaultAddRoles creators = self.getProductConfig().defaultAddRoles
allCreators = allCreators.union(creators) # Add those creators to the list of creators for this meta_type
# Grant this "add" permission to those roles addPermission = addPermissions[refMetaType]
updateRolesForPermission( if addPermission in allCreators:
self.getProductConfig().ADD_CONTENT_PERMISSIONS[\ allCreators[addPermission] = allCreators[\
refMetaType], creators, folder) addPermission].union(creators)
else:
allCreators[addPermission] = set(creators)
# Update the permissions
for permission, creators in allCreators.iteritems():
updateRolesForPermission(permission, tuple(creators), folder)
# Beyond content-type-specific "add" permissions, creators must also # Beyond content-type-specific "add" permissions, creators must also
# have the main permission "Add portal content". # have the main permission "Add portal content".
if allCreators: permission = 'Add portal content'
updateRolesForPermission('Add portal content', tuple(allCreators), for creators in allCreators.itervalues():
folder) updateRolesForPermission(permission, tuple(creators), folder)
def _appy_getPortalType(self, request): def _appy_getPortalType(self, request):
'''Guess the portal_type of p_self from info about p_self and '''Guess the portal_type of p_self from info about p_self and

View file

@ -17,7 +17,7 @@
tool python: portal.get('portal_%s' % appName.lower()); tool python: portal.get('portal_%s' % appName.lower());
contentType python:context.REQUEST.get('type_name'); contentType python:context.REQUEST.get('type_name');
flavour python: tool.getFlavour(contentType); flavour python: tool.getFlavour(contentType);
flavourNumber python:int(context.REQUEST.get('flavourNumber')); flavourNumber python:int(context.REQUEST.get('flavourNumber', 1));
searchName python:context.REQUEST.get('search', '')"> searchName python:context.REQUEST.get('search', '')">
<div metal:use-macro="here/skyn/page/macros/prologue"/> <div metal:use-macro="here/skyn/page/macros/prologue"/>

View file

@ -42,8 +42,9 @@
selected python:contextObj.dateValueSelected(name, 'year', year, rawValue)" selected python:contextObj.dateValueSelected(name, 'year', year, rawValue)"
tal:content="year"></option> tal:content="year"></option>
</select> </select>
<tal:comment replace="nothing">The icon for displaying the date chooser</tal:comment> <tal:comment replace="nothing">The icon for displaying the calendar (=date chooser)</tal:comment>
<a tal:attributes="onclick python: 'return showJsCalendar(\'%s_month\', \'%s\', \'%s_year\', \'%s_month\', \'%s_day\', null, null, %d, %d)' % (name, dummyName, name, name, name, years[0], years[-1])"><img tal:attributes="src string: $portal_url/popup_calendar.gif"/></a> <a tal:condition="widget/calendar"
tal:attributes="onclick python: 'return showJsCalendar(\'%s_month\', \'%s\', \'%s_year\', \'%s_month\', \'%s_day\', null, null, %d, %d)' % (name, dummyName, name, name, name, years[0], years[-1])"><img tal:attributes="src string: $portal_url/popup_calendar.gif"/></a>
<tal:hour condition="python: widget['format'] == 0"> <tal:hour condition="python: widget['format'] == 0">
<select tal:define="hours python:range(0,24);" <select tal:define="hours python:range(0,24);"

View file

@ -9,9 +9,10 @@
on a forward reference, the "nav" parameter is added to the URL for allowing to navigate on a forward reference, the "nav" parameter is added to the URL for allowing to navigate
from one object to the next/previous on skyn/view.</tal:comment> from one object to the next/previous on skyn/view.</tal:comment>
<a tal:define="viewUrl obj/getUrl; <a tal:define="viewUrl obj/getUrl;
includeShownInfo includeShownInfo | python:False;
navInfo python:'nav=ref.%s.%s.%d.%d' % (contextObj.UID(), fieldName, repeat['obj'].number()+startNumber, totalNumber); navInfo python:'nav=ref.%s.%s.%d.%d' % (contextObj.UID(), fieldName, repeat['obj'].number()+startNumber, totalNumber);
fullUrl python: test(appyType['isBack'], viewUrl + '/?page=%s' % appyType['page'], viewUrl + '/?' + navInfo)" fullUrl python: appyType['isBack'] and (viewUrl + '/?page=%s' % appyType['backd']['page']) or (viewUrl + '/?' + navInfo)"
tal:attributes="href fullUrl" tal:content="obj/Title"></a> tal:attributes="href fullUrl" tal:content="python: (not includeShownInfo) and obj.Title() or contextObj.getReferenceLabel(fieldName, obj.appy())"></a>
</metal:objectTitle> </metal:objectTitle>
<metal:objectActions define-macro="objectActions"> <metal:objectActions define-macro="objectActions">
@ -99,7 +100,7 @@
ajaxHookId python: contextObj.UID()+fieldName; ajaxHookId python: contextObj.UID()+fieldName;
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0)); startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
tool contextObj/getTool; tool contextObj/getTool;
refObjects python:contextObj.getAppyRefs(appyType, startNumber); refObjects python:contextObj.getAppyRefs(fieldName, startNumber);
objs refObjects/objects; objs refObjects/objects;
totalNumber refObjects/totalNumber; totalNumber refObjects/totalNumber;
batchSize refObjects/batchSize; batchSize refObjects/batchSize;
@ -126,7 +127,8 @@
<tal:comment replace="nothing">Display a simplified widget if maximum number of <tal:comment replace="nothing">Display a simplified widget if maximum number of
referenced objects is 1.</tal:comment> referenced objects is 1.</tal:comment>
<table class="no-style-table" cellpadding="0" cellspacing="0"><tr valign="top"> <table class="no-style-table" cellpadding="0" cellspacing="0"><tr valign="top">
<td><span class="appyLabel" tal:condition="not: innerRef" tal:content="structure label"></span></td> <td><span class="appyLabel" tal:condition="python: not innerRef and not appyType['link']"
tal:content="structure label"></span></td>
<tal:comment replace="nothing">If there is no object...</tal:comment> <tal:comment replace="nothing">If there is no object...</tal:comment>
<tal:noObject condition="not:objs"> <tal:noObject condition="not:objs">
@ -135,9 +137,9 @@
</tal:noObject> </tal:noObject>
<tal:comment replace="nothing">If there is an object...</tal:comment> <tal:comment replace="nothing">If there is an object...</tal:comment>
<tal:objectIsPresent condition="python: len(objs) == 1"> <tal:objectIsPresent condition="objs">
<tal:obj repeat="obj objs"> <tal:obj repeat="obj objs">
<td><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" /></td> <td tal:define="includeShownInfo python:True"><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" /></td>
<td tal:condition="not: appyType/isBack"> <td tal:condition="not: appyType/isBack">
<metal:showObjectActions use-macro="portal/skyn/widgets/ref/macros/objectActions" /> <metal:showObjectActions use-macro="portal/skyn/widgets/ref/macros/objectActions" />
</td> </td>
@ -240,31 +242,25 @@
</div> </div>
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment> <tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
<div define-macro="edit" <div metal:define-macro="edit"
tal:condition="widget/link" tal:condition="widget/link"
tal:define="refPortalType python: contextObj.getAppyRefPortalType(name); tal:define="rname python: 'appy_ref_%s' % name;
allBrains python:here.uid_catalog(portal_type=refPortalType); requestValue python: request.get(rname, []);
brains python:contextObj.callAppySelect(widget['select'], allBrains); inRequest python: request.has_key(rname);
allObjects python: contextObj.getSelectableAppyRefs(name);
refUids python: [o.UID() for o in here.getAppyRefs(name)['objects']]; refUids python: [o.UID() for o in here.getAppyRefs(name)['objects']];
isMultiple python:test(widget['multiplicity'][1]!=1, 'multiple', ''); isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
appyFieldName python: 'appy_ref_%s' % name;
inError python:test(errors.has_key(name), True, False);
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())"
tal:attributes="class python:'appyRefEdit field' + test(inError, ' error', '')">
<tal:comment replace="nothing">This macro displays the Reference widget on an "edit" page</tal:comment> <select tal:attributes="name rname;
multiple python: isMultiple and 'multiple' or ''">
<label tal:attributes="for python:appyFieldName" tal:content="label"></label>&nbsp; <option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
<span class="fieldRequired" tal:condition="python: appyType['multiplicity'][0]&gt;0"></span><br/> <tal:ref repeat="refObj allObjects">
<div tal:condition="inError" tal:content="python: errors[field.getName()]"></div> <option tal:define="uid python: contextObj.getReferenceUid(refObj)"
<select tal:define="valueIsInReq python:test(request.get(appyFieldName, None) != None, True, False)" tal:content="python: contextObj.getReferenceLabel(name, refObj)"
tal:attributes="name python:'appy_ref_%s' % field.getName(); tal:attributes="value uid;
multiple isMultiple"> selected python:(inRequest and (uid in requestValue) or (not inRequest and ((uid in refUids)))) and True or False">
<option tal:condition="not: isMultiple" value="" i18n:translate="choose_a_value"/> </option>
<option tal:repeat="brain brains" </tal:ref>
tal:content="python: tool.getReferenceLabel(brain, appyType)"
tal:attributes="value brain/UID;
selected python:test((valueIsInReq and (brain.UID in request.get(appyFieldName, []))) or (not valueIsInReq and ((brain.UID in refUids) or (isBeingCreated and (brain.UID==defaultValueUID)))), True, False)"/>
</select> </select>
</div> </div>

View file

@ -59,7 +59,9 @@
requestValue python: request.get(name, None); requestValue python: request.get(name, None);
inRequest python: request.has_key(name); inRequest python: request.has_key(name);
errors errors | python: (); errors errors | python: ();
inError python: test(widget['name'] in errors, True, False)"> inError python: (widget['name'] in errors) and True or False;
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] &gt; 1)">
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/> <metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
</metal:field> </metal:field>
@ -89,7 +91,7 @@
<tal:asTabs condition="python: widget['style'] == 'tabs'"> <tal:asTabs condition="python: widget['style'] == 'tabs'">
<table cellpadding="0" cellspacing="0" tal:attributes="width python: test(widget['wide'], '100%', '')"> <table cellpadding="0" cellspacing="0" tal:attributes="width python: test(widget['wide'], '100%', '')">
<tal:comment replace="nothing">First row: the tabs.</tal:comment> <tal:comment replace="nothing">First row: the tabs.</tal:comment>
<tr><td style="border-bottom: 1px solid #ff8040"> <tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
<table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;"> <table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;">
<tr valign="bottom"> <tr valign="bottom">
<tal:tab repeat="widgetRow widget/widgets"> <tal:tab repeat="widgetRow widget/widgets">
@ -157,7 +159,7 @@
</th> </th>
</tr> </tr>
<tal:comment replace="nothing">The rows of widgets</tal:comment> <tal:comment replace="nothing">The rows of widgets</tal:comment>
<tr valign="top" tal:repeat="widgetRow widget/widgets"> <tr tal:attributes="valign widget/valign" tal:repeat="widgetRow widget/widgets">
<td tal:repeat="widget widgetRow" <td tal:repeat="widget widgetRow"
tal:attributes="colspan widget/colspan|python:1; tal:attributes="colspan widget/colspan|python:1;
style python: test(repeat['widget'].number() != len(widgetRow), 'padding-right: 0.6em', '')"> style python: test(repeat['widget'].number() != len(widgetRow), 'padding-right: 0.6em', '')">
@ -176,7 +178,7 @@
<tal:comment replace="nothing">Displays a field label.</tal:comment> <tal:comment replace="nothing">Displays a field label.</tal:comment>
<tal:label metal:define-macro="label" condition="widget/hasLabel"> <tal:label metal:define-macro="label" condition="widget/hasLabel">
<label tal:attributes="for widget/name" <label tal:attributes="for widget/name"
tal:condition="python: widget['type'] not in ('Action', 'Ref')" tal:condition="python: not ((widget['type'] == 'Action') or ((widget['type'] == 'Ref') and (widget['add'])))"
tal:content="structure python: contextObj.translate(widget['labelId'])"></label> tal:content="structure python: contextObj.translate(widget['labelId'])"></label>
</tal:label> </tal:label>

View file

@ -1,14 +1,12 @@
<tal:comment replace="nothing">View macro for a String.</tal:comment> <tal:comment replace="nothing">View macro for a String.</tal:comment>
<metal:view define-macro="view" <metal:view define-macro="view"
tal:define="fmt widget/format; tal:define="fmt widget/format">
maxMult python: widget['multiplicity'][1];
severalValues python: (maxMult == None) or (maxMult &gt; 1)">
<span tal:condition="python: fmt in (0, 3)" <span tal:condition="python: fmt in (0, 3)"
tal:attributes="class widget/master_css; id rawValue"> tal:attributes="class widget/master_css; id rawValue">
<ul class="appyList" tal:condition="python: value and severalValues"> <ul class="appyList" tal:condition="python: value and isMultiple">
<li class="appyBullet" tal:repeat="sv value"><i tal:content="structure sv"></i></li> <li class="appyBullet" tal:repeat="sv value"><i tal:content="structure sv"></i></li>
</ul> </ul>
<tal:singleValue condition="python: value and not severalValues"> <tal:singleValue condition="python: value and not isMultiple">
<span tal:condition="python: fmt != 3" tal:replace="structure value"/> <span tal:condition="python: fmt != 3" tal:replace="structure value"/>
<span tal:condition="python: fmt == 3">********</span> <span tal:condition="python: fmt == 3">********</span>
</tal:singleValue> </tal:singleValue>

View file

@ -14,7 +14,6 @@ label { font-weight: bold; font-style: italic; }
.appyNav { padding: 0.4em 0 0.4em 0; } .appyNav { padding: 0.4em 0 0.4em 0; }
.appyFocus { color: #900101; } .appyFocus { color: #900101; }
.appyTitle { padding-top: 0.5em; font-size: 110%; } .appyTitle { padding-top: 0.5em; font-size: 110%; }
.appyRefEdit { line-height: 1.5em; }
.appyWorkflow { text-align: center; background-color: &dtml-globalBackgroundColor;;} .appyWorkflow { text-align: center; background-color: &dtml-globalBackgroundColor;;}
.appyPlusImg { .appyPlusImg {