Worflow state can not be used in ref fields; mayNavigate is not static anymore, but an instance method.

This commit is contained in:
Gaetan Delannay 2010-12-06 11:11:40 +01:00
parent e62e00d367
commit 2124cffa5e
14 changed files with 187 additions and 161 deletions

View file

@ -90,7 +90,8 @@ class Group:
def __init__(self, name, columns=['100%'], wide=True, style='section2',
hasLabel=True, hasDescr=False, hasHelp=False,
hasHeaders=False, group=None, colspan=1, align='center',
valign='top', css_class='', master=None, masterValue=None):
valign='top', css_class='', master=None, masterValue=None,
cellpadding=1, cellspacing=1):
self.name = name
# 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
@ -127,6 +128,8 @@ class Group:
self.colspan = colspan
self.align = align
self.valign = valign
self.cellpadding = cellpadding
self.cellspacing = cellspacing
if style == 'tabs':
# Group content will be rendered as tabs. In this case, some
# param combinations have no sense.
@ -289,8 +292,9 @@ class Search:
self.group = group # Searches may be visually grouped in the portlet
self.sortBy = sortBy
self.limit = limit
self.fields = fields # This is a dict whose keys are indexed field
# names and whose values are search values.
# In the dict below, keys are indexed field names and values are
# search values.
self.fields = fields
@staticmethod
def getIndexName(fieldName, usage='search'):
'''Gets the name of the technical index that corresponds to field named
@ -1466,7 +1470,8 @@ class Ref(Type):
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=5,
colspan=1, master=None, masterValue=None, focus=False,
historized=False):
historized=False, queryable=False, queryFields=None,
queryNbCols=1):
self.klass = klass
self.attribute = attribute
# May the user add new objects through this ref ?
@ -1503,6 +1508,15 @@ class Ref(Type):
self.maxPerPage = maxPerPage
# Specifies sync
sync = {'view': False, 'edit':True}
# If param p_queryable is True, the user will be able to perform queries
# from the UI within referenced objects.
self.queryable = queryable
# Here is the list of fields that will appear on the search screen.
# If None is specified, by default we take every indexed field
# defined on referenced objects' class.
self.queryFields = queryFields
# The search screen will have this number of columns
self.queryNbCols = queryNbCols
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
False, specificReadPermission, specificWritePermission,

View file

@ -535,7 +535,6 @@ class UserClassDescriptor(ClassDescriptor):
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)

View file

@ -98,7 +98,6 @@ class Generator(AbstractGenerator):
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD),
msg('ref_name', '', msg.REF_NAME),
msg('ref_actions', '', msg.REF_ACTIONS),
msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN),
@ -303,7 +302,7 @@ class Generator(AbstractGenerator):
repls['addPermissions'] = addPermissions
# Compute root classes
rootClasses = ''
for classDescr in self.classes:
for classDescr in self.getClasses(include='allButTool'):
if classDescr.isRoot():
rootClasses += "'%s'," % classDescr.name
repls['rootClasses'] = rootClasses
@ -617,9 +616,20 @@ class Generator(AbstractGenerator):
if self.user.customized:
Tool.users.klass = self.user.klass
# Generate the User class
self.user.generateSchema()
self.labels += [ Msg(self.userName, '', Msg.USER),
Msg('%s_edit_descr' % self.userName, '', ' '),
Msg('%s_plural' % self.userName, '',self.userName+'s')]
repls = self.repls.copy()
repls['fields'] = self.user.schema
repls['methods'] = self.user.methods
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
self.copyFile('UserTemplate.py', repls,destName='%s.py' % self.userName)
# Before generating the Tool class, finalize it with query result
# columns, with fields to propagate, workflow-related fields.
for classDescr in self.classes:
for classDescr in self.getClasses(include='allButTool'):
for fieldName, fieldType in classDescr.toolFieldsToPropagate:
for childDescr in classDescr.getChildren():
childFieldName = fieldName % childDescr.name
@ -644,16 +654,6 @@ class Generator(AbstractGenerator):
repls['wrapperClass'] = '%s_Wrapper' % self.tool.name
self.copyFile('ToolTemplate.py', repls, destName='%s.py'% self.toolName)
# Generate the User class
self.user.generateSchema()
self.labels += [ Msg(self.userName, '', Msg.USER),
Msg('%s_edit_descr' % self.userName, '', ' ')]
repls = self.repls.copy()
repls['fields'] = self.user.schema
repls['methods'] = self.user.methods
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
self.copyFile('UserTemplate.py', repls,destName='%s.py' % self.userName)
def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application, for
generating the corresponding Archetype class and schema.'''

View file

@ -164,16 +164,25 @@ class ToolMixin(BaseMixin):
res.append((appyType.name, self.translate(appyType.labelId)))
return res
def getSearchableFields(self, contentType):
'''Returns, among the list of all searchable fields (see method above),
the list of fields that the user has configured as being effectively
used in the search screen.'''
res = []
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
def getSearchInfo(self, contentType, refInfo=None):
'''Returns a tuple:
1) The list of searchable fields (ie fields among all indexed fields)
2) The number of columns for layouting those fields.'''
fields = []
if refInfo:
# The search is triggered from a Ref field.
refField = self.getRefInfo(refInfo)[1]
fieldNames = refField.queryFields or ()
nbOfColumns = refField.queryNbCols
else:
# The search is triggered from an app-wide search.
at = self.appy()
fieldNames = getattr(at, 'searchFieldsFor%s' % contentType,())
nbOfColumns = getattr(at, 'numberOfSearchColumnsFor%s' %contentType)
for name in fieldNames:
appyType = self.getAppyType(name, asDict=True,className=contentType)
res.append(appyType)
return res
fields.append(appyType)
return fields, nbOfColumns
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
@ -220,7 +229,8 @@ class ToolMixin(BaseMixin):
def executeQuery(self, contentType, searchName=None, startNumber=0,
search=None, remember=False, brainsOnly=False,
maxResults=None, noSecurity=False, sortBy=None,
sortOrder='asc', filterKey=None, filterValue=None):
sortOrder='asc', filterKey=None, filterValue=None,
refField=None):
'''Executes a query on a given p_contentType (or several, separated
with commas) in Plone's portal_catalog. If p_searchName is specified,
it corresponds to:
@ -256,7 +266,10 @@ class ToolMixin(BaseMixin):
If p_filterKey is given, it represents an additional search parameter
to take into account: the corresponding search value is in
p_filterValue.'''
p_filterValue.
If p_refField is given, the query is limited to the objects that are
referenced through it.'''
# Is there one or several content types ?
if contentType.find(',') != -1:
portalTypes = contentType.split(',')
@ -276,6 +289,9 @@ class ToolMixin(BaseMixin):
if search:
# Add additional search criteria
for fieldName, fieldValue in search.fields.iteritems():
# Management of searches restricted to objects linked through a
# Ref field: not implemented yet.
if fieldName == '_ref': continue
# Make the correspondance between the name of the field and the
# name of the corresponding index.
attrName = Search.getIndexName(fieldName)
@ -306,7 +322,9 @@ class ToolMixin(BaseMixin):
# Return brains only.
if not maxResults: return brains
else: return brains[:maxResults]
if not maxResults: maxResults = self.appy().numberOfResultsPerPage
if not maxResults:
if refField: maxResults = refField.maxPerPage
else: maxResults = self.appy().numberOfResultsPerPage
elif maxResults == 'NO_LIMIT': maxResults = None
res = SomeObjects(brains, maxResults, startNumber,noSecurity=noSecurity)
res.brainsToObjects()
@ -327,13 +345,16 @@ class ToolMixin(BaseMixin):
s['search_%s' % searchName] = uids
return res.__dict__
def getResultColumnsNames(self, contentType):
def getResultColumnsNames(self, contentType, refField):
contentTypes = contentType.strip(',').split(',')
resSet = None # Temporary set for computing intersections.
res = [] # Final, sorted result.
fieldNames = None
appyTool = self.appy()
for cType in contentTypes:
if refField:
fieldNames = refField.shownInfo
else:
fieldNames = getattr(appyTool, 'resultColumnsFor%s' % cType)
if not resSet:
resSet = set(fieldNames)
@ -346,27 +367,6 @@ class ToolMixin(BaseMixin):
res.append(fieldName)
return res
def getResultColumns(self, anObject, contentType):
'''What columns must I show when displaying a list of root class
instances? Result is a list of tuples containing the name of the
column (=name of the field) and the corresponding appyType (dict
version).'''
res = []
for fieldName in self.getResultColumnsNames(contentType):
if fieldName == 'workflowState':
# We do not return a appyType if the attribute is not a *real*
# attribute, but the workfow state.
res.append(fieldName)
else:
appyType = anObject.getAppyType(fieldName, asDict=True)
if not appyType:
res.append({'name': fieldName, '_wrong': True})
# The field name is wrong.
# We return it so we can show it in an error message.
else:
res.append(appyType)
return res
def truncateValue(self, value, appyType):
'''Truncates the p_value according to p_appyType width.'''
maxWidth = appyType['width']
@ -452,14 +452,13 @@ class ToolMixin(BaseMixin):
return pythonClass.maySearch(self.appy())
return True
def userMayNavigate(self, someClass):
def userMayNavigate(self, obj):
'''This method checks if the currently logged user can display the
navigation panel within the portlet. This is done by calling method
"mayNavigate" on the class whose currently shown object is an
instance of. If no such method exists, we return True.'''
pythonClass = self.getAppyClass(someClass)
if 'mayNavigate' in pythonClass.__dict__:
return pythonClass.mayNavigate(self.appy())
"mayNavigate" on the currently shown object. If no such method
exists, we return True.'''
appyObj = obj.appy()
if hasattr(appyObj, 'mayNavigate'): return appyObj.mayNavigate()
return True
def onImportObjects(self):
@ -485,8 +484,9 @@ class ToolMixin(BaseMixin):
return False
def isSortable(self, name, className, usage):
'''Is field p_name defined on p_metaType sortable for p_usage purposes
'''Is field p_name defined on p_className sortable for p_usage purposes
(p_usage can be "ref" or "search")?'''
if (',' in className) or (name == 'workflowState'): return False
appyType = self.getAppyType(name, className=className)
return appyType.isSortable(usage=usage)
@ -591,6 +591,10 @@ class ToolMixin(BaseMixin):
oper = ' %s ' % rq.form.get(operKey, 'or').upper()
attrValue = oper.join(attrValue)
criteria[attrName[2:]] = attrValue
# Complete criteria with Ref info if the search is restricted to
# referenced objects of a Ref field.
refInfo = rq.get('ref', None)
if refInfo: criteria['_ref'] = refInfo
rq.SESSION['searchCriteria'] = criteria
# Go to the screen that displays search results
backUrl = '%s/query?type_name=%s&&search=_advanced' % \
@ -605,6 +609,18 @@ class ToolMixin(BaseMixin):
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
return res
def getRefInfo(self, refInfo=None):
'''When a search is restricted to objects referenced through a Ref
field, this method returns information about this reference: the
source content type and the Ref field (Appy type). If p_refInfo is
not given, we search it among search criteria in the session.'''
if not refInfo:
criteria = self.REQUEST.SESSION.get('searchCriteria', None)
if criteria and criteria.has_key('_ref'): refInfo = criteria['_ref']
if not refInfo: return ('', None)
sourceContentType, refField = refInfo.split(':')
return refInfo, self.getAppyType(refField, className=sourceContentType)
def getSearches(self, contentType):
'''Returns the list of searches that are defined for p_contentType.
Every list item is a dict that contains info about a search or about
@ -650,8 +666,9 @@ class ToolMixin(BaseMixin):
on p_contentType.'''
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
baseParams = 'type_name=%s' % contentType
# Manage start number
rq = self.REQUEST
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
# Manage start number
if startNumber != None:
baseParams += '&startNumber=%s' % startNumber
elif rq.has_key('startNumber'):

View file

@ -490,9 +490,25 @@ class BaseMixin:
if tjs not in js: js.append(tjs)
return css, js
def getAppyTypesFromNames(self, fieldNames, asDict=True):
'''Gets the Appy types names p_fieldNames.'''
return [self.getAppyType(name, asDict) for name in fieldNames]
def getAppyTypesFromNames(self, fieldNames, asDict=True, addTitle=True):
'''Gets the Appy types named p_fieldNames. If 'title' is not among
p_fieldNames and p_addTitle is True, field 'title' is prepended to
the result.'''
res = []
for name in fieldNames:
appyType = self.getAppyType(name, asDict)
if appyType: res.append(appyType)
elif name == 'workflowState':
# We do not return a appyType if the attribute is not a *real*
# attribute, but the workfow state.
res.append({'name': name, 'labelId': 'workflow_state',
'filterable': False})
else:
self.appy().log('Field "%s", used as shownInfo in a Ref, ' \
'was not found.' % name, type='warning')
if addTitle and ('title' not in fieldNames):
res.insert(0, self.getAppyType('title', asDict))
return res
def getAppyStates(self, phase, currentOnly=False):
'''Returns information about the states that are related to p_phase.

View file

@ -71,7 +71,7 @@ class User(ModelClass):
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
'password2', 'roles']
# All methods defined below are fake. Real versions are in the wrapper.
title = String(show=False)
title = String(show=False, indexed=True)
gm = {'group': 'main', 'multiplicity': (1,1)}
name = String(**gm)
firstName = String(**gm)
@ -109,8 +109,9 @@ class Tool(ModelClass):
# link to the predefined User class or a custom class defined in the
# application.
users = Ref(None, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool'), page='users',
shownInfo=('login', 'title', 'roles'), showHeaders=True)
back=Ref(attribute='toTool'), page='users', queryable=True,
queryFields=('login',), showHeaders=True,
shownInfo=('login', 'title', 'roles'))
enableNotifications = Boolean(default=True, page='notifications')
def validPythonWithUno(self, value): pass # Real method in the wrapper
unoEnabledPython = String(group="connectionToOpenOffice",

View file

@ -1,6 +1,9 @@
<metal:queryResults define-macro="queryResult"
tal:define="tool python: contextObj;
contentType request/type_name;
refInfo tool/getRefInfo;
refField python: refInfo[1];
refInfo python: refInfo[0];
startNumber request/startNumber|python:'0';
startNumber python: int(startNumber);
searchName request/search;
@ -11,13 +14,13 @@
sortOrder request/sortOrder| python:'asc';
filterKey request/filterKey| python:'';
filterValue request/filterValue | python:'';
queryResult python: tool.executeQuery(contentType, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue);
queryResult python: tool.executeQuery(contentType, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refField=refField);
objs queryResult/objects;
totalNumber queryResult/totalNumber;
batchSize queryResult/batchSize;
ajaxHookId python:'queryResult';
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, searchName);
newSearchUrl python: '%s/skyn/search?type_name=%s&' % (tool.getAppFolder().absolute_url(), contentType);">
newSearchUrl python: '%s/skyn/search?type_name=%s&ref=%s' % (tool.getAppFolder().absolute_url(), contentType, refInfo);">
<tal:result condition="objs">
@ -42,44 +45,23 @@
</td>
</tr></table>
<table tal:define="fieldDescrs python: tool.getResultColumns(objs[0], contentType)"
<table tal:define="fieldNames python: tool.getResultColumnsNames(contentType, refField);
widgets python: objs[0].getAppyTypesFromNames(fieldNames);"
class="listing nosort" width="100%" cellpadding="0" cellspacing="0">
<tal:comment replace="nothing">Every item in fieldDescrs is an Appy type (dict version),
excepted for workflow state (which is not a field): in this case it is simply the
string "workflow_state".</tal:comment>
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
<tr>
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
<th tal:define="fieldName python:'title'; sortable python:True; filterable python:True">
<span tal:replace="structure python: tool.truncateText(tool.translate('ref_name'))"/>
<metal:sortAndFilter use-macro="here/skyn/navigate/macros/sortAndFilter"/>
<tal:header repeat="widget widgets">
<th tal:define="sortable python: tool.isSortable(widget['name'], contentType, 'search');
filterable widget/filterable|nothing;">
<span tal:replace="structure python: tool.truncateText(tool.translate(widget['labelId']))"/>
<metal:icons use-macro="here/skyn/navigate/macros/sortAndFilter"/>
</th>
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
<tal:columnHeader repeat="fieldDescr fieldDescrs">
<th tal:define="fieldName fieldDescr/name|string:workflow_state;
sortable python: tool.isSortable(fieldName, contentType, 'search');
filterable fieldDescr/filterable|nothing;">
<tal:comment replace="nothing">Display header for a "standard" field</tal:comment>
<tal:standardField condition="python: fieldName != 'workflow_state'">
<span tal:replace="structure python: tool.truncateText(tool.translate(fieldDescr['labelId']))"/>
</tal:standardField>
<tal:comment replace="nothing">Display header for the workflow state</tal:comment>
<tal:workflowState condition="python: fieldName == 'workflow_state'">
<span tal:replace="python: tool.translate('workflow_state')"/>
</tal:workflowState>
<metal:sortAndFilter use-macro="here/skyn/navigate/macros/sortAndFilter"/>
</th>
</tal:columnHeader>
<tal:comment replace="nothing">Column "Object type", shown if instances of several types are shown</tal:comment>
</tal:header>
<tal:comment replace="nothing">Object type, shown if instances of several types are shown</tal:comment>
<th tal:condition="severalTypes">
<span tal:replace="python: tool.translate('root_type')"/>
<span tal:replace="python: tool.translate('root_type')"></span>
</th>
<tal:comment replace="nothing">Column "Actions"</tal:comment>
<tal:comment replace="nothing">Actions</tal:comment>
<th tal:content="python: tool.translate('ref_actions')"></th>
</tr>
@ -88,15 +70,22 @@
<tr id="query_row" tal:define="odd repeat/obj/odd"
tal:attributes="class python:test(odd, 'even', 'odd')">
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
<td id="field_title"><a
tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a></td>
<tal:fields repeat="widget widgets">
<tal:comment replace="nothing">Title</tal:comment>
<td id="field_title"
tal:condition="python: widget['name'] == 'title'">
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a>
</td>
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
<tal:otherFields repeat="widget fieldDescrs">
<tal:standardField condition="python: widget != 'workflowState'">
<td tal:condition="python: '_wrong' not in widget"
<tal:comment replace="nothing">Workflow state</tal:comment>
<td id="field_workflow_state"
tal:condition="python: widget['name'] == 'workflowState'"
tal:content="python: tool.translate(obj.getWorkflowLabel())">
</td>
<tal:comment replace="nothing">Any other field</tal:comment>
<td tal:condition="python: widget['name'] not in ('title', 'workflowState')"
tal:attributes="id python:'field_%s' % widget['name']">
<tal:field define="contextObj python:obj;
layoutType python:'cell';
@ -105,14 +94,7 @@
<metal:field use-macro="here/skyn/widgets/show/macros/field"/>
</tal:field>
</td>
<td tal:condition="python: '_wrong' in widget" style="color:red">Field
<span tal:replace="widget/name"/> not found.
</td>
</tal:standardField>
<tal:workflowState condition="python: widget == 'workflowState'">
<td id="field_workflow_state" tal:content="python: tool.translate(obj.getWorkflowLabel())"></td>
</tal:workflowState>
</tal:otherFields>
</tal:fields>
<tal:comment replace="nothing">Column "Object type", shown if instances of several types are shown</tal:comment>
<td tal:condition="severalTypes" id="field_root_type"

View file

@ -95,7 +95,7 @@
This macro displays up/down arrows in a table header column for sorting a given column.
It requires variables "sortable", 'filterable' and 'fieldName'.
</tal:comment>
<metal:sortAndFilter define-macro="sortAndFilter">
<metal:sortAndFilter define-macro="sortAndFilter" tal:define="fieldName widget/name">
<tal:sort condition="sortable">
<img tal:attributes="src string: $portal_url/skyn/sortDown.gif;
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'asc\',\'%s\'' % (fieldName, filterKey))"

View file

@ -28,7 +28,7 @@
</td>
</dt>
<tal:publishedObject condition="python: contextObj and tool.userMayNavigate(contextObj.meta_type)">
<tal:publishedObject condition="python: contextObj and tool.userMayNavigate(contextObj)">
<dt class="portletAppyItem portletCurrent"><b tal:content="contextObj/Title"></b></dt>
<dt class="portletAppyItem"><metal:phases use-macro="here/skyn/portlet/macros/phases"/></dt>
</tal:publishedObject>

View file

@ -15,8 +15,8 @@
tal:define="appFolder context/getParentNode;
appName appFolder/id;
tool python: portal.get('portal_%s' % appName.lower());
contentType python:context.REQUEST.get('type_name');
searchName python:context.REQUEST.get('search', '')">
contentType request/type_name;
searchName request/search|python:'';">
<div metal:use-macro="here/skyn/page/macros/prologue"/>
<tal:comment replace="nothing">Query result</tal:comment>

View file

@ -15,8 +15,11 @@
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
contentType request/type_name;
refInfo request/ref|nothing;
tool python: portal.get('portal_%s' % appFolder.id.lower());
searchableFields python: tool.getSearchableFields(contentType)">
searchInfo python: tool.getSearchInfo(contentType, refInfo);
searchableFields python: searchInfo[0];
numberOfColumns python: searchInfo[1]">
<tal:comment replace="nothing">Search title</tal:comment>
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
@ -26,9 +29,9 @@
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
<input type="hidden" name="action" value="SearchObjects"/>
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
<input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/>
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
tal:define="numberOfColumns python: tool.getAttr('numberOfSearchColumnsFor%s' % contentType)">
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%">
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
<tal:field condition="widget">

View file

@ -78,7 +78,7 @@
ref field according to the field that corresponds to this column.
</tal:comment>
<metal:sortIcons define-macro="sortIcons"
tal:define="ajaxBaseCall python: navBaseCall.replace('**v**', '\'%s\',\'SortReference\', {\'sortKey\':\'%s\', \'reverse\':\'**v**\'}' % (startNumber, shownField))" tal:condition="python: canWrite and tool.isSortable(shownField, objs[0].meta_type, 'ref')">
tal:define="ajaxBaseCall python: navBaseCall.replace('**v**', '\'%s\',\'SortReference\', {\'sortKey\':\'%s\', \'reverse\':\'**v**\'}' % (startNumber, widget['name']))" tal:condition="python: canWrite and tool.isSortable(widget['name'], objs[0].meta_type, 'ref')">
<img style="cursor:pointer"
tal:attributes="src string:$portal_url/skyn/sortAsc.png;
onClick python: ajaxBaseCall.replace('**v**', 'False')"/>
@ -159,6 +159,10 @@
<span tal:condition="not: innerRef" tal:content="label"/>
(<span tal:replace="totalNumber"/>)
<metal:plusIcon use-macro="portal/skyn/widgets/ref/macros/plusIcon"/>
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
<a tal:condition="appyType/queryable"
tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.getAppFolder().absolute_url(), linkedPortalType, contextObj.meta_type, appyType['name'])">
<img src="search.gif" tal:attributes="title python: tool.translate('search_objects')"/></a>
</legend>
<tal:comment replace="nothing">Object description</tal:comment>
@ -185,44 +189,34 @@
</table>
<tal:comment replace="nothing">Show forward reference(s)</tal:comment>
<table tal:attributes="class python:test(innerRef, '', 'listing nosort');
width python:test(innerRef, '100%', appyType['layouts']['view']['width']);"
align="right" tal:condition="python: not appyType['isBack'] and objs" cellpadding="0" cellspacing="0">
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
<tr tal:condition="appyType/showHeaders">
<th tal:condition="python: 'title' not in appyType['shownInfo']"
tal:define="shownField python:'title'">
<span tal:content="python: tool.translate('ref_name')"></span>
<metal:sortIcons use-macro="portal/skyn/widgets/ref/macros/sortIcons" />
</th>
<th tal:repeat="widget widgets">
<tal:header define="shownField widget/name">
<span tal:content="python: tool.translate(widget['labelId'])"></span>
<metal:sortIcons use-macro="portal/skyn/widgets/ref/macros/sortIcons" />
</tal:header>
</th>
<th tal:content="python: tool.translate('ref_actions')"></th>
</tr>
<tal:row repeat="obj objs">
<tr valign="top" tal:define="odd repeat/obj/odd"
tal:attributes="class python:test(odd, 'even', 'odd')">
<tal:comment replace="nothing">Object title, shown here if not specified somewhere
else in appyType.shownInfo.</tal:comment>
<td tal:condition="python: 'title' not in appyType['shownInfo']"><metal:showObjectTitle
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
</td>
<tal:comment replace="nothing">Additional fields that must be shown</tal:comment>
<td tal:repeat="widget widgets">
<tal:showTitle condition="python: widget['name'] == 'title'">
<tal:title condition="python: widget['name'] == 'title'">
<metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
</tal:showTitle>
<tal:showOtherField define="contextObj python:obj;
</tal:title>
<tal:state condition="python: widget['name'] == 'workflowState'"
content="python: tool.translate(obj.getWorkflowLabel())">
</tal:state>
<tal:other condition="python: widget['name'] not in ('title', 'workflowState')">
<tal:field define="contextObj python:obj;
layoutType python: 'cell';
innerRef python:True"
condition="python: widget['name'] != 'title'">
<metal:showField use-macro="portal/skyn/widgets/show/macros/field" />
</tal:showOtherField>
innerRef python:True">
<metal:field use-macro="portal/skyn/widgets/show/macros/field" />
</tal:field>
</tal:other>
</td>
<tal:comment replace="nothing">Actions</tal:comment>
<td align="right">
@ -232,7 +226,6 @@
</tal:row>
</tal:widgets>
</table>
</td></tr>
</table>

View file

@ -140,7 +140,9 @@
<table metal:define-macro="groupContent"
tal:attributes="width python: test(widget['wide'], '100%', '');
align widget/align;
class widget/css_class">
class widget/css_class;
cellspacing widget/cellspacing;
cellpadding widget/cellpadding">
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
<td tal:attributes="colspan python: len(widget['columnsWidths']);

View file

@ -46,7 +46,6 @@ class PoMessage:
POD_ASKACTION = 'Trigger related action'
REF_NO = 'No object.'
REF_ADD = 'Add a new one'
REF_NAME = 'Name'
REF_ACTIONS = 'Actions'
REF_MOVE_UP = 'Move up'
REF_MOVE_DOWN = 'Move down'