Basic refactoring of standard search functionality.

Move everything to loops.expert, making loops.search obsolete.
This commit is contained in:
Helmut Merz 2011-05-20 10:55:44 +02:00
parent 12de2376ac
commit ad8c236aa5
12 changed files with 819 additions and 14 deletions

View file

@ -108,9 +108,9 @@ class NodeView(BaseView):
subMacros=[node_macros.macros['page_actions'],
i18n_macros.macros['language_switch']])
if self.globalOptions('expert.quicksearch'):
from loops.expert.browser.search import searchMacrosTemplate
from loops.expert.browser.search import search_template
cm.register('top_actions', 'top_quicksearch', name='multi_actions',
subMacros=[searchMacrosTemplate.macros['quicksearch']],
subMacros=[search_template.macros['quicksearch']],
priority=20)
cm.register('portlet_left', 'navigation', title='Navigation',
subMacro=node_macros.macros['menu'])

View file

@ -474,7 +474,7 @@
<include package=".media" />
<include package=".organize" />
<include package=".rest" />
<include package=".search" />
<!--<include package=".search" />-->
<include package=".security" />
<include package=".system" />
<include package=".versioning" />

View file

@ -16,7 +16,31 @@
<browser:page
name="search.html"
for="loops.interfaces.INode"
class="loops.expert.browser.search.SearchResults"
class="loops.expert.browser.search.QuickSearchResults"
permission="zope.View" />
<zope:adapter
name="search"
for="loops.interfaces.IConcept
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.expert.browser.search.Search"
permission="zope.View"
/>
<browser:page
name="listConceptsForComboBox.js"
for="loops.interfaces.ILoopsObject"
class="loops.expert.browser.search.Search"
attribute="listConcepts"
permission="zope.View"
/>
<browser:page
name="searchresults.html"
for="loops.interfaces.ILoopsObject"
class="loops.expert.browser.search.SearchResults"
permission="zope.View"
/>
</configure>

View file

@ -20,6 +20,57 @@
</div>
<metal:search define-macro="search"
i18n:domain="loops">
<div id="search"
tal:define="macros item/search_macros;
idPrefix string:${view/itemNum}.search;
formId string:$idPrefix.form;
resultsId string:$idPrefix.results;
submitted request/search.submitted|nothing">
<h1 tal:attributes="class string:content-$level;
ondblclick item/openEditWindow"
tal:content="item/title">
Search
</h1>
<div metal:define-macro="search_form" class="searchForm">
<fieldset class="box">
<form action="." method="post" id="1.search.form"
tal:attributes="id formId">
<input type="hidden" name="search.submitted" value="yes" />
<input type="hidden" name="search.resultsId" value="1.results"
tal:attributes="value resultsId" />
<table cellpadding="3">
<tal:block repeat="search_param python:
['type', 'text', 'concept', 'state']">
<metal:row use-macro="macros/search_row" />
</tal:block>
<tr tal:condition="nothing">
<td colspan="2">
<input type="button" value="Add concept filter" class="button" />
<input type="button" value="Add attribute filter" class="button" />
</td>
</tr>
<tr>
<td></td>
<td colspan="3"><br />
<input type="submit" name="button.search" value="Search" class="submit"
i18n:attributes="value"
tal:attributes="xx_onclick python:
item.submitReplacing(resultsId, formId, view)" />
</td>
</tr>
</table>
</form>
</fieldset>
</div>
<tal:results condition="request/search.submitted|nothing">
<metal:results use-macro="item/search_macros/results" />
</tal:results>
</div>
</metal:search>
<div metal:define-macro="search_results" id="search.results"
tal:define="item nocall:view">
<fieldset class="box">
@ -87,5 +138,240 @@
</div>
<div metal:define-macro="search_row" id="1.1.row"
tal:define="rowNum item/rowNum;
basicIdPrefix idPrefix;
idPrefix string:$idPrefix.$rowNum;
namePrefix string:search.$rowNum;
param search_param | item/searchParam"
tal:attributes="id string:$idPrefix.row">
<div metal:use-macro="macros/?param" />
</div>
<metal:text define-macro="type">
<tr>
<td metal:use-macro="macros/minus"/>
<td colspan="3">
<h2 i18n:translate="">Type(s) to search for</h2>
</td>
<tr>
<td></td>
<td>
<label for="text"
tal:attributes="for string:$idPrefix.text">
<span i18n:translate="">Type</span>:</label>
<select name="text"
tal:define="name string:$namePrefix.text;
value request/?name|nothing"
tal:attributes="name name;
id string:$idPrefix.text;">
<tal:types repeat="type item/typesForSearch">
<option value="loops:*"
i18n:translate=""
tal:attributes="value type/token;
selected python: value == type.token"
tal:content="type/title">Topic</option>
</tal:types>
</select>
<input type="button" value="+"
title="Add type"
tal:condition="nothing" />&nbsp;
</td>
<td colspan="2"></td>
</tr>
</metal:text>
<metal:text define-macro="text">
<tr>
<td metal:use-macro="macros/minus"/>
<td colspan="3">
<h2 i18n:translate="">Text-based search</h2>
</td>
<tr>
<td></td>
<td>
<input type="checkbox" value="yes"
tal:define="name string:$namePrefix.title"
tal:attributes="name name;
id string:$idPrefix.title;
checked request/?name|not:submitted|string:yes" />
<label for="title"
i18n:translate=""
tal:attributes="for string:$idPrefix.title">Title</label>
<input type="checkbox" value="yes"
tal:define="name string:$namePrefix.full"
tal:attributes="name name;
id string:$idPrefix.full;
checked request/?name|nothing" />
<label for="full"
i18n:translate=""
tal:attributes="for string:$idPrefix.full">Full text</label>&nbsp;&nbsp;
</td>
<td>
<label for="text"
tal:attributes="for string:$idPrefix.text">
<span i18n:translate="">Search text</span>:</label>
</td>
<td>
<input type="text"
tal:define="name string:$namePrefix.text"
tal:attributes="name name;
id string:$idPrefix.text;
value request/?name|nothing" />
<input type="button" value="+"
title="Add search word"
tal:condition="nothing" />&nbsp;
</td>
</tr>
</metal:text>
<metal:text define-macro="concept">
<tr>
<td metal:use-macro="macros/minus"/>
<td colspan="3">
<h2 i18n:translate="">Search via related concepts</h2>
</td>
<tr tal:repeat="type item/presetSearchTypes">
<tal:preset define="rowNum item/rowNum;
idPrefix string:$basicIdPrefix.$rowNum;
namePrefix string:search.$rowNum;">
<td></td>
<td>
<span i18n:translate="">Type</span>:
<b tal:content="type/title" i18n:translate="" />
<input type="hidden" name="type" value=""
tal:attributes="name string:$namePrefix.type;
value type/token" />
</td>
<td>
<label for="text"
tal:attributes="for string:$idPrefix.text">
<span i18n:translate="">Concept for Search</span>:</label>
</td>
<td>
<select name="text_selected"
tal:define="name string:$namePrefix.text_selected;
value request/?name|nothing"
tal:attributes="name name;
id string:$idPrefix.text;">
<tal:concepts repeat="concept python: item.conceptsForType(type['token'])">
<option tal:attributes="value concept/token;
selected python: value == concept['token']"
i18n:translate=""
tal:content="concept/title">Zope Corp</option>
</tal:concepts>
</select>
</td>
</tal:preset>
</tr>
<tr>
<td></td>
<td>
<label for="type"
tal:attributes="for string:$idPrefix.type">
<span i18n:translate="">Type</span>:</label>
<select name="type"
tal:define="name string:$namePrefix.type;
global conceptTypeValue request/?name|string:"
tal:attributes="name name;
id string:$idPrefix.type;
value conceptTypeValue;
onChange string:setConceptTypeForComboBox('$idPrefix.type', '$idPrefix.text')">
<tal:types repeat="type item/conceptTypesForSearch">
<option value="loops:*"
i18n:translate=""
tal:attributes="value type/token;
selected python: conceptTypeValue == type.token"
tal:content="type/title">Topic</option>
</tal:types>
</select>&nbsp;&nbsp;
</td>
<td>
<label for="text"
tal:attributes="for string:$idPrefix.text">
<span i18n:translate="">Concept for Search</span>:</label>
<input type="text" name="text"
tal:condition="nothing"
tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;" />
<input type="button" value="+"
title="Add type"
tal:condition="nothing" />&nbsp;
</td>
<td>
<tal:combo tal:define="dummy item/initDojo;
name string:$namePrefix.text;
value request/?name|nothing;
concept python:
value and view.getObjectForUid(value);
displayValue python:
concept and concept.title or u''">
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
url="listConceptsForComboBox.js?searchType="
tal:attributes="url
string:listConceptsForComboBox.js?searchType=$conceptTypeValue">
</div>
<input dojoType="dijit.form.FilteringSelect" store="conceptSearch"
autoComplete="False" labelAttr="name" style="height: 16px"
name="concept.search.text" id="concept.search.text"
tal:attributes="name name;
id string:$idPrefix.text;
displayedValue displayValue" />
</tal:combo>
</td>
</tr>
</metal:text>
<metal:text define-macro="state"
tal:define="stateDefs item/statesDefinitions;
deftype string:resource"
tal:condition="stateDefs">
<tr>
<td metal:use-macro="macros/minus"/>
<td colspan="3">
<h2 i18n:translate="">Restrict to objects with certain states</h2>
</td>
<tr>
<td></td>
<th i18n:translate="">Workflow</th>
<th colspan="2"
i18n:translate="">States</th>
</tr>
<tr tal:repeat="def stateDefs">
<td></td>
<td valign="top"
tal:content="def/name"
i18n:translate=""></td>
<td colspan="2">
<tal:states repeat="state def/states">
<tal:state define="name string:state.$deftype.${def/name};
value state/name">
<input type="checkbox"
tal:attributes="name string:$name:list;
value value;
checked python:
value in item.selectedStates.get(name, ());
id string:$name.$value"
/>&nbsp;<label tal:content="state/title"
i18n:translate=""
tal:attributes="for string:$name.$value" />
&nbsp;
</tal:state>
</tal:states>
</td>
</tr>
</metal:text>
<td metal:define-macro="minus">
<input type="button" value="&minus;"
title="Remove search parameter"
tal:condition="python:
param not in ['type', 'text', 'concept', 'state']" />&nbsp;
</td>
</html>

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -28,22 +28,26 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.traversing.api import getName, getParent
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
from loops.browser.common import BaseView
from loops.browser.node import NodeView
from loops.common import adapted, AdapterBase
from loops.expert.concept import ConceptQuery, FullQuery
from loops.interfaces import IResource
from loops.organize.personal.browser.filter import FilterView
from loops import util
from loops.util import _
searchMacrosTemplate = ViewPageTemplateFile('search.pt')
search_template = ViewPageTemplateFile('search.pt')
class SearchResults(NodeView):
class QuickSearchResults(NodeView):
""" Provides results listing """
@Lazy
def search_macros(self):
return self.controller.getTemplateMacros('search', searchMacrosTemplate)
return self.controller.getTemplateMacros('search', search_template)
@Lazy
def macro(self):
@ -64,3 +68,250 @@ class SearchResults(NodeView):
result = fv.apply(result)
result.sort(key=lambda x: x.title)
return self.viewIterator(result)
class Search(BaseView):
maxRowNum = 0
@Lazy
def search_macros(self):
return self.controller.getTemplateMacros('search', search_template)
@Lazy
def macro(self):
return self.search_macros['search']
@property
def rowNum(self):
""" Return the rowNum to be used for identifying the current search
parameter row.
"""
n = self.request.get('loops.rowNum', 0)
if n: # if given directly we don't use the calculation
return n
n = (self.maxRowNum or self.request.get('loops.maxRowNum', 0)) + 1
self.maxRowNum = n
return n
@Lazy
def presetSearchTypes(self):
""" Return a list of concept type info dictionaries (see BaseView)
that should be displayed on a separate search parameter row.
"""
#return ITypeManager(self.context).listTypes(include=('search',))
return self.listTypesForSearch(include=('search',))
def conceptsForType(self, token):
result = ConceptQuery(self).query(type=token)
fv = FilterView(self.context, self.request)
result = fv.apply(result)
result.sort(key=lambda x: x.title)
noSelection = dict(token='none', title=u'not selected')
return [noSelection] + [dict(title=adapted(o, self.languageInfo).title,
token=util.getUidForObject(o))
for o in result]
def initDojo(self):
self.registerDojo()
cm = self.controller.macros
jsCall = ('dojo.require("dojo.parser");'
'dojo.require("dijit.form.FilteringSelect");'
'dojo.require("dojox.data.QueryReadStore");')
cm.register('js-execute', jsCall, jsCall=jsCall)
def listConcepts(self, filterMethod=None):
""" Used for dijit.FilteringSelect.
"""
request = self.request
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
title = request.get('name')
if title == '*':
title = None
types = request.get('searchType')
data = []
if title or types:
if title is not None:
title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
#title = title.split(' ', 1)[0]
if not types:
types = ['loops:concept:*']
if not isinstance(types, (list, tuple)):
types = [types]
for type in types:
result = self.executeQuery(title=title or None, type=type,
exclude=('hidden',))
fv = FilterView(self.context, self.request)
result = fv.apply(result)
for o in result:
if o.getLoopsRoot() == self.loopsRoot:
adObj = adapted(o, self.languageInfo)
if filterMethod is not None and not filterMethod(adObj):
continue
name = self.getRowName(adObj)
if title and title.endswith('*'):
title = title[:-1]
sort = ((title and name.startswith(title) and '0' or '1')
+ name.lower())
if o.conceptType is None:
raise ValueError('Concept Type missing for %r.' % name)
data.append({'label': self.getRowLabel(adObj, name),
'name': name,
'id': util.getUidForObject(o),
'sort': sort})
data.sort(key=lambda x: x['sort'])
if not title:
data.insert(0, {'label': '', 'name': '', 'id': ''})
json = []
for item in data[:100]:
json.append("{label: '%s', name: '%s', id: '%s'}" %
(item['label'], item['name'], item['id']))
json = "{identifier: 'id', items: [%s]}" % ', '.join(json)
#print '***', json
return json
def executeQuery(self, **kw):
return ConceptQuery(self).query(**kw)
def getRowName(self, obj):
return obj.getLongTitle()
def getRowLabel(self, obj, name=None):
if isinstance(obj, AdapterBase):
obj = obj.context
if name is None:
name = obj.title
return '%s (%s)' % (name, obj.conceptType.title)
@Lazy
def statesDefinitions(self):
return [component.getUtility(IStatesDefinition, name=n)
for n in self.globalOptions('organize.stateful.resource', ())]
@Lazy
def selectedStates(self):
result = {}
for k, v in self.request.form.items():
if k.startswith('state.') and v:
result[k] = v
return result
def submitReplacing(self, targetId, formId, view):
self.registerDojo()
return 'submitReplacing("%s", "%s", "%s"); return false;' % (
targetId, formId,
'%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId))
@Lazy
def results(self):
form = self.request.form
type = form.get('search.1.text', 'loops:*')
text = form.get('search.2.text')
if text is not None:
text = util.toUnicode(text, encoding='ISO8859-15') # IE hack!!!
useTitle = form.get('search.2.title')
useFull = form.get('search.2.full')
conceptType = form.get('search.3.type', 'loops:concept:*')
#conceptTitle = form.get('search.3.text')
#if conceptTitle is not None:
# conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
conceptUid = form.get('search.3.text')
result = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull,
#conceptTitle=conceptTitle,
conceptUid=conceptUid,
conceptType=conceptType)
rowNum = 4
while rowNum < 10:
addCriteria = form.get('search.%i.text_selected' % rowNum)
rowNum += 1
if not addCriteria:
break
if addCriteria == 'none':
continue
addSelection = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull,
conceptUid=addCriteria)
if result:
result = [r for r in result if r in addSelection]
else:
result = addSelection
result = [r for r in result if self.checkStates(r)]
fv = FilterView(self.context, self.request)
result = fv.apply(result)
result = sorted(result, key=lambda x: x.title.lower())
return self.viewIterator(result)
def checkStates(self, obj):
if not IResource.providedBy(obj):
return True
for std, states in self.selectedStates.items():
if std.startswith('state.resource.'):
std = std[len('state.resource.'):]
else:
continue
stf = component.queryAdapter(obj, IStateful, name=std)
if stf is None:
continue
for state in states:
if stf.state == state:
break
else:
return False
return True
#class SearchResults(BaseView):
class SearchResults(NodeView):
""" Provides results as inner HTML """
@Lazy
def search_macros(self):
return self.controller.getTemplateMacros('search', search_template)
@Lazy
def macro(self):
return self.search_macros['search_results']
def __call__(self):
return innerHtml(self)
@Lazy
def results(self):
form = self.request.form
type = form.get('search.1.text', 'loops:*')
text = form.get('search.2.text')
if text is not None:
text = util.toUnicode(text, encoding='ISO8859-15') # IE hack!!!
useTitle = form.get('search.2.title')
useFull = form.get('search.2.full')
conceptType = form.get('search.3.type', 'loops:concept:*')
#conceptTitle = form.get('search.3.text')
#if conceptTitle is not None:
# conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
conceptUid = form.get('search.3.text')
result = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull,
#conceptTitle=conceptTitle,
conceptUid=conceptUid,
conceptType=conceptType)
rowNum = 4
while rowNum < 10:
addCriteria = form.get('search.%i.text_selected' % rowNum)
rowNum += 1
if not addCriteria:
break
if addCriteria == 'none':
continue
addSelection = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull,
conceptUid=addCriteria)
if result:
result = [r for r in result if r in addSelection]
else:
result = addSelection
fv = FilterView(self.context, self.request)
result = fv.apply(result)
result = sorted(result, key=lambda x: x.title.lower())
return self.viewIterator(result)

239
expert/search.txt Executable file
View file

@ -0,0 +1,239 @@
===================================================================
loops.search - Provide search functionality for the loops framework
===================================================================
($Id$)
Let's do some basic set up
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from zope import component, interface
>>> from zope.interface import implements
and setup a simple loops site with a concept manager and some concepts
(with all the type machinery, what in real life is done via standard
ZCML setup):
>>> from loops.concept import Concept
>>> from loops.type import ConceptType, TypeConcept
>>> from loops.interfaces import ITypeConcept
>>> from loops.base import Loops
>>> from loops.expert.testsetup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
>>> loopsRoot = site['loops']
>>> query = concepts['query']
>>> topic = concepts['topic']
In addition we create a concept that holds the search page and a node
(page) that links to this concept:
>>> search = concepts['search'] = Concept(u'Search')
>>> search.conceptType = query
>>> from loops.view import Node
>>> page = views['page'] = Node('Search Page')
>>> page.target = search
Search views
------------
Now we are ready to create a search view object:
>>> from zope.publisher.browser import TestRequest
>>> from loops.expert.browser.search import Search
>>> searchView = Search(search, TestRequest())
The search view provides values for identifying the search form itself
and the parameter rows; the rowNum is auto-incremented, so it should be
accessed exactly once per row:
>>> searchView.rowNum
1
>>> searchView.rowNum
2
The search view provides vocabularies for types that allow the selection
of types to search for; this needs an ITypeManager adapter registered via
zcml in real life:
>>> from loops.type import LoopsTypeManager
>>> component.provideAdapter(LoopsTypeManager)
>>> t = searchView.typesForSearch()
>>> len(t)
14
>>> t.getTermByToken('loops:resource:*').title
'Any Resource'
>>> t = searchView.conceptTypesForSearch()
>>> len(t)
11
>>> t.getTermByToken('loops:concept:*').title
'Any Concept'
To execute the search in the context of a node we have to set up a node
view for our page. The submitReplacing method returns a JavaScript call
that will replace the results part on the search page; as this registers
the dojo library with the view's controller we also have to supply
a controller attribute for the search view.
>>> from loops.browser.node import NodeView
>>> request = TestRequest()
>>> pageView = NodeView(page, request)
>>> from cybertools.browser.liquid.controller import Controller
>>> searchView.controller = Controller(searchView, request)
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
'submitReplacing("1.results", "1.search.form",
"http://127.0.0.1/loops/views/page/.target80/@@searchresults.html");...'
Basic (text/title) search
-------------------------
The searchresults.html view, i.e. the SearchResults view class provides the
result set of the search via its `results` property.
Before accessing the `results` property we have to prepare a
resource we can search for and index it in the catalog.
>>> from loops.resource import Resource
>>> rplone = resources['plone'] = Resource()
>>> from zope.app.catalog.interfaces import ICatalog
>>> from loops import util
>>> catalog = component.getUtility(ICatalog)
>>> catalog.index_doc(int(util.getUidForObject(rplone)), rplone)
>>> from loops.expert.browser.search import SearchResults
>>> form = {'search.2.title': True, 'search.2.text': u'plone'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
>>> len(results)
1
>>> results[0].context == rplone
True
>>> form = {'search.2.title': True, 'search.2.text': u'foo'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> len(list(resultsView.results))
0
Search via related concepts
---------------------------
We first have to prepare some test concepts (topics); we also assign our test
resource (rplone) from above to one of the topics:
>>> czope = concepts['zope'] = Concept(u'Zope')
>>> czope2 = concepts['zope2'] = Concept(u'Zope 2')
>>> czope3 = concepts['zope3'] = Concept(u'Zope 3')
>>> cplone = concepts['plone'] = Concept(u'Plone')
>>> for c in (czope, czope2, czope3, cplone):
... c.conceptType = topic
... catalog.index_doc(int(util.getUidForObject(c)), c)
>>> czope.assignChild(czope2)
>>> czope.assignChild(czope3)
>>> czope2.assignChild(cplone)
>>> rplone.assignConcept(cplone)
Now we can fill our search form and execute the query; note that all concepts
found are listed, plus all their children and all resources associated
with them:
>>> uid = util.getUidForObject(concepts['zope'])
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
>>> len(results)
5
>>> results[0].context.__name__
u'plone'
>>> uid = util.getUidForObject(concepts['zope3'])
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
>>> len(results)
1
>>> results[0].context.__name__
u'zope3'
To support easy entry of concepts to search for we can preselect the available
concepts (optionally restricted to a certain type) by entering text parts
of the concepts' titles:
>>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'}
>>> request = TestRequest(form=form)
>>> view = Search(page, request)
>>> view.listConcepts()
u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '85'}, {label: 'Zope 2 (Topic)', name: 'Zope 2', id: '87'}, {label: 'Zope 3 (Topic)', name: 'Zope 3', id: '89'}]}"
Preset Concept Types on Search Forms
------------------------------------
Often we want to include certain types in our search. We can instruct
the search form to include lines for these types by giving these types
a certain qualifier, via the option attribute of the type interface.
Let's start with a new type, the customer type.
>>> customer = concepts['customer']
>>> custType = ITypeConcept(customer)
>>> custType.options
[]
>>> cust1 = concepts['cust1']
>>> cust2 = concepts['cust2']
>>> for c in (cust1, cust2):
... c.conceptType = customer
... catalog.index_doc(int(util.getUidForObject(c)), c)
>>> from cybertools.typology.interfaces import IType
>>> IType(cust1).qualifiers
('concept',)
>>> searchView = Search(search, TestRequest())
>>> list(searchView.presetSearchTypes)
[]
We can now add a 'search' qualifier to the customer type's options
and thus include the customer type in the preset search types.
>>> custType.options = ('qualifier:search',)
>>> IType(cust1).qualifiers
('concept', 'search')
>>> searchView = Search(search, TestRequest())
>>> list(searchView.presetSearchTypes)
[{'token': 'loops:concept:customer', 'title': u'Customer'}]
>>> searchView.conceptsForType('loops:concept:customer')
[{'token': 'none', 'title': u'not selected'},
{'token': '58', 'title': u'Customer 1'},
{'token': '60', 'title': u'Customer 2'},
{'token': '62', 'title': u'Customer 3'}]
Let's use this new search option for querying:
>>> form = {'search.4.text_selected': u'58'}
>>> resultsView = SearchResults(page, TestRequest(form=form))
>>> results = list(resultsView.results)
>>> results[0].title
u'Customer 1'
Automatic Filtering
-------------------
TODO - more to come...

View file

@ -24,6 +24,7 @@ def test_suite():
return unittest.TestSuite((
unittest.makeSuite(Test),
DocFileSuite('README.txt', optionflags=flags),
DocFileSuite('search.txt', optionflags=flags),
))
if __name__ == '__main__':

View file

@ -31,7 +31,7 @@ from cybertools.stateful.interfaces import IStateful, IStatesDefinition
from loops.browser.common import BaseView
from loops.browser.concept import ConceptView
from loops.expert.query import And, Or, State, Type, getObjects
from loops.search.browser import search_template
from loops.expert.browser.search import search_template
from loops.util import _

View file

@ -45,7 +45,7 @@ Search views
Now we are ready to create a search view object:
>>> from zope.publisher.browser import TestRequest
>>> from loops.search.browser import Search
>>> from loops.expert.browser.search import Search
>>> searchView = Search(search, TestRequest())
The search view provides values for identifying the search form itself
@ -110,7 +110,7 @@ resource we can search for and index it in the catalog.
>>> catalog = component.getUtility(ICatalog)
>>> catalog.index_doc(int(util.getUidForObject(rplone)), rplone)
>>> from loops.search.browser import SearchResults
>>> from loops.expert.browser.search import SearchResults
>>> form = {'search.2.title': True, 'search.2.text': u'plone'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)

View file

@ -37,6 +37,7 @@ from cybertools.typology.interfaces import ITypeManager
from loops.browser.common import BaseView
from loops.browser.node import NodeView
from loops.common import adapted, AdapterBase
#from loops.expert.browser.search import searchMacrosTemplate as search_template
from loops.expert.concept import ConceptQuery, FullQuery
from loops.interfaces import IResource
from loops.organize.personal.browser.filter import FilterView
@ -44,7 +45,7 @@ from loops import util
from loops.util import _
search_template = ViewPageTemplateFile('search.pt')
#search_template = ViewPageTemplateFile('search.pt')
class Search(BaseView):

View file

@ -6,7 +6,7 @@
i18n_domain="zope"
>
<zope:adapter
<!--<zope:adapter
name="search"
for="loops.interfaces.IConcept
zope.publisher.interfaces.browser.IBrowserRequest"
@ -28,6 +28,6 @@
for="loops.interfaces.ILoopsObject"
class="loops.search.browser.SearchResults"
permission="zope.View"
/>
/>-->
</configure>

View file

@ -43,6 +43,9 @@
</form>
</fieldset>
</div>
<tal:results condition="request/search.submitted|nothing">
<metal:results use-macro="item/search_macros/results" />
</tal:results>
<div metal:define-macro="search_results" id="1.search.results"
tal:attributes="id resultsId | request/search.resultsId"