introduced simple search using concept hierarchy

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1329 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-09-06 15:31:42 +00:00
parent 01680a666a
commit 029632fde7
5 changed files with 190 additions and 48 deletions

View file

@ -553,7 +553,7 @@
for="loops.interfaces.INode"
class="loops.browser.node.InlineEdit"
attribute="save"
permission="zope.View"
permission="zope.ManageContent"
/>
<!-- render file or image assigned to a node as target -->

View file

@ -53,7 +53,7 @@ class IQueryConcept(Interface):
viewName = schema.TextLine(
title=_(u'Adapter/View name'),
description=_(u'The name of the (mulit-) adapter (typically a view) '
description=_(u'The name of the (multi-) adapter (typically a view) '
'to be used for the query and for presenting '
'the results'),
default=u'',

View file

@ -50,6 +50,9 @@ In addition we create a concept that holds the search page and a 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
@ -65,6 +68,25 @@ accessed exactly once per row:
>>> 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)
11
>>> t.getTermByToken('loops:resource:*').title
'Any Resource'
>>> t = searchView.conceptTypesForSearch()
>>> len(t)
5
>>> 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
@ -82,22 +104,86 @@ a controller attribute for the search view.
'return submitReplacing("1.results", "1.search.form",
"http://127.0.0.1/loops/views/page/.target19/@@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.
>>> from loops.search.browser import SearchResults
>>> form = {'search.2.title': 'yes', 'search.2.text': u'foo' }
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
Before accessing the `results` property we have to prepare a catalog.
Before accessing the `results` property we have to prepare a (for testing
purposes fairly primitive) catalog and a resource we can search for:
>>> from zope.app.catalog.interfaces import ICatalog
>>> class DummyCat(object):
... implements(ICatalog)
... def searchResults(self, **criteria):
... name = criteria.get('loops_title')
... type = criteria.get('loops_type', ('resource',))
... if name:
... if 'concept' in type[0]:
... result = concepts.get(name)
... else:
... result = resources.get(name)
... if result:
... return [result]
... return []
>>> component.provideUtility(DummyCat())
>>> list(resultsView.results)
[]
>>> from loops.resource import Resource
>>> rplone = resources['plone'] = Resource()
>>> from loops.search.browser 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('Zope')
>>> czope2 = concepts['zope2'] = Concept('Zope 2')
>>> czope3 = concepts['zope3'] = Concept('Zope 3')
>>> cplone = concepts['plone'] = Concept('Plone')
>>> for c in (czope, czope2, czope3, cplone):
... c.conceptType = topic
>>> 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:
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
>>> len(results)
5
>>> results[0].context.__name__
u'plone'
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
>>> len(results)
1
>>> results[0].context.__name__
u'zope3'

View file

@ -31,7 +31,9 @@ from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementatio
from zope.i18nmessageid import MessageFactory
from cybertools.ajax import innerHtml
from cybertools.typology.interfaces import ITypeManager
from loops.browser.common import BaseView
from loops import util
_ = MessageFactory('zope')
@ -59,6 +61,12 @@ class Search(BaseView):
self.maxRowNum = n
return n
def conceptTypesForSearch(self):
general = [('loops:concept:*', 'Any Concept'),]
return util.KeywordVocabulary(general + sorted([(t.tokenForSearch, t.title)
for t in ITypeManager(self.context).types
if 'concept' in t.qualifiers]))
def submitReplacing(self, targetId, formId, view):
self.registerDojo()
return 'return submitReplacing("%s", "%s", "%s")' % (
@ -76,31 +84,73 @@ class SearchResults(BaseView):
def __call__(self):
return innerHtml(self)
@Lazy
def catalog(self):
return component.getUtility(ICatalog)
@Lazy
def results(self):
result = set()
request = self.request
r3 = self.queryConcepts()
type = request.get('search.1.text', 'loops:resource:*')
text = request.get('search.2.text')
if not text and '*' in type: # there should be some sort of selection...
return set()
useTitle = request.get('search.2.title')
useFull = request.get('search.2.full')
r1 = set()
cat = component.getUtility(ICatalog)
if useFull and text and not type.startswith('loops:concept:'):
criteria = {'loops_resource_textng': {'query': text},}
r1 = set(cat.searchResults(**criteria))
if not r3 and not text and '*' in type: # there should be some sort of selection...
return result
if text or not '*' in type:
useTitle = request.get('search.2.title')
useFull = request.get('search.2.full')
r1 = set()
cat = self.catalog
if useFull and text and not type.startswith('loops:concept:'):
criteria = {'loops_resource_textng': {'query': text},}
r1 = set(cat.searchResults(**criteria))
if type.endswith('*'):
start = type[:-1]
end = start + '\x7f'
else:
start = end = type
criteria = {'loops_type': (start, end),}
if useTitle and text:
criteria['loops_title'] = text
r2 = set(cat.searchResults(**criteria))
result = r1.union(r2)
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
if r3 is not None:
if result:
result = result.intersection(r3)
else:
result = r3
result = sorted(result, key=lambda x: x.title.lower())
return self.viewIterator(result)
def queryConcepts(self):
result = set()
cat = self.catalog
request = self.request
type = request.get('search.3.type', 'loops:concept:*')
text = request.get('search.3.text')
if not text and '*' in type:
return None
if type.endswith('*'):
start = type[:-1]
end = start + '\x7f'
else:
start = end = type
criteria = {'loops_type': (start, end),}
if useTitle and text:
if text:
criteria['loops_title'] = text
r2 = set(cat.searchResults(**criteria))
result = r1.union(r2)
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
result.sort(key=lambda x: x.title.lower())
return self.viewIterator(result)
queue = list(cat.searchResults(**criteria))
concepts = []
while queue:
c = queue.pop(0)
concepts.append(c)
for child in c.getChildren():
# TODO: check for tree level, use relevance factors, ...
if child not in queue and child not in concepts:
queue.append(child)
for c in concepts:
result.add(c)
result.update(c.getResources())
return result

View file

@ -17,7 +17,7 @@
<input type="hidden" name="search.resultsId" value="1.results"
tal:attributes="value resultsId" />
<table cellpadding="3">
<tal:block repeat="search_param python: ['type', 'text']">
<tal:block repeat="search_param python: ['type', 'text', 'concept']">
<metal:row use-macro="macros/search_row" />
</tal:block>
<tr tal:condition="nothing">
@ -68,7 +68,7 @@
value param" />
<input type="button" value="&minus;"
title="Remove search parameter"
tal:condition="python: param not in ['type', 'text']" />&nbsp;
tal:condition="python: param not in ['type', 'text', 'concept']" />&nbsp;
</td>
<td>
<div metal:use-macro="macros/?param" />
@ -84,7 +84,7 @@
<select name="text"
tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;">
<tal:types repeat="type view/typesForSearch">
<tal:types repeat="type item/typesForSearch">
<option value="loops:*"
i18n:translate=""
tal:attributes="value type/token"
@ -114,7 +114,7 @@
<label for="full"
tal:attributes="for string:$idPrefix.full">Full text</label>&nbsp;&nbsp;
<label for="text"
tal:attributes="for string:$idPrefix.text">Search words:</label>
tal:attributes="for string:$idPrefix.text">Search text:</label>
<input type="text" name="text"
tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;" />
@ -125,23 +125,29 @@
</metal:text>
<!-- obsolete -->
<tr metal:define-macro="search_row_selectable" id="search.row.1.1">
<td>
<input type="button" value="&minus;"
title="Remove search parameter" />&nbsp;
</td>
<td tal:define="selection search_selection | string:type">
<select>
<option value="type"
tal:attributes="selected python: selection=='type'">Type</option>
<option value="text"
tal:attributes="selected python: selection=='text'">Text</option>
<option value="attribute">Attribute value</option>
<option value="concept">Concept</option>
</select>
<input metal:use-macro="template/macros/?selection" />
</td>
</tr>
<metal:text define-macro="concept">
<div>
<h3>Search via related conceps</h3>
<label for="type"
tal:attributes="for string:$idPrefix.type">Type:</label>
<select name="type"
tal:attributes="name string:$namePrefix.type;
id string:$idPrefix.type;">
<tal:types repeat="type item/conceptTypesForSearch">
<option value="loops:*"
i18n:translate=""
tal:attributes="value type/token"
tal:content="type/title">Topic</option>
</tal:types>
</select>&nbsp;&nbsp;
<label for="text"
tal:attributes="for string:$idPrefix.text">Search text:</label>
<input type="text" name="text"
tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;" />
<input type="button" value="+"
title="Add type"
tal:condition="nothing" />&nbsp;
</div>
</metal:text>