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
					
				
					 14 changed files with 187 additions and 161 deletions
				
			
		|  | @ -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,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,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)" | ||||
|     <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" | ||||
|  |  | |||
|  | @ -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> | ||||
|             </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> | ||||
| 
 | ||||
|  |  | |||
|  | @ -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']); | ||||
|  |  | |||
|  | @ -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' | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay