Improved advanced search screen.
This commit is contained in:
		
							parent
							
								
									d6607d7815
								
							
						
					
					
						commit
						24d0370892
					
				
					 6 changed files with 182 additions and 25 deletions
				
			
		|  | @ -199,7 +199,12 @@ class String(Type): | |||
|         if complement: | ||||
|             return (97 - (number % 97)) == checkNumber | ||||
|         else: | ||||
|             return (number % 97) == checkNumber | ||||
|             # The check number can't be 0. In this case, we force it to be 97. | ||||
|             # This is the way Belgian bank account numbers work. I hope this | ||||
|             # behaviour is general enough to be implemented here. | ||||
|             mod97 = (number % 97) | ||||
|             if mod97 == 0: return checkNumber == 97 | ||||
|             else:          return checkNumber == mod97 | ||||
|     @staticmethod | ||||
|     def MODULO_97(obj, value): return String._MODULO_97(obj, value) | ||||
|     @staticmethod | ||||
|  |  | |||
|  | @ -122,6 +122,8 @@ class Generator(AbstractGenerator): | |||
|             msg('search_results',       '', msg.SEARCH_RESULTS), | ||||
|             msg('search_results_descr', '', ' '), | ||||
|             msg('search_new',           '', msg.SEARCH_NEW), | ||||
|             msg('search_from',          '', msg.SEARCH_FROM), | ||||
|             msg('search_to',            '', msg.SEARCH_TO), | ||||
|             msg('ref_invalid_index',    '', msg.REF_INVALID_INDEX), | ||||
|             msg('bad_int',              '', msg.BAD_INT), | ||||
|             msg('bad_float',            '', msg.BAD_FLOAT), | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| # ------------------------------------------------------------------------------ | ||||
| import re, os, os.path, Cookie | ||||
| from appy.gen import Type, Search | ||||
| from appy.gen.utils import FieldDescr, SomeObjects | ||||
| from appy.gen.utils import FieldDescr, SomeObjects, sequenceTypes | ||||
| from appy.gen.plone25.mixins import AbstractMixin | ||||
| from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin | ||||
| from appy.gen.plone25.wrappers import AbstractWrapper | ||||
|  | @ -160,15 +160,30 @@ class ToolMixin(AbstractMixin): | |||
|         if search: | ||||
|             # Add additional search criteria | ||||
|             for fieldName, fieldValue in search.fields.iteritems(): | ||||
|                 # Make the correspondance between the name of the field and the | ||||
|                 # name of the corresponding index. | ||||
|                 attrName = fieldName | ||||
|                 if attrName == 'title': attrName = 'Title' | ||||
|                 elif attrName == 'description': attrName = 'Description' | ||||
|                 elif attrName == 'state': attrName = 'review_state' | ||||
|                 else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:]) | ||||
|                 # Express the field value in the way needed by the index | ||||
|                 if isinstance(fieldValue, basestring) and \ | ||||
|                    fieldValue.endswith('*'): | ||||
|                     v = fieldValue[:-1] | ||||
|                     params[attrName] = {'query':[v,v+'Z'], 'range':'minmax'} | ||||
|                     params[attrName] = {'query':(v,v+'Z'), 'range':'minmax'} | ||||
|                 elif type(fieldValue) in sequenceTypes: | ||||
|                     # We have a range of values instead of a single value | ||||
|                     minv, maxv = fieldValue | ||||
|                     rangev = 'minmax' | ||||
|                     queryv = fieldValue | ||||
|                     if minv == None: | ||||
|                         rangev = 'max' | ||||
|                         queryv = maxv | ||||
|                     elif maxv == None: | ||||
|                         rangev = 'min' | ||||
|                         queryv = minv | ||||
|                     params[attrName] = {'query':queryv, 'range':rangev} | ||||
|                 else: | ||||
|                     params[attrName] = fieldValue | ||||
|             # Add a sort order if specified | ||||
|  | @ -380,6 +395,48 @@ class ToolMixin(AbstractMixin): | |||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     def _searchValueIsEmpty(self, key): | ||||
|         '''Returns True if request value in key p_key can be considered as | ||||
|            empty.''' | ||||
|         rq = self.REQUEST.form | ||||
|         if key.endswith('*int'): | ||||
|             # We return True if "from" AND "to" values are empty. | ||||
|             toKey = '%s_to' % key[2:-4] | ||||
|             return not rq[key].strip() and not rq[toKey].strip() | ||||
|         elif key.endswith('*date'): | ||||
|             # We return True if "from" AND "to" values are empty. A value is | ||||
|             # considered as not empty if at least the year is specified. | ||||
|             toKey = '%s_to_year' % key[2:-5] | ||||
|             return not rq[key] and not rq[toKey] | ||||
|         else: | ||||
|             return not rq[key] | ||||
| 
 | ||||
|     def _getDateTime(self, year, month, day, setMin): | ||||
|         '''Gets a valid DateTime instance from date information coming from the | ||||
|            request as strings in p_year, p_month and p_day. Returns None if | ||||
|            p_year is empty. If p_setMin is True, when some | ||||
|            information is missing (month or day), we will replace it with the | ||||
|            minimum value (=1). Else, we will replace it with the maximum value | ||||
|            (=12, =31).''' | ||||
|         if not year: return None | ||||
|         if not month: | ||||
|             if setMin: month = 1 | ||||
|             else:      month = 12 | ||||
|         if not day: | ||||
|             if setMin: day = 1 | ||||
|             else:      day = 31 | ||||
|         DateTime = self.getProductConfig().DateTime | ||||
|         # We loop until we find a valid date. For example, we could loop from | ||||
|         # 2009/02/31 to 2009/02/28. | ||||
|         dateIsWrong = True | ||||
|         while dateIsWrong: | ||||
|             try: | ||||
|                 res = DateTime('%s/%s/%s' % (year, month, day)) | ||||
|                 dateIsWrong = False | ||||
|             except: | ||||
|                 day = int(day)-1 | ||||
|         return res | ||||
| 
 | ||||
|     def onSearchObjects(self): | ||||
|         '''This method is called when the user triggers a search from | ||||
|            search.pt.''' | ||||
|  | @ -387,16 +444,40 @@ class ToolMixin(AbstractMixin): | |||
|         # Store the search criteria in the session | ||||
|         criteria = {} | ||||
|         for attrName in rq.form.keys(): | ||||
|             if attrName.startswith('w_'): | ||||
|             if attrName.startswith('w_') and \ | ||||
|                not self._searchValueIsEmpty(attrName): | ||||
|                 # We have a(n interval of) value(s) that is not empty for a | ||||
|                 # given field. | ||||
|                 attrValue = rq.form[attrName] | ||||
|                 if attrValue: | ||||
|                     if attrName.find('*') != -1: | ||||
|                         attrName, attrType = attrName.split('*') | ||||
|                         if attrType == 'bool': | ||||
|                             exec 'attrValue = %s' % attrValue | ||||
|                     if isinstance(attrValue, list): | ||||
|                         attrValue = ' OR '.join(attrValue) | ||||
|                     criteria[attrName[2:]] = attrValue | ||||
|                 if attrName.find('*') != -1: | ||||
|                     attrName, attrType = attrName.split('*') | ||||
|                     if attrType == 'bool': | ||||
|                         exec 'attrValue = %s' % attrValue | ||||
|                     elif attrType == 'int': | ||||
|                         # Get the "from" value | ||||
|                         if not attrValue.strip(): attrValue = None | ||||
|                         else:                     attrValue = int(attrValue) | ||||
|                         # Get the "to" value | ||||
|                         toValue = rq.form['%s_to' % attrName[2:]].strip() | ||||
|                         if not toValue:           toValue = None | ||||
|                         else:                     toValue = int(toValue) | ||||
|                         attrValue = (attrValue, toValue) | ||||
|                     elif attrType == 'date': | ||||
|                         prefix = attrName[2:] | ||||
|                         # Get the "from" value | ||||
|                         year  = attrValue | ||||
|                         month = rq.form['%s_from_month' % prefix] | ||||
|                         day   = rq.form['%s_from_day' % prefix] | ||||
|                         fromDate = self._getDateTime(year, month, day, True) | ||||
|                         # Get the "to" value" | ||||
|                         year  = rq.form['%s_to_year' % prefix] | ||||
|                         month = rq.form['%s_to_month' % prefix] | ||||
|                         day   = rq.form['%s_to_day' % prefix] | ||||
|                         toDate = self._getDateTime(year, month, day, False) | ||||
|                         attrValue = (fromDate, toDate) | ||||
|                 if isinstance(attrValue, list): | ||||
|                     attrValue = ' OR '.join(attrValue) | ||||
|                 criteria[attrName[2:]] = attrValue | ||||
|         rq.SESSION['searchCriteria'] = criteria | ||||
|         # Goto the screen that displays search results | ||||
|         backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \ | ||||
|  | @ -594,4 +675,12 @@ class ToolMixin(AbstractMixin): | |||
|         '''Truncates string p_value to p_numberOfChars.''' | ||||
|         if len(value) > numberOfChars: return value[:numberOfChars] + '...' | ||||
|         return value | ||||
| 
 | ||||
|     monthsIds = { | ||||
|         1:  'month_jan', 2:  'month_feb', 3:  'month_mar', 4:  'month_apr', | ||||
|         5:  'month_may', 6:  'month_jun', 7:  'month_jul', 8:  'month_aug', | ||||
|         9:  'month_sep', 10: 'month_oct', 11: 'month_nov', 12: 'month_dec'} | ||||
|     def getMonthName(self, monthNumber): | ||||
|         '''Gets the translated month name of month numbered p_monthNumber.''' | ||||
|         return self.translate(self.monthsIds[int(monthNumber)], domain='plone') | ||||
| # ------------------------------------------------------------------------------ | ||||
|  |  | |||
|  | @ -31,15 +31,15 @@ | |||
| 
 | ||||
|     <table class="no-style-table" cellpadding="0" cellspacing="0" width="100%" | ||||
|            tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)"> | ||||
|       <tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top" class="appySearchRow"> | ||||
|         <td tal:repeat="searchField searchRow"> | ||||
|       <tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top"> | ||||
|         <td tal:repeat="searchField searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)"> | ||||
|           <tal:field condition="searchField"> | ||||
|             <tal:showSearchField define="fieldName python:searchField[0]; | ||||
|                                          appyType python:searchField[1]; | ||||
|                                          widgetName python: 'w_%s' % fieldName"> | ||||
|               <metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/> | ||||
|             </tal:showSearchField> | ||||
|           </tal:field><br/><br class="discreet"/> | ||||
|           </tal:field><br class="discreet"/> | ||||
|         </td> | ||||
|       </tr> | ||||
|     </table> | ||||
|  |  | |||
|  | @ -1,5 +1,13 @@ | |||
| <metal:searchInteger define-macro="searchInteger"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <label tal:content="python: tool.translate(appyType['label'])"></label><br>   | ||||
|   <tal:from define="fromName python: '%s*int' % widgetName"> | ||||
|     <label tal:attributes="for fromName" tal:content="python: tool.translate('search_from')"></label> | ||||
|     <input type="text" tal:attributes="name fromName" size="4"/> | ||||
|   </tal:from> | ||||
|   <tal:to define="toName python: '%s_to' % fieldName"> | ||||
|     <label tal:attributes="for toName" tal:content="python: tool.translate('search_to')"></label> | ||||
|     <input type="text" tal:attributes="name toName" size="4"/> | ||||
|   </tal:to><br/> | ||||
| </metal:searchInteger> | ||||
| 
 | ||||
| <metal:searchFloat define-macro="searchFloat"> | ||||
|  | @ -19,10 +27,11 @@ | |||
|               tal:content="python: tool.truncate(tool.translate('%s_list_%s' % (appyType['label'], v)), 70)"> | ||||
|       </option> | ||||
|     </select> | ||||
|   </tal:selectSearch> | ||||
|   </tal:selectSearch><br/> | ||||
| </metal:searchString> | ||||
| 
 | ||||
| <metal:searchBoolean define-macro="searchBoolean" tal:define="typedWidget python:'%s*bool' % widgetName"> | ||||
| <metal:searchBoolean define-macro="searchBoolean" | ||||
|                      tal:define="typedWidget python:'%s*bool' % widgetName"> | ||||
|   <label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>   | ||||
|   <tal:yes define="valueId python:'%s_yes' % fieldName"> | ||||
|     <input type="radio" class="noborder" value="True" tal:attributes="name typedWidget; id valueId"/> | ||||
|  | @ -35,29 +44,79 @@ | |||
|   <tal:whatever define="valueId python:'%s_whatever' % fieldName"> | ||||
|     <input type="radio" class="noborder" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/> | ||||
|     <label tal:attributes="for valueId" tal:content="python: tool.translate('whatever')"></label> | ||||
|   </tal:whatever> | ||||
|   </tal:whatever><br/> | ||||
| </metal:searchBoolean> | ||||
| 
 | ||||
| <metal:searchDate define-macro="searchDate"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <label tal:content="python: tool.translate(appyType['label'])"></label> | ||||
|   <table cellpadding="0" cellspacing="0"> | ||||
|     <tal:comment replace="nothing">From</tal:comment> | ||||
|     <tr tal:define="fromName python: '%s*date' % widgetName"> | ||||
|       <td width="10px"> </td> | ||||
|       <td> | ||||
|         <label tal:content="python: tool.translate('search_from')"></label> | ||||
|       </td> | ||||
|       <td> | ||||
|         <select tal:attributes="name fromName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python:range(appyType['startYear'], appyType['endYear']+1)" | ||||
|                   tal:content="value" tal:attributes="value value"></option> | ||||
|         </select> / | ||||
|         <select tal:attributes="name python: '%s_from_month' % fieldName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]" | ||||
|                   tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option> | ||||
|         </select> / | ||||
|         <select tal:attributes="name python: '%s_from_day' % fieldName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]" | ||||
|                   tal:content="value" tal:attributes="value value"></option> | ||||
|         </select> | ||||
|       </td> | ||||
|     </tr> | ||||
|     <tal:comment replace="nothing">To</tal:comment> | ||||
|     <tr> | ||||
|       <td></td> | ||||
|       <td> | ||||
|         <label tal:content="python: tool.translate('search_to')"></label> | ||||
|       </td> | ||||
|       <td> | ||||
|         <select tal:attributes="name python: '%s_to_year' % fieldName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python:range(appyType['startYear'], appyType['endYear']+1)" | ||||
|                  tal:content="value" tal:attributes="value value"></option> | ||||
|         </select> / | ||||
|         <select tal:attributes="name python: '%s_to_month' % fieldName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]" | ||||
|                   tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option> | ||||
|         </select> / | ||||
|         <select tal:attributes="name python: '%s_to_day' % fieldName"> | ||||
|           <option value="">--</option> | ||||
|           <option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]" | ||||
|                   tal:content="value" tal:attributes="value value"></option> | ||||
|         </select> | ||||
|       </td> | ||||
|     </tr> | ||||
|   </table> | ||||
| </metal:searchDate> | ||||
| 
 | ||||
| <metal:searchFile define-macro="searchFile"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <p tal:content="fieldName"></p> | ||||
| </metal:searchFile> | ||||
| 
 | ||||
| <metal:searchRef define-macro="searchRef"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <p tal:content="fieldName"></p> | ||||
| </metal:searchRef> | ||||
| 
 | ||||
| <metal:searchComputed define-macro="searchComputed"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <p tal:content="fieldName"></p> | ||||
| </metal:searchComputed> | ||||
| 
 | ||||
| <metal:searchAction define-macro="searchAction"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <p tal:content="fieldName"></p> | ||||
| </metal:searchAction> | ||||
| 
 | ||||
| <metal:searchInfo define-macro="searchInfo"> | ||||
|   <p tal:content="fieldName">Hello</p> | ||||
|   <p tal:content="fieldName"></p> | ||||
| </metal:searchInfo> | ||||
|  |  | |||
|  | @ -62,6 +62,8 @@ class PoMessage: | |||
|     SEARCH_OBJECTS = 'Search objects of this type.' | ||||
|     SEARCH_RESULTS = 'Search results' | ||||
|     SEARCH_NEW = 'New search' | ||||
|     SEARCH_FROM = 'From' | ||||
|     SEARCH_TO = 'to' | ||||
|     WORKFLOW_COMMENT = 'Optional comment' | ||||
|     WORKFLOW_STATE = 'state' | ||||
|     DATA_CHANGE = 'Data change' | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay