Improved advanced search screen.

This commit is contained in:
Gaetan Delannay 2010-01-07 20:25:18 +01:00
parent d6607d7815
commit 24d0370892
6 changed files with 182 additions and 25 deletions

View file

@ -199,7 +199,12 @@ class String(Type):
if complement: if complement:
return (97 - (number % 97)) == checkNumber return (97 - (number % 97)) == checkNumber
else: 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 @staticmethod
def MODULO_97(obj, value): return String._MODULO_97(obj, value) def MODULO_97(obj, value): return String._MODULO_97(obj, value)
@staticmethod @staticmethod

View file

@ -122,6 +122,8 @@ class Generator(AbstractGenerator):
msg('search_results', '', msg.SEARCH_RESULTS), msg('search_results', '', msg.SEARCH_RESULTS),
msg('search_results_descr', '', ' '), msg('search_results_descr', '', ' '),
msg('search_new', '', msg.SEARCH_NEW), 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('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_int', '', msg.BAD_INT), msg('bad_int', '', msg.BAD_INT),
msg('bad_float', '', msg.BAD_FLOAT), msg('bad_float', '', msg.BAD_FLOAT),

View file

@ -1,7 +1,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re, os, os.path, Cookie import re, os, os.path, Cookie
from appy.gen import Type, Search 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 import AbstractMixin
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
from appy.gen.plone25.wrappers import AbstractWrapper from appy.gen.plone25.wrappers import AbstractWrapper
@ -160,15 +160,30 @@ class ToolMixin(AbstractMixin):
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():
# Make the correspondance between the name of the field and the
# name of the corresponding index.
attrName = fieldName attrName = fieldName
if attrName == 'title': attrName = 'Title' if attrName == 'title': attrName = 'Title'
elif attrName == 'description': attrName = 'Description' elif attrName == 'description': attrName = 'Description'
elif attrName == 'state': attrName = 'review_state' elif attrName == 'state': attrName = 'review_state'
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:]) 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 \ if isinstance(fieldValue, basestring) and \
fieldValue.endswith('*'): fieldValue.endswith('*'):
v = fieldValue[:-1] 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: else:
params[attrName] = fieldValue params[attrName] = fieldValue
# Add a sort order if specified # Add a sort order if specified
@ -380,6 +395,48 @@ class ToolMixin(AbstractMixin):
else: else:
return False 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): def onSearchObjects(self):
'''This method is called when the user triggers a search from '''This method is called when the user triggers a search from
search.pt.''' search.pt.'''
@ -387,13 +444,37 @@ class ToolMixin(AbstractMixin):
# Store the search criteria in the session # Store the search criteria in the session
criteria = {} criteria = {}
for attrName in rq.form.keys(): 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] attrValue = rq.form[attrName]
if attrValue:
if attrName.find('*') != -1: if attrName.find('*') != -1:
attrName, attrType = attrName.split('*') attrName, attrType = attrName.split('*')
if attrType == 'bool': if attrType == 'bool':
exec 'attrValue = %s' % attrValue 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): if isinstance(attrValue, list):
attrValue = ' OR '.join(attrValue) attrValue = ' OR '.join(attrValue)
criteria[attrName[2:]] = attrValue criteria[attrName[2:]] = attrValue
@ -594,4 +675,12 @@ class ToolMixin(AbstractMixin):
'''Truncates string p_value to p_numberOfChars.''' '''Truncates string p_value to p_numberOfChars.'''
if len(value) > numberOfChars: return value[:numberOfChars] + '...' if len(value) > numberOfChars: return value[:numberOfChars] + '...'
return value 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')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -31,15 +31,15 @@
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%" <table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)"> tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)">
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top" class="appySearchRow"> <tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
<td tal:repeat="searchField searchRow"> <td tal:repeat="searchField searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
<tal:field condition="searchField"> <tal:field condition="searchField">
<tal:showSearchField define="fieldName python:searchField[0]; <tal:showSearchField define="fieldName python:searchField[0];
appyType python:searchField[1]; appyType python:searchField[1];
widgetName python: 'w_%s' % fieldName"> widgetName python: 'w_%s' % fieldName">
<metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/> <metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/>
</tal:showSearchField> </tal:showSearchField>
</tal:field><br/><br class="discreet"/> </tal:field><br class="discreet"/>
</td> </td>
</tr> </tr>
</table> </table>

View file

@ -1,5 +1,13 @@
<metal:searchInteger define-macro="searchInteger"> <metal:searchInteger define-macro="searchInteger">
<p tal:content="fieldName">Hello</p> <label tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<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:searchInteger>
<metal:searchFloat define-macro="searchFloat"> <metal:searchFloat define-macro="searchFloat">
@ -19,10 +27,11 @@
tal:content="python: tool.truncate(tool.translate('%s_list_%s' % (appyType['label'], v)), 70)"> tal:content="python: tool.truncate(tool.translate('%s_list_%s' % (appyType['label'], v)), 70)">
</option> </option>
</select> </select>
</tal:selectSearch> </tal:selectSearch><br/>
</metal:searchString> </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>&nbsp;&nbsp; <label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<tal:yes define="valueId python:'%s_yes' % fieldName"> <tal:yes define="valueId python:'%s_yes' % fieldName">
<input type="radio" class="noborder" value="True" tal:attributes="name typedWidget; id valueId"/> <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"> <tal:whatever define="valueId python:'%s_whatever' % fieldName">
<input type="radio" class="noborder" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/> <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> <label tal:attributes="for valueId" tal:content="python: tool.translate('whatever')"></label>
</tal:whatever> </tal:whatever><br/>
</metal:searchBoolean> </metal:searchBoolean>
<metal:searchDate define-macro="searchDate"> <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">&nbsp;</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:searchDate>
<metal:searchFile define-macro="searchFile"> <metal:searchFile define-macro="searchFile">
<p tal:content="fieldName">Hello</p> <p tal:content="fieldName"></p>
</metal:searchFile> </metal:searchFile>
<metal:searchRef define-macro="searchRef"> <metal:searchRef define-macro="searchRef">
<p tal:content="fieldName">Hello</p> <p tal:content="fieldName"></p>
</metal:searchRef> </metal:searchRef>
<metal:searchComputed define-macro="searchComputed"> <metal:searchComputed define-macro="searchComputed">
<p tal:content="fieldName">Hello</p> <p tal:content="fieldName"></p>
</metal:searchComputed> </metal:searchComputed>
<metal:searchAction define-macro="searchAction"> <metal:searchAction define-macro="searchAction">
<p tal:content="fieldName">Hello</p> <p tal:content="fieldName"></p>
</metal:searchAction> </metal:searchAction>
<metal:searchInfo define-macro="searchInfo"> <metal:searchInfo define-macro="searchInfo">
<p tal:content="fieldName">Hello</p> <p tal:content="fieldName"></p>
</metal:searchInfo> </metal:searchInfo>

View file

@ -62,6 +62,8 @@ class PoMessage:
SEARCH_OBJECTS = 'Search objects of this type.' SEARCH_OBJECTS = 'Search objects of this type.'
SEARCH_RESULTS = 'Search results' SEARCH_RESULTS = 'Search results'
SEARCH_NEW = 'New search' SEARCH_NEW = 'New search'
SEARCH_FROM = 'From'
SEARCH_TO = 'to'
WORKFLOW_COMMENT = 'Optional comment' WORKFLOW_COMMENT = 'Optional comment'
WORKFLOW_STATE = 'state' WORKFLOW_STATE = 'state'
DATA_CHANGE = 'Data change' DATA_CHANGE = 'Data change'