Ready for an 'advanced search' functionality.

This commit is contained in:
Gaetan Delannay 2009-12-30 17:12:18 +01:00
parent 2ff08258bc
commit f8baeee4f7
8 changed files with 178 additions and 50 deletions

View file

@ -90,47 +90,52 @@ class Generator(AbstractGenerator):
# Some global i18n messages # Some global i18n messages
poMsg = msg(app, '', app); poMsg.produceNiceDefault() poMsg = msg(app, '', app); poMsg.produceNiceDefault()
self.labels += [poMsg, self.labels += [poMsg,
msg('workflow_state', '', msg.WORKFLOW_STATE), msg('workflow_state', '', msg.WORKFLOW_STATE),
msg('data_change', '', msg.DATA_CHANGE), msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD), msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE), msg('previous_value', '', msg.PREVIOUS_VALUE),
msg('phase', '', msg.PHASE), msg('phase', '', msg.PHASE),
msg('root_type', '', msg.ROOT_TYPE), msg('root_type', '', msg.ROOT_TYPE),
msg('workflow_comment', '', msg.WORKFLOW_COMMENT), msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
msg('choose_a_value', '', msg.CHOOSE_A_VALUE), msg('choose_a_value', '', msg.CHOOSE_A_VALUE),
msg('choose_a_doc', '', msg.CHOOSE_A_DOC), msg('choose_a_doc', '', msg.CHOOSE_A_DOC),
msg('min_ref_violated', '', msg.MIN_REF_VIOLATED), msg('min_ref_violated', '', msg.MIN_REF_VIOLATED),
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED), msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO), msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD), msg('add_ref', '', msg.REF_ADD),
msg('ref_name', '', msg.REF_NAME), msg('ref_name', '', msg.REF_NAME),
msg('ref_actions', '', msg.REF_ACTIONS), msg('ref_actions', '', msg.REF_ACTIONS),
msg('move_up', '', msg.REF_MOVE_UP), msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN), msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE), msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT), msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT), msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL), msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE), msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE), msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY), msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY), msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE), msg('import_done', '', msg.IMPORT_DONE),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX), msg('search_title', '', msg.SEARCH_TITLE),
msg('bad_int', '', msg.BAD_INT), msg('search_button', '', msg.SEARCH_BUTTON),
msg('bad_float', '', msg.BAD_FLOAT), msg('search_objects', '', msg.SEARCH_OBJECTS),
msg('bad_email', '', msg.BAD_EMAIL), msg('search_results', '', msg.SEARCH_RESULTS),
msg('bad_url', '', msg.BAD_URL), msg('search_results_descr', '', ' '),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC), msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('select_delesect', '', msg.SELECT_DESELECT), msg('bad_int', '', msg.BAD_INT),
msg('no_elem_selected', '', msg.NO_SELECTION), msg('bad_float', '', msg.BAD_FLOAT),
msg('delete_confirm', '', msg.DELETE_CONFIRM), msg('bad_email', '', msg.BAD_EMAIL),
msg('delete_done', '', msg.DELETE_DONE), msg('bad_url', '', msg.BAD_URL),
msg('goto_first', '', msg.GOTO_FIRST), msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('goto_previous', '', msg.GOTO_PREVIOUS), msg('select_delesect', '', msg.SELECT_DESELECT),
msg('goto_next', '', msg.GOTO_NEXT), msg('no_elem_selected', '', msg.NO_SELECTION),
msg('goto_last', '', msg.GOTO_LAST), msg('delete_confirm', '', msg.DELETE_CONFIRM),
msg('goto_source', '', msg.GOTO_SOURCE), msg('delete_done', '', msg.DELETE_DONE),
msg('goto_first', '', msg.GOTO_FIRST),
msg('goto_previous', '', msg.GOTO_PREVIOUS),
msg('goto_next', '', msg.GOTO_NEXT),
msg('goto_last', '', msg.GOTO_LAST),
msg('goto_source', '', msg.GOTO_SOURCE),
] ]
# Create basic files (config.py, Install.py, etc) # Create basic files (config.py, Install.py, etc)
self.generateTool() self.generateTool()

View file

@ -1,5 +1,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re, os, os.path, Cookie import re, os, os.path, Cookie
from appy.gen import Type
from appy.gen.utils import FieldDescr, SomeObjects from appy.gen.utils import FieldDescr, SomeObjects
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
@ -111,12 +112,19 @@ class ToolMixin(AbstractMixin):
'''Executes a query on a given p_contentType (or several, separated '''Executes a query on a given p_contentType (or several, separated
with commas) in Plone's portal_catalog. Portal types are from the with commas) in Plone's portal_catalog. Portal types are from the
flavour numbered p_flavourNumber. If p_searchName is specified, it flavour numbered p_flavourNumber. If p_searchName is specified, it
corresponds to a search defined on p_contentType: additional search corresponds to:
criteria will be added to the query. We will retrieve objects from 1) a search defined on p_contentType: additional search criteria
p_startNumber. If p_search is defined, it corresponds to a custom will be added to the query, or;
Search instance (instead of a predefined named search like in 2) "_advanced": in this case, additional search criteria will also
p_searchName). If both p_searchName and p_search are given, p_search be added to the query, but those criteria come from the session
is ignored. This method returns a list of objects in the form of the and were created from search.pt.
We will retrieve objects from p_startNumber. If p_search is defined,
it corresponds to a custom Search instance (instead of a predefined
named search like in p_searchName). If both p_searchName and p_search
are given, p_search is ignored.
This method returns a list of objects in the form of the
__dict__ attribute of an instance of SomeObjects (see in __dict__ attribute of an instance of SomeObjects (see in
appy.gen.utils). We return the __dict__ attribute instead of real appy.gen.utils). We return the __dict__ attribute instead of real
instance: that way, it can be used in ZPTs without security problems. instance: that way, it can be used in ZPTs without security problems.
@ -143,8 +151,9 @@ class ToolMixin(AbstractMixin):
# In this case, contentType must contain a single content type. # In this case, contentType must contain a single content type.
appyClass = self.getAppyClass(contentType) appyClass = self.getAppyClass(contentType)
if searchName: if searchName:
search = ArchetypesClassDescriptor.getSearch( if searchName != '_advanced':
appyClass, searchName) search = ArchetypesClassDescriptor.getSearch(
appyClass, searchName)
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():
@ -362,6 +371,26 @@ class ToolMixin(AbstractMixin):
else: else:
return False return False
def getSearchableFields(self, contentType):
'''Returns the list of fields that may be searched on objects on type
p_contentType (=indexed fields).'''
appyClass = self.getAppyClass(contentType)
res = []
for attrName in dir(appyClass):
attr = getattr(appyClass, attrName)
if isinstance(attr, Type) and attr.indexed:
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
res.append((attrName, dAttr))
return res
def onSearchObjects(self):
'''This method is called when the user triggers a search from
search.pt.'''
rq = self.REQUEST
backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \
(os.path.dirname(rq['URL']), rq['type_name'], rq['flavourNumber'])
return self.goto(backUrl)
def getJavascriptMessages(self): def getJavascriptMessages(self):
'''Returns the translated version of messages that must be shown in '''Returns the translated version of messages that must be shown in
Javascript popups.''' Javascript popups.'''

View file

@ -639,7 +639,7 @@
startNumber request/startNumber|python:'0'; startNumber request/startNumber|python:'0';
startNumber python: int(startNumber); startNumber python: int(startNumber);
searchName request/search; searchName request/search;
searchLabel python: '%s_search_%s' % (contentType, searchName); searchLabel python: test(searchName=='_advanced', 'search_results', '%s_search_%s' % (contentType, searchName));
searchDescr python: '%s_descr' % searchLabel; searchDescr python: '%s_descr' % searchLabel;
severalTypes python: contentType and (contentType.find(',') != -1); severalTypes python: contentType and (contentType.find(',') != -1);
queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber, remember=True); queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber, remember=True);
@ -909,6 +909,11 @@
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootClass); tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootClass);
src string: $portal_url/skyn/import.png; src string: $portal_url/skyn/import.png;
title python: tool.translate('query_import')"/> title python: tool.translate('query_import')"/>
<tal:comment replace="nothing">Search objects of this type (todo: update flavourNumber)</tal:comment>
<img style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s&flavourNumber=1\'' % (appFolder.absolute_url(), rootClass);
src string: $portal_url/skyn/search.gif;
title python: tool.translate('search_objects')"/>
</td> </td>
</tr> </tr>
</table> </table>

BIN
gen/plone25/skin/search.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

View file

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
metal:use-macro="here/main_template/macros/master">
<tal:comment replace="nothing">Disable standard Plone green tabs</tal:comment>
<div metal:fill-slot="top_slot">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">Fill main slot of Plone main_template</tal:comment>
<body>
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
contentType request/type_name;
tool python: portal.get('portal_%s' % appFolder.id.lower());
searchableFields python: tool.getSearchableFields(contentType)">
<tal:comment replace="nothing">Search title</tal:comment>
<h1 tal:content="python: tool.translate('search_title')"></h1><br/>
<tal:comment replace="nothing">Form for searching objects of request/type_name.</tal:comment>
<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 type="hidden" name="flavourNumber:int" tal:attributes="value request/flavourNumber"/>
<tal:searchFields repeat="searchField searchableFields">
<tal:showSearchField define="fieldName python:searchField[0];
appyType python:searchField[1]">
<metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/>
</tal:showSearchField>
</tal:searchFields>
<tal:comment replace="nothing">Submit button</tal:comment>
<p align="right"><br/>
<input type="submit" tal:attributes="value python:tool.translate('search_button')"/>
</p>
</form>
</metal:fill>
</body>
</html>

View file

@ -0,0 +1,39 @@
<metal:searchInteger define-macro="searchInteger">
<p tal:content="fieldName">Hello</p>
</metal:searchInteger>
<metal:searchFloat define-macro="searchFloat">
<p tal:content="fieldName">Hello</p>
</metal:searchFloat>
<metal:searchString define-macro="searchString">
<p tal:content="fieldName">Hello</p>
</metal:searchString>
<metal:searchBoolean define-macro="searchBoolean">
<p tal:content="fieldName">Hello</p>
</metal:searchBoolean>
<metal:searchDate define-macro="searchDate">
<p tal:content="fieldName">Hello</p>
</metal:searchDate>
<metal:searchFile define-macro="searchFile">
<p tal:content="fieldName">Hello</p>
</metal:searchFile>
<metal:searchRef define-macro="searchRef">
<p tal:content="fieldName">Hello</p>
</metal:searchRef>
<metal:searchComputed define-macro="searchComputed">
<p tal:content="fieldName">Hello</p>
</metal:searchComputed>
<metal:searchAction define-macro="searchAction">
<p tal:content="fieldName">Hello</p>
</metal:searchAction>
<metal:searchInfo define-macro="searchInfo">
<p tal:content="fieldName">Hello</p>
</metal:searchInfo>

View file

@ -57,6 +57,10 @@ class PoMessage:
IMPORT_ALREADY = 'Already imported.' IMPORT_ALREADY = 'Already imported.'
IMPORT_MANY = 'Import selected elements' IMPORT_MANY = 'Import selected elements'
IMPORT_DONE = 'Import terminated successfully.' IMPORT_DONE = 'Import terminated successfully.'
SEARCH_TITLE = 'Advanced search'
SEARCH_BUTTON = 'Search'
SEARCH_OBJECTS = 'Search objects of this type.'
SEARCH_RESULTS = 'Search results'
WORKFLOW_COMMENT = 'Optional comment' WORKFLOW_COMMENT = 'Optional comment'
WORKFLOW_STATE = 'state' WORKFLOW_STATE = 'state'
DATA_CHANGE = 'Data change' DATA_CHANGE = 'Data change'

2
version.py Normal file
View file

@ -0,0 +1,2 @@
short='dev'
verbose='dev'