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
poMsg = msg(app, '', app); poMsg.produceNiceDefault()
self.labels += [poMsg,
msg('workflow_state', '', msg.WORKFLOW_STATE),
msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE),
msg('phase', '', msg.PHASE),
msg('root_type', '', msg.ROOT_TYPE),
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
msg('choose_a_value', '', msg.CHOOSE_A_VALUE),
msg('choose_a_doc', '', msg.CHOOSE_A_DOC),
msg('min_ref_violated', '', msg.MIN_REF_VIOLATED),
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD),
msg('ref_name', '', msg.REF_NAME),
msg('ref_actions', '', msg.REF_ACTIONS),
msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_int', '', msg.BAD_INT),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
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),
msg('workflow_state', '', msg.WORKFLOW_STATE),
msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE),
msg('phase', '', msg.PHASE),
msg('root_type', '', msg.ROOT_TYPE),
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
msg('choose_a_value', '', msg.CHOOSE_A_VALUE),
msg('choose_a_doc', '', msg.CHOOSE_A_DOC),
msg('min_ref_violated', '', msg.MIN_REF_VIOLATED),
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD),
msg('ref_name', '', msg.REF_NAME),
msg('ref_actions', '', msg.REF_ACTIONS),
msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE),
msg('search_title', '', msg.SEARCH_TITLE),
msg('search_button', '', msg.SEARCH_BUTTON),
msg('search_objects', '', msg.SEARCH_OBJECTS),
msg('search_results', '', msg.SEARCH_RESULTS),
msg('search_results_descr', '', ' '),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_int', '', msg.BAD_INT),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
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)
self.generateTool()

View file

@ -1,5 +1,6 @@
# ------------------------------------------------------------------------------
import re, os, os.path, Cookie
from appy.gen import Type
from appy.gen.utils import FieldDescr, SomeObjects
from appy.gen.plone25.mixins import AbstractMixin
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
with commas) in Plone's portal_catalog. Portal types are from the
flavour numbered p_flavourNumber. If p_searchName is specified, it
corresponds to a search defined on p_contentType: additional search
criteria will be added to the query. 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
corresponds to:
1) a search defined on p_contentType: additional search criteria
will be added to the query, or;
2) "_advanced": in this case, additional search criteria will also
be added to the query, but those criteria come from the session
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
appy.gen.utils). We return the __dict__ attribute instead of real
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.
appyClass = self.getAppyClass(contentType)
if searchName:
search = ArchetypesClassDescriptor.getSearch(
appyClass, searchName)
if searchName != '_advanced':
search = ArchetypesClassDescriptor.getSearch(
appyClass, searchName)
if search:
# Add additional search criteria
for fieldName, fieldValue in search.fields.iteritems():
@ -362,6 +371,26 @@ class ToolMixin(AbstractMixin):
else:
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):
'''Returns the translated version of messages that must be shown in
Javascript popups.'''

View file

@ -639,7 +639,7 @@
startNumber request/startNumber|python:'0';
startNumber python: int(startNumber);
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;
severalTypes python: contentType and (contentType.find(',') != -1);
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);
src string: $portal_url/skyn/import.png;
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>
</tr>
</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_MANY = 'Import selected elements'
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_STATE = 'state'
DATA_CHANGE = 'Data change'

2
version.py Normal file
View file

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