Worflow state can not be used in ref fields; mayNavigate is not static anymore, but an instance method.
This commit is contained in:
parent
e62e00d367
commit
2124cffa5e
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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,14 +345,17 @@ 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:
|
||||
fieldNames = getattr(appyTool, 'resultColumnsFor%s' % cType)
|
||||
if refField:
|
||||
fieldNames = refField.shownInfo
|
||||
else:
|
||||
fieldNames = getattr(appyTool, 'resultColumnsFor%s' % cType)
|
||||
if not resSet:
|
||||
resSet = set(fieldNames)
|
||||
else:
|
||||
|
@ -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,10 +591,14 @@ 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' % \
|
||||
(os.path.dirname(rq['URL']), rq['type_name'])
|
||||
(os.path.dirname(rq['URL']),rq['type_name'])
|
||||
return self.goto(backUrl)
|
||||
|
||||
def getJavascriptMessages(self):
|
||||
|
@ -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'):
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)"
|
||||
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>
|
||||
|
||||
<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">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"/>
|
||||
</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"/>
|
||||
<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: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,31 +70,31 @@
|
|||
<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:attributes="id python:'field_%s' % widget['name']">
|
||||
<tal:field define="contextObj python:obj;
|
||||
layoutType python:'cell';
|
||||
innerRef python:True"
|
||||
condition="python: contextObj.showField(widget['name'], 'view')">
|
||||
<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: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';
|
||||
innerRef python:True"
|
||||
condition="python: contextObj.showField(widget['name'], 'view')">
|
||||
<metal:field use-macro="here/skyn/widgets/show/macros/field"/>
|
||||
</tal:field>
|
||||
</td>
|
||||
</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"
|
||||
|
|
|
@ -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))"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
<span tal:content="python: tool.translate(widget['labelId'])"></span>
|
||||
<metal:sortIcons use-macro="portal/skyn/widgets/ref/macros/sortIcons" />
|
||||
</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;
|
||||
layoutType python: 'cell';
|
||||
innerRef python:True"
|
||||
condition="python: widget['name'] != 'title'">
|
||||
<metal:showField use-macro="portal/skyn/widgets/show/macros/field" />
|
||||
</tal:showOtherField>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Reference in a new issue