[gen] It is now possible to define, via method 'getDynamicSearches', dynamic searches for a class.
This commit is contained in:
		
							parent
							
								
									4872e5d8b8
								
							
						
					
					
						commit
						5269b278f7
					
				
					 6 changed files with 99 additions and 52 deletions
				
			
		|  | @ -106,7 +106,8 @@ class Group: | ||||||
|                  hasLabel=True, hasDescr=False, hasHelp=False, |                  hasLabel=True, hasDescr=False, hasHelp=False, | ||||||
|                  hasHeaders=False, group=None, colspan=1, align='center', |                  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, cellgap='0.6em', label=None): |                  cellpadding=1, cellspacing=1, cellgap='0.6em', label=None, | ||||||
|  |                  translated=None): | ||||||
|         self.name = name |         self.name = name | ||||||
|         # In its simpler form, field "columns" below can hold a list or tuple |         # In its simpler form, field "columns" below can hold a list or tuple | ||||||
|         # of column widths expressed as strings, that will be given as is in |         # of column widths expressed as strings, that will be given as is in | ||||||
|  | @ -168,6 +169,9 @@ class Group: | ||||||
|         self.masterValue = initMasterValue(masterValue) |         self.masterValue = initMasterValue(masterValue) | ||||||
|         if master: master.slaves.append(self) |         if master: master.slaves.append(self) | ||||||
|         self.label = label # See similar attr of Type class. |         self.label = label # See similar attr of Type class. | ||||||
|  |         # If a translated name is already given here, we will use it instead of | ||||||
|  |         # trying to translate the group label. | ||||||
|  |         self.translated = translated | ||||||
| 
 | 
 | ||||||
|     def _setColumns(self): |     def _setColumns(self): | ||||||
|         '''Standardizes field "columns" as a list of Column instances. Indeed, |         '''Standardizes field "columns" as a list of Column instances. Indeed, | ||||||
|  | @ -293,7 +297,8 @@ 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, colspan=1, **fields): |                  default=False, colspan=1, translated=None, | ||||||
|  |                  translatedDescr=None, **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) | ||||||
|  | @ -304,6 +309,10 @@ class Search: | ||||||
|         # on main link. |         # on main link. | ||||||
|         self.default = default |         self.default = default | ||||||
|         self.colspan = colspan |         self.colspan = colspan | ||||||
|  |         # If a translated name or description is already given here, we will | ||||||
|  |         # use it instead of trying to translate from labels. | ||||||
|  |         self.translated = translated | ||||||
|  |         self.translatedDescr = translatedDescr | ||||||
|         # 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 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ 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, Page | from appy.gen import Type, Search, Selection, String, Page | ||||||
| from appy.gen.utils import SomeObjects, getClassName, GroupDescr | from appy.gen.utils import SomeObjects, getClassName, GroupDescr, SearchDescr | ||||||
| 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 | ||||||
|  | @ -288,9 +288,8 @@ class ToolMixin(BaseMixin): | ||||||
|                      maxResults=None, noSecurity=False, sortBy=None, |                      maxResults=None, noSecurity=False, sortBy=None, | ||||||
|                      sortOrder='asc', filterKey=None, filterValue=None, |                      sortOrder='asc', filterKey=None, filterValue=None, | ||||||
|                      refObject=None, refField=None): |                      refObject=None, refField=None): | ||||||
|         '''Executes a query on instances of a given p_className (or several, |         '''Executes a query on instances of a given p_className in the catalog. | ||||||
|            separated with commas) in the catalog. If p_searchName is specified, |            If p_searchName is specified, it corresponds to: | ||||||
|            it corresponds to: |  | ||||||
|              1) a search defined on p_className: additional search criteria |              1) a search defined on p_className: additional search criteria | ||||||
|                 will be added to the query, or; |                 will be added to the query, or; | ||||||
|              2) "_advanced": in this case, additional search criteria will also |              2) "_advanced": in this case, additional search criteria will also | ||||||
|  | @ -328,22 +327,10 @@ class ToolMixin(BaseMixin): | ||||||
| 
 | 
 | ||||||
|            If p_refObject and p_refField are given, the query is limited to the |            If p_refObject and p_refField are given, the query is limited to the | ||||||
|            objects that are referenced from p_refObject through p_refField.''' |            objects that are referenced from p_refObject through p_refField.''' | ||||||
|         # Is there one or several content types ? |         params = {'ClassName': className} | ||||||
|         if className.find(',') != -1: |  | ||||||
|             classNames = className.split(',') |  | ||||||
|         else: |  | ||||||
|             classNames = className |  | ||||||
|         params = {'ClassName': classNames} |  | ||||||
|         if not brainsOnly: params['batch'] = True |         if not brainsOnly: params['batch'] = True | ||||||
|         # Manage additional criteria from a search when relevant |         # Manage additional criteria from a search when relevant | ||||||
|         if searchName: |         if searchName: search = self.getSearch(className, searchName) | ||||||
|             # In this case, className must contain a single content type. |  | ||||||
|             appyClass = self.getAppyClass(className) |  | ||||||
|             if searchName != '_advanced': |  | ||||||
|                 search = ClassDescriptor.getSearch(appyClass, searchName) |  | ||||||
|             else: |  | ||||||
|                 fields = self.REQUEST.SESSION['searchCriteria'] |  | ||||||
|                 search = Search('customSearch', **fields) |  | ||||||
|         if search: |         if search: | ||||||
|             # Add additional search criteria |             # Add additional search criteria | ||||||
|             for fieldName, fieldValue in search.fields.iteritems(): |             for fieldName, fieldValue in search.fields.iteritems(): | ||||||
|  | @ -672,36 +659,61 @@ class ToolMixin(BaseMixin): | ||||||
|         obj = self.getObject(objectUid) |         obj = self.getObject(objectUid) | ||||||
|         return obj, fieldName |         return obj, fieldName | ||||||
| 
 | 
 | ||||||
|     def getGroupedSearches(self, contentType): |     def getGroupedSearches(self, className): | ||||||
|         '''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_className; | ||||||
|            * "default" stores the search defined as the default one. |            * "default" stores the search defined as the default one. | ||||||
|            Every item representing a search is a dict containing info about a |            Every item representing a search is a dict containing info about a | ||||||
|            search or about a group of searches. |            search or about a group of searches. | ||||||
|         ''' |         ''' | ||||||
|         appyClass = self.getAppyClass(contentType) |         appyClass = self.getAppyClass(className) | ||||||
|         res = [] |         res = [] | ||||||
|         default = None # Also retrieve the default one here. |         default = None # Also retrieve the default one here. | ||||||
|         groups = {} # The already encountered groups |         groups = {} # The already encountered groups | ||||||
|         page = Page('main') # A dummy page required by class GroupDescr |         page = Page('main') # A dummy page required by class GroupDescr | ||||||
|         for search in ClassDescriptor.getSearches(appyClass): |         searches = ClassDescriptor.getSearches(appyClass) | ||||||
|             # Compute the dict representing this search |         # Add dynamic searches if defined on p_appyClass | ||||||
|             searchLabel = '%s_search_%s' % (contentType, search.name) |         if hasattr(appyClass, 'getDynamicSearches'): | ||||||
|             dSearch = {'name': search.name, 'type': 'search', |             searches += appyClass.getDynamicSearches(self.appy()) | ||||||
|                        'colspan': search.colspan, |         for search in searches: | ||||||
|                        'label': self.translate(searchLabel), |             # Create the search descriptor | ||||||
|                        'descr': self.translate('%s_descr' % searchLabel)} |             searchDescr = SearchDescr(search, className, self).get() | ||||||
|             if not search.group: |             if not search.group: | ||||||
|                 # Insert the search at the highest level, not in any group. |                 # Insert the search at the highest level, not in any group. | ||||||
|                 res.append(dSearch) |                 res.append(searchDescr) | ||||||
|             else: |             else: | ||||||
|                 groupDescr = search.group.insertInto(res, groups, page, |                 groupDescr = search.group.insertInto(res, groups, page, | ||||||
|                                                     contentType, forSearch=True) |                                                      className, forSearch=True) | ||||||
|                 GroupDescr.addWidget(groupDescr, dSearch) |                 GroupDescr.addWidget(groupDescr, searchDescr) | ||||||
|             # Is this search the default search? |             # Is this search the default search? | ||||||
|             if search.default: default = dSearch |             if search.default: default = searchDescr | ||||||
|         return Object(searches=res, default=default).__dict__ |         return Object(searches=res, default=default).__dict__ | ||||||
| 
 | 
 | ||||||
|  |     def getSearch(self, className, name, descr=False): | ||||||
|  |         '''Gets the Search instance (or a SearchDescr instance if p_descr is | ||||||
|  |            True) corresponding to the search named p_name, on class | ||||||
|  |            p_className.''' | ||||||
|  |         if name == '_advanced': | ||||||
|  |             # It is an advanced search whose parameters are in the session. | ||||||
|  |             fields = self.REQUEST.SESSION['searchCriteria'] | ||||||
|  |             res = Search('customSearch', **fields) | ||||||
|  |         elif name: | ||||||
|  |             appyClass = self.getAppyClass(className) | ||||||
|  |             # Search among static searches | ||||||
|  |             res = ClassDescriptor.getSearch(appyClass, name) | ||||||
|  |             if not res and hasattr(appyClass, 'getDynamicSearches'): | ||||||
|  |                 # Search among dynamic searches | ||||||
|  |                 for search in appyClass.getDynamicSearches(self.appy()): | ||||||
|  |                     if search.name == name: | ||||||
|  |                         res = search | ||||||
|  |                         break | ||||||
|  |         else: | ||||||
|  |             # It is the search for every instance of p_className | ||||||
|  |             res = Search('allSearch') | ||||||
|  |         # Return a SearchDescr if required. | ||||||
|  |         if descr: res = SearchDescr(res, className, self).get() | ||||||
|  |         return res | ||||||
|  | 
 | ||||||
|     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) | ||||||
|            request for getting queried objects from a search named p_searchName |            request for getting queried objects from a search named p_searchName | ||||||
|  | @ -753,11 +765,13 @@ class ToolMixin(BaseMixin): | ||||||
|                 # This is a named, predefined search. |                 # This is a named, predefined search. | ||||||
|                 label = '%s_search_%s' % (d1.split(':')[0], searchName) |                 label = '%s_search_%s' % (d1.split(':')[0], searchName) | ||||||
|             res['backText'] = self.translate(label) |             res['backText'] = self.translate(label) | ||||||
|  |             # If it is a dynamic search this label does not exist. | ||||||
|  |             if ('_' in res['backText']): res['backText'] = '' | ||||||
|         else: |         else: | ||||||
|             fieldName, pageName = d2.split(':') |             fieldName, pageName = d2.split(':') | ||||||
|             sourceObj = self.getObject(d1) |             sourceObj = self.getObject(d1) | ||||||
|             label = '%s_%s' % (sourceObj.meta_type, fieldName) |             label = '%s_%s' % (sourceObj.meta_type, fieldName) | ||||||
|             res['backText'] = '%s : %s' % (sourceObj.Title(), |             res['backText'] = '%s - %s' % (sourceObj.Title(), | ||||||
|                                            self.translate(label)) |                                            self.translate(label)) | ||||||
|         newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber) |         newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber) | ||||||
|         # Among, first, previous, next and last, which one do I need? |         # Among, first, previous, next and last, which one do I need? | ||||||
|  |  | ||||||
|  | @ -64,8 +64,9 @@ | ||||||
|     <tr valign="middle"> |     <tr valign="middle"> | ||||||
|       <tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment> |       <tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment> | ||||||
|       <td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img |       <td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img | ||||||
|           tal:attributes="src string: $appUrl/ui/gotoSource.png; |           tal:define="gotoSource python: _('goto_source'); | ||||||
|                           title python: backText + ' : ' + _('goto_source')"/></a></td> |                       goBack python: backText and ('%s - %s' % (backText, gotoSource)) or gotoSource" | ||||||
|  |           tal:attributes="src string: $appUrl/ui/gotoSource.png; title goBack"/></a></td> | ||||||
|       <tal:comment replace="nothing">Go to the first page</tal:comment> |       <tal:comment replace="nothing">Go to the first page</tal:comment> | ||||||
|       <td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img |       <td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img | ||||||
|           tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png; |           tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png; | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| <tal:comment replace="nothing">Macro for displaying a search</tal:comment> | <tal:comment replace="nothing">Macro for displaying a search</tal:comment> | ||||||
| <div metal:define-macro="search" class="portletSearch"> | <div metal:define-macro="search" class="portletSearch"> | ||||||
|   <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); |   <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); | ||||||
|                      title search/descr; |                      title search/translatedDescr; | ||||||
|                      class python: test(search['name'] == currentSearch, 'portletCurrent', '');" |                      class python: test(search['name'] == currentSearch, 'portletCurrent', '');" | ||||||
|      tal:content="structure search/label"></a> |      tal:content="search/translated"> | ||||||
|  |   </a> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment> | <tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment> | ||||||
|  | @ -16,7 +17,8 @@ | ||||||
|                         src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif'); |                         src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif'); | ||||||
|                         onClick python:'toggleCookie(\'%s\')' % widget['labelId']; |                         onClick python:'toggleCookie(\'%s\')' % widget['labelId']; | ||||||
|                         align dleft"/> |                         align dleft"/> | ||||||
|    <span tal:replace="python: _(widget['labelId'])"/> |    <span tal:condition="not: widget/translated" tal:replace="python: _(widget['labelId'])"/> | ||||||
|  |    <span tal:condition="widget/translated" tal:replace="widget/translated"/> | ||||||
|  </div> |  </div> | ||||||
|  <tal:comment replace="nothing">Group content</tal:comment> |  <tal:comment replace="nothing">Group content</tal:comment> | ||||||
|  <div tal:define="display python:test(expanded, 'display:block', 'display:none')" |  <div tal:define="display python:test(expanded, 'display:block', 'display:none')" | ||||||
|  |  | ||||||
|  | @ -7,15 +7,13 @@ | ||||||
|                refUrlPart    python: refObject and ('&ref=%s:%s' % (refObject.UID(), refField))  or ''; |                refUrlPart    python: refObject and ('&ref=%s:%s' % (refObject.UID(), refField))  or ''; | ||||||
|                startNumber   request/startNumber|python:'0'; |                startNumber   request/startNumber|python:'0'; | ||||||
|                startNumber   python: int(startNumber); |                startNumber   python: int(startNumber); | ||||||
|                searchName    request/search; |                searchName    request/search|python:''; | ||||||
|                labelId       python: searchName and ('%s_search_%s' % (className, searchName)) or ''; |                searchDescr   python: tool.getSearch(className, searchName, descr=True); | ||||||
|                labelId       python: (searchName == '_advanced') and 'search_results' or labelId; |  | ||||||
|                searchLabel   python: labelId and _(labelId) or ''; |  | ||||||
|                sortKey       request/sortKey| python:''; |                sortKey       request/sortKey| python:''; | ||||||
|                sortOrder     request/sortOrder| python:'asc'; |                sortOrder     request/sortOrder| python:'asc'; | ||||||
|                filterKey     request/filterKey| python:''; |                filterKey     request/filterKey| python:''; | ||||||
|                filterValue   request/filterValue | python:''; |                filterValue   request/filterValue | python:''; | ||||||
|                queryResult   python: tool.executeQuery(className, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField); |                queryResult   python: tool.executeQuery(className, search=searchDescr['search'], startNumber=startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField); | ||||||
|                objs          queryResult/objects; |                objs          queryResult/objects; | ||||||
|                totalNumber   queryResult/totalNumber; |                totalNumber   queryResult/totalNumber; | ||||||
|                batchSize     queryResult/batchSize; |                batchSize     queryResult/batchSize; | ||||||
|  | @ -39,7 +37,7 @@ | ||||||
| 
 | 
 | ||||||
|   <tal:comment replace="nothing">The title of the search.</tal:comment> |   <tal:comment replace="nothing">The title of the search.</tal:comment> | ||||||
|   <p> |   <p> | ||||||
|    <span tal:replace="structure python: test(searchName, searchLabel, _('%s_plural' % className))"/> |    <span tal:replace="searchDescr/translated"/> | ||||||
|     (<span tal:replace="totalNumber"/>) |     (<span tal:replace="totalNumber"/>) | ||||||
|       <tal:newSearch condition="python: searchName == '_advanced'"> |       <tal:newSearch condition="python: searchName == '_advanced'"> | ||||||
|          — <i><a tal:attributes="href newSearchUrl" |          — <i><a tal:attributes="href newSearchUrl" | ||||||
|  | @ -48,19 +46,16 @@ | ||||||
|   </p> |   </p> | ||||||
|   <table width="100%"> |   <table width="100%"> | ||||||
|    <tr> |    <tr> | ||||||
|     <tal:descr condition="searchName"> |     <tal:comment replace="nothing">Search description</tal:comment> | ||||||
|     <td tal:define="descr python: _('%s_descr' % labelId)" |     <td tal:condition="searchDescr/translatedDescr"> | ||||||
|         tal:condition="descr/strip"> |      <span class="discreet" tal:content="searchDescr/translatedDescr"></span><br/> | ||||||
|      <span class="discreet" tal:content="descr"></span><br/> |  | ||||||
|     </td> |     </td> | ||||||
|     </tal:descr> |  | ||||||
|     <td tal:attributes="align dright" width="25%"> |     <td tal:attributes="align dright" width="25%"> | ||||||
|      <tal:comment replace="nothing">Appy (top) navigation</tal:comment> |      <tal:comment replace="nothing">Appy (top) navigation</tal:comment> | ||||||
|      <metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/> |      <metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/> | ||||||
|     </td> |     </td> | ||||||
|    </tr> |    </tr> | ||||||
|   </table> |   </table> | ||||||
| 
 |  | ||||||
|   <table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo); |   <table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo); | ||||||
|                      columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)" |                      columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)" | ||||||
|          class="list" width="100%"> |          class="list" width="100%"> | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								gen/utils.py
									
										
									
									
									
								
							
							
						
						
									
										26
									
								
								gen/utils.py
									
										
									
									
									
								
							|  | @ -185,6 +185,32 @@ class PhaseDescr(Descr): | ||||||
|                     self.nextPhase = allPhases[i+1] |                     self.nextPhase = allPhases[i+1] | ||||||
|         self.phaseStatus = res |         self.phaseStatus = res | ||||||
| 
 | 
 | ||||||
|  | class SearchDescr(Descr): | ||||||
|  |     '''Describes a Search.''' | ||||||
|  |     def __init__(self, search, className, tool): | ||||||
|  |         self.search = search | ||||||
|  |         self.name = search.name | ||||||
|  |         self.type = 'search' | ||||||
|  |         self.colspan = search.colspan | ||||||
|  |         if search.translated: | ||||||
|  |             self.translated = search.translated | ||||||
|  |             self.translatedDescr = search.translatedDescr | ||||||
|  |         else: | ||||||
|  |             # The label may be specific in some special cases. | ||||||
|  |             labelDescr = '' | ||||||
|  |             if search.name == 'allSearch': | ||||||
|  |                 label = '%s_plural' % className | ||||||
|  |             elif search.name == 'customSearch': | ||||||
|  |                 label = 'search_results' | ||||||
|  |             else: | ||||||
|  |                 label = '%s_search_%s' % (className, search.name) | ||||||
|  |                 labelDescr = label + '_descr' | ||||||
|  |             self.translated = tool.translate(label) | ||||||
|  |             if labelDescr: | ||||||
|  |                 self.translatedDescr = tool.translate(labelDescr) | ||||||
|  |             else: | ||||||
|  |                 self.translatedDescr = '' | ||||||
|  | 
 | ||||||
| # ------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------ | ||||||
| upperLetter = re.compile('[A-Z]') | upperLetter = re.compile('[A-Z]') | ||||||
| def produceNiceMessage(msg): | def produceNiceMessage(msg): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay