[gen] Searches can now be grouped into groups of groups of..., creating a tree structure in the portlet.
This commit is contained in:
		
							parent
							
								
									6cf29778b6
								
							
						
					
					
						commit
						4872e5d8b8
					
				
					 6 changed files with 92 additions and 78 deletions
				
			
		
										
											Binary file not shown.
										
									
								
							|  | @ -233,24 +233,26 @@ class Group: | ||||||
|         if self.group and (self.group not in walkedGroups) and \ |         if self.group and (self.group not in walkedGroups) and \ | ||||||
|            not self.group.label: |            not self.group.label: | ||||||
|             # We remember walked groups for avoiding infinite recursion. |             # We remember walked groups for avoiding infinite recursion. | ||||||
|             self.group.generateLabels(messages, classDescr, walkedGroups) |             self.group.generateLabels(messages, classDescr, walkedGroups, | ||||||
|  |                                       forSearch=forSearch) | ||||||
| 
 | 
 | ||||||
|     def insertInto(self, widgets, groupDescrs, page, metaType): |     def insertInto(self, widgets, groupDescrs, page, metaType, forSearch=False): | ||||||
|         '''Inserts the GroupDescr instance corresponding to this Group instance |         '''Inserts the GroupDescr instance corresponding to this Group instance | ||||||
|            into p_widgets, the recursive structure used for displaying all |            into p_widgets, the recursive structure used for displaying all | ||||||
|            widgets in a given p_page, and returns this GroupDescr instance.''' |            widgets in a given p_page (or all searches), and returns this | ||||||
|  |            GroupDescr instance.''' | ||||||
|         # First, create the corresponding GroupDescr if not already in |         # First, create the corresponding GroupDescr if not already in | ||||||
|         # p_groupDescrs. |         # p_groupDescrs. | ||||||
|         if self.name not in groupDescrs: |         if self.name not in groupDescrs: | ||||||
|             groupDescr = groupDescrs[self.name] = GroupDescr(self, page, |             groupDescr = groupDescrs[self.name] = \ | ||||||
|                                                              metaType).get() |                 GroupDescr(self, page, metaType, forSearch=forSearch).get() | ||||||
|             # Insert the group at the higher level (ie, directly in p_widgets) |             # Insert the group at the higher level (ie, directly in p_widgets) | ||||||
|             # if the group is not itself in a group. |             # if the group is not itself in a group. | ||||||
|             if not self.group: |             if not self.group: | ||||||
|                 widgets.append(groupDescr) |                 widgets.append(groupDescr) | ||||||
|             else: |             else: | ||||||
|                 outerGroupDescr = self.group.insertInto(widgets, groupDescrs, |                 outerGroupDescr = self.group.insertInto(widgets, groupDescrs, | ||||||
|                                                         page, metaType) |                                             page, metaType, forSearch=forSearch) | ||||||
|                 GroupDescr.addWidget(outerGroupDescr, groupDescr) |                 GroupDescr.addWidget(outerGroupDescr, groupDescr) | ||||||
|         else: |         else: | ||||||
|             groupDescr = groupDescrs[self.name] |             groupDescr = groupDescrs[self.name] | ||||||
|  | @ -291,7 +293,7 @@ class Import: | ||||||
| class Search: | class Search: | ||||||
|     '''Used for specifying a search for a given type.''' |     '''Used for specifying a search for a given type.''' | ||||||
|     def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None, |     def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None, | ||||||
|                  default=False, **fields): |                  default=False, colspan=1, **fields): | ||||||
|         self.name = name |         self.name = name | ||||||
|         # Searches may be visually grouped in the portlet. |         # Searches may be visually grouped in the portlet. | ||||||
|         self.group = Group.get(group) |         self.group = Group.get(group) | ||||||
|  | @ -301,6 +303,7 @@ class Search: | ||||||
|         # If this search is the default one, it will be triggered by clicking |         # If this search is the default one, it will be triggered by clicking | ||||||
|         # on main link. |         # on main link. | ||||||
|         self.default = default |         self.default = default | ||||||
|  |         self.colspan = colspan | ||||||
|         # In the dict below, keys are indexed field names and values are |         # In the dict below, keys are indexed field names and values are | ||||||
|         # search values. |         # search values. | ||||||
|         self.fields = fields |         self.fields = fields | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ | ||||||
| import os, os.path, sys, re, time, random, types, base64, urllib | import os, os.path, sys, re, time, random, types, base64, urllib | ||||||
| from appy import Object | from appy import Object | ||||||
| import appy.gen | import appy.gen | ||||||
| from appy.gen import Type, Search, Selection, String | from appy.gen import Type, Search, Selection, String, Page | ||||||
| from appy.gen.utils import SomeObjects, getClassName | from appy.gen.utils import SomeObjects, getClassName, GroupDescr | ||||||
| from appy.gen.mixins import BaseMixin | from appy.gen.mixins import BaseMixin | ||||||
| from appy.gen.wrappers import AbstractWrapper | from appy.gen.wrappers import AbstractWrapper | ||||||
| from appy.gen.descriptors import ClassDescriptor | from appy.gen.descriptors import ClassDescriptor | ||||||
|  | @ -672,7 +672,7 @@ class ToolMixin(BaseMixin): | ||||||
|         obj = self.getObject(objectUid) |         obj = self.getObject(objectUid) | ||||||
|         return obj, fieldName |         return obj, fieldName | ||||||
| 
 | 
 | ||||||
|     def getSearches(self, contentType): |     def getGroupedSearches(self, contentType): | ||||||
|         '''Returns an object with 2 attributes: |         '''Returns an object with 2 attributes: | ||||||
|            * "searches" stores the searches that are defined for p_contentType; |            * "searches" stores the searches that are defined for p_contentType; | ||||||
|            * "default" stores the search defined as the default one. |            * "default" stores the search defined as the default one. | ||||||
|  | @ -680,37 +680,27 @@ class ToolMixin(BaseMixin): | ||||||
|            search or about a group of searches. |            search or about a group of searches. | ||||||
|         ''' |         ''' | ||||||
|         appyClass = self.getAppyClass(contentType) |         appyClass = self.getAppyClass(contentType) | ||||||
|         searches = [] |         res = [] | ||||||
|         default = None # Also retrieve the default one here. |         default = None # Also retrieve the default one here. | ||||||
|         visitedGroups = {} # Names of already visited search groups |         groups = {} # The already encountered groups | ||||||
|  |         page = Page('main') # A dummy page required by class GroupDescr | ||||||
|         for search in ClassDescriptor.getSearches(appyClass): |         for search in ClassDescriptor.getSearches(appyClass): | ||||||
|             # Determine first group label, we will need it. |             # Compute the dict representing this search | ||||||
|             groupLabel = None |  | ||||||
|             groupName = None |  | ||||||
|             if search.group: |  | ||||||
|                 groupName = search.group.name |  | ||||||
|                 groupLabel = '%s_searchgroup_%s' % (contentType, groupName) |  | ||||||
|             # Add an item representing the search group if relevant |  | ||||||
|             if groupName and (groupName not in visitedGroups): |  | ||||||
|                 group = {'name': groupName, 'isGroup': True, |  | ||||||
|                          'labelId': groupLabel, 'searches': [], |  | ||||||
|                          'label': self.translate(groupLabel), |  | ||||||
|                          'descr': self.translate('%s_descr' % groupLabel), |  | ||||||
|                 } |  | ||||||
|                 searches.append(group) |  | ||||||
|                 visitedGroups[groupName] = group |  | ||||||
|             # Add the search itself |  | ||||||
|             searchLabel = '%s_search_%s' % (contentType, search.name) |             searchLabel = '%s_search_%s' % (contentType, search.name) | ||||||
|             dSearch = {'name': search.name, 'isGroup': False, |             dSearch = {'name': search.name, 'type': 'search', | ||||||
|  |                        'colspan': search.colspan, | ||||||
|                        'label': self.translate(searchLabel), |                        'label': self.translate(searchLabel), | ||||||
|                        'descr': self.translate('%s_descr' % searchLabel)} |                        'descr': self.translate('%s_descr' % searchLabel)} | ||||||
|             if groupName: |             if not search.group: | ||||||
|                 visitedGroups[groupName]['searches'].append(dSearch) |                 # Insert the search at the highest level, not in any group. | ||||||
|  |                 res.append(dSearch) | ||||||
|             else: |             else: | ||||||
|                 searches.append(dSearch) |                 groupDescr = search.group.insertInto(res, groups, page, | ||||||
|             if search.default: |                                                     contentType, forSearch=True) | ||||||
|                 default = dSearch |                 GroupDescr.addWidget(groupDescr, dSearch) | ||||||
|         return Object(searches=searches, default=default).__dict__ |             # Is this search the default search? | ||||||
|  |             if search.default: default = dSearch | ||||||
|  |         return Object(searches=res, default=default).__dict__ | ||||||
| 
 | 
 | ||||||
|     def getQueryUrl(self, contentType, searchName, startNumber=None): |     def getQueryUrl(self, contentType, searchName, startNumber=None): | ||||||
|         '''This method creates the URL that allows to perform a (non-Ajax) |         '''This method creates the URL that allows to perform a (non-Ajax) | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ img { border: 0; vertical-align: middle} | ||||||
| .portletPage { font-style: italic; } | .portletPage { font-style: italic; } | ||||||
| .portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal; | .portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal; | ||||||
|                 margin-top: 0.1em } |                 margin-top: 0.1em } | ||||||
| .portletSearch { font-size: 90%; font-style: italic; padding-left: 1em} | .portletSearch { font-size: 90%; font-style: italic } | ||||||
| .phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;} | .phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;} | ||||||
| .phaseSelected { background-color: #F4F5F6; } | .phaseSelected { background-color: #F4F5F6; } | ||||||
| .content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 } | .content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,51 @@ | ||||||
| <tal:comment replace="nothing"> | <tal:comment replace="nothing">Macro for displaying a search</tal:comment> | ||||||
|     This macro displays the content of the application portlet. | <div metal:define-macro="search" class="portletSearch"> | ||||||
| </tal:comment> |   <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); | ||||||
|  |                      title search/descr; | ||||||
|  |                      class python: test(search['name'] == currentSearch, 'portletCurrent', '');" | ||||||
|  |      tal:content="structure search/label"></a> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment> | ||||||
|  | <metal:group define-macro="group" | ||||||
|  |              tal:define="expanded python: request.get(widget['labelId'], 'collapsed') == 'expanded'"> | ||||||
|  |  <tal:comment replace="nothing">Group name</tal:comment> | ||||||
|  |  <div class="portletGroup"> | ||||||
|  |    <img style="cursor:pointer; margin-right: 3px" | ||||||
|  |         tal:attributes="id python: '%s_img' % widget['labelId']; | ||||||
|  |                         src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif'); | ||||||
|  |                         onClick python:'toggleCookie(\'%s\')' % widget['labelId']; | ||||||
|  |                         align dleft"/> | ||||||
|  |    <span tal:replace="python: _(widget['labelId'])"/> | ||||||
|  |  </div> | ||||||
|  |  <tal:comment replace="nothing">Group content</tal:comment> | ||||||
|  |  <div tal:define="display python:test(expanded, 'display:block', 'display:none')" | ||||||
|  |       tal:attributes="id widget/labelId; | ||||||
|  |                       style python: 'padding-left: 10px;; %s' % display"> | ||||||
|  |   <tal:searchRows repeat="searches widget/widgets"> | ||||||
|  |    <tal:searchElem repeat="searchElem searches"> | ||||||
|  |     <tal:comment replace="nothing">An inner group within this group</tal:comment> | ||||||
|  |     <tal:group condition="python: searchElem['type'] == 'group'"> | ||||||
|  |      <tal:g define="widget searchElem"> | ||||||
|  |       <metal:s use-macro="app/ui/portlet/macros/group"/> | ||||||
|  |      </tal:g>  | ||||||
|  |     </tal:group> | ||||||
|  |     <tal:search condition="python: searchElem['type'] != 'group'"> | ||||||
|  |      <tal:s define="search searchElem"> | ||||||
|  |       <metal:s use-macro="app/ui/portlet/macros/search"/> | ||||||
|  |      </tal:s> | ||||||
|  |     </tal:search> | ||||||
|  |    </tal:searchElem> | ||||||
|  |   </tal:searchRows> | ||||||
|  |  </div> | ||||||
|  | </metal:group> | ||||||
|  | 
 | ||||||
|  | <tal:comment replace="nothing">Macro displaying the whole portlet</tal:comment> | ||||||
| <metal:portlet define-macro="portlet" | <metal:portlet define-macro="portlet" | ||||||
|    tal:define="queryUrl           python: '%s/ui/query' % tool.absolute_url(); |    tal:define="queryUrl           python: '%s/ui/query' % tool.absolute_url(); | ||||||
|                toolUrl            tool/absolute_url; |                toolUrl            tool/absolute_url; | ||||||
|  |                app                tool/getApp; | ||||||
|  |                appUrl             app/absolute_url; | ||||||
|                currentSearch      req/search|nothing; |                currentSearch      req/search|nothing; | ||||||
|                currentClass       req/className|nothing; |                currentClass       req/className|nothing; | ||||||
|                contextObj         tool/getPublishedObject; |                contextObj         tool/getPublishedObject; | ||||||
|  | @ -16,7 +58,7 @@ | ||||||
|        <img tal:attributes="src string: $appUrl/ui/gotoSource.png"/> |        <img tal:attributes="src string: $appUrl/ui/gotoSource.png"/> | ||||||
|      </a> |      </a> | ||||||
|   </div> |   </div> | ||||||
|   <metal:phases use-macro="here/ui/portlet/macros/phases"/> |   <metal:phases use-macro="app/ui/portlet/macros/phases"/> | ||||||
|  </div> |  </div> | ||||||
| 
 | 
 | ||||||
|  <tal:comment replace="nothing">One section for every searchable root class.</tal:comment> |  <tal:comment replace="nothing">One section for every searchable root class.</tal:comment> | ||||||
|  | @ -26,7 +68,7 @@ | ||||||
|   <div class="portletSep" tal:define="nb repeat/rootClass/number" |   <div class="portletSep" tal:define="nb repeat/rootClass/number" | ||||||
|                           tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>   |                           tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>   | ||||||
| 
 | 
 | ||||||
|   <div class="portletContent" tal:define="searchInfo python: tool.getSearches(rootClass)"> |   <div class="portletContent" tal:define="searchInfo python: tool.getGroupedSearches(rootClass)"> | ||||||
|    <tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment> |    <tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment> | ||||||
|    <a tal:define="queryParam python: searchInfo['default'] and ('&search=%s' % searchInfo['default']['name']) or ''" |    <a tal:define="queryParam python: searchInfo['default'] and ('&search=%s' % searchInfo['default']['name']) or ''" | ||||||
|       tal:attributes="href python: '%s?className=%s%s' % (queryUrl, rootClass, queryParam); |       tal:attributes="href python: '%s?className=%s%s' % (queryUrl, rootClass, queryParam); | ||||||
|  | @ -56,40 +98,17 @@ | ||||||
|      <img tal:attributes="src string: $appUrl/ui/search.gif"/> |      <img tal:attributes="src string: $appUrl/ui/search.gif"/> | ||||||
|     </a> |     </a> | ||||||
|    </span> |    </span> | ||||||
|    <tal:comment replace="nothing">Searches for this content type.</tal:comment> |    <tal:comment replace="nothing">Searches for this content type</tal:comment> | ||||||
|    <tal:searchOrGroup repeat="searchOrGroup searchInfo/searches"> |    <tal:widget repeat="widget searchInfo/searches"> | ||||||
|      <tal:group condition="searchOrGroup/isGroup"> |     <tal:group condition="python: widget['type'] == 'group'"> | ||||||
|      <tal:expanded define="group searchOrGroup; |      <metal:s use-macro="app/ui/portlet/macros/group"/> | ||||||
|                            expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'"> |  | ||||||
|        <tal:comment replace="nothing">Group name</tal:comment> |  | ||||||
|        <div class="portletGroup"> |  | ||||||
|          <img style="cursor:pointer; margin-right: 3px" |  | ||||||
|               tal:attributes="id python: '%s_img' % group['labelId']; |  | ||||||
|                               src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif'); |  | ||||||
|                               onClick python:'toggleCookie(\'%s\')' % group['labelId']; |  | ||||||
|                               align dleft"/> |  | ||||||
|          <span tal:replace="group/label"/> |  | ||||||
|        </div> |  | ||||||
|        <tal:comment replace="nothing">Group searches</tal:comment> |  | ||||||
|        <div tal:attributes="id group/labelId; |  | ||||||
|                              style python:test(expanded, 'display:block', 'display:none')"> |  | ||||||
|          <div class="portletSearch" tal:repeat="search group/searches"> |  | ||||||
|            <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); |  | ||||||
|                               title search/descr; |  | ||||||
|                               class python: test(search['name'] == currentSearch, 'portletCurrent', '');" |  | ||||||
|               tal:content="structure search/label"></a> |  | ||||||
|          </div> |  | ||||||
|        </div> |  | ||||||
|      </tal:expanded> |  | ||||||
|     </tal:group> |     </tal:group> | ||||||
|      <dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup" |     <tal:search condition="python: widget['type'] != 'group'"> | ||||||
|          class="portletAppyItem portletSearch"> |      <tal:s define="search widget"> | ||||||
|        <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); |       <metal:s use-macro="app/ui/portlet/macros/search"/> | ||||||
|                     title search/descr; |      </tal:s> | ||||||
|                     class python: test(search['name'] == currentSearch, 'portletCurrent', '');" |     </tal:search> | ||||||
|           tal:content="structure search/label"></a> |    </tal:widget> | ||||||
|      </dt> |  | ||||||
|    </tal:searchOrGroup> |  | ||||||
|    </div> |    </div> | ||||||
|  </tal:section> |  </tal:section> | ||||||
| </metal:portlet> | </metal:portlet> | ||||||
|  |  | ||||||
|  | @ -59,8 +59,8 @@ class Descr: | ||||||
|     def get(self): return self.__dict__ |     def get(self): return self.__dict__ | ||||||
| 
 | 
 | ||||||
| class GroupDescr(Descr): | class GroupDescr(Descr): | ||||||
|     def __init__(self, group, page, metaType): |     def __init__(self, group, page, metaType, forSearch=False): | ||||||
|         '''Creates the data structure manipulated in ZPTs from p_group, the |         '''Creates the data structure manipulated in ZPTs for p_group, the | ||||||
|            Group instance used in the field definition.''' |            Group instance used in the field definition.''' | ||||||
|         self.type = 'group' |         self.type = 'group' | ||||||
|         # All p_group attributes become self attributes. |         # All p_group attributes become self attributes. | ||||||
|  | @ -77,7 +77,9 @@ class GroupDescr(Descr): | ||||||
|             else: # It is a tuple (metaType, name) |             else: # It is a tuple (metaType, name) | ||||||
|                 if group.label[1]: labelName = group.label[1] |                 if group.label[1]: labelName = group.label[1] | ||||||
|                 if group.label[0]: prefix = group.label[0] |                 if group.label[0]: prefix = group.label[0] | ||||||
|         self.labelId = '%s_group_%s' % (prefix, labelName) |         if forSearch: gp = 'searchgroup' | ||||||
|  |         else:         gp = 'group' | ||||||
|  |         self.labelId = '%s_%s_%s' % (prefix, gp, labelName) | ||||||
|         self.descrId = self.labelId + '_descr' |         self.descrId = self.labelId + '_descr' | ||||||
|         self.helpId  = self.labelId + '_help' |         self.helpId  = self.labelId + '_help' | ||||||
|         # The name of the page where the group lies |         # The name of the page where the group lies | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay