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

View file

@ -53,7 +53,7 @@ class IQueryConcept(Interface):
viewName = schema.TextLine( viewName = schema.TextLine(
title=_(u'Adapter/View name'), 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 ' 'to be used for the query and for presenting '
'the results'), 'the results'),
default=u'', 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 = views['page'] = Node('Search Page')
>>> page.target = search >>> page.target = search
Search views
------------
Now we are ready to create a search view object: Now we are ready to create a search view object:
>>> from zope.publisher.browser import TestRequest >>> from zope.publisher.browser import TestRequest
@ -65,6 +68,25 @@ accessed exactly once per row:
>>> searchView.rowNum >>> searchView.rowNum
2 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 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 view for our page. The submitReplacing method returns a JavaScript call
that will replace the results part on the search page; as this registers 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", 'return submitReplacing("1.results", "1.search.form",
"http://127.0.0.1/loops/views/page/.target19/@@searchresults.html")' "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 The searchresults.html view, i.e. the SearchResults view class provides the
result set of the search via its `results` property. result set of the search via its `results` property.
>>> from loops.search.browser import SearchResults Before accessing the `results` property we have to prepare a (for testing
>>> form = {'search.2.title': 'yes', 'search.2.text': u'foo' } purposes fairly primitive) catalog and a resource we can search for:
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
Before accessing the `results` property we have to prepare a catalog.
>>> from zope.app.catalog.interfaces import ICatalog >>> from zope.app.catalog.interfaces import ICatalog
>>> class DummyCat(object): >>> class DummyCat(object):
... implements(ICatalog) ... implements(ICatalog)
... def searchResults(self, **criteria): ... 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 [] ... return []
>>> component.provideUtility(DummyCat()) >>> 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 zope.i18nmessageid import MessageFactory
from cybertools.ajax import innerHtml from cybertools.ajax import innerHtml
from cybertools.typology.interfaces import ITypeManager
from loops.browser.common import BaseView from loops.browser.common import BaseView
from loops import util
_ = MessageFactory('zope') _ = MessageFactory('zope')
@ -59,6 +61,12 @@ class Search(BaseView):
self.maxRowNum = n self.maxRowNum = n
return 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): def submitReplacing(self, targetId, formId, view):
self.registerDojo() self.registerDojo()
return 'return submitReplacing("%s", "%s", "%s")' % ( return 'return submitReplacing("%s", "%s", "%s")' % (
@ -76,31 +84,73 @@ class SearchResults(BaseView):
def __call__(self): def __call__(self):
return innerHtml(self) return innerHtml(self)
@Lazy
def catalog(self):
return component.getUtility(ICatalog)
@Lazy @Lazy
def results(self): def results(self):
result = set()
request = self.request request = self.request
r3 = self.queryConcepts()
type = request.get('search.1.text', 'loops:resource:*') type = request.get('search.1.text', 'loops:resource:*')
text = request.get('search.2.text') text = request.get('search.2.text')
if not text and '*' in type: # there should be some sort of selection... if not r3 and not text and '*' in type: # there should be some sort of selection...
return set() return result
useTitle = request.get('search.2.title') if text or not '*' in type:
useFull = request.get('search.2.full') useTitle = request.get('search.2.title')
r1 = set() useFull = request.get('search.2.full')
cat = component.getUtility(ICatalog) r1 = set()
if useFull and text and not type.startswith('loops:concept:'): cat = self.catalog
criteria = {'loops_resource_textng': {'query': text},} if useFull and text and not type.startswith('loops:concept:'):
r1 = set(cat.searchResults(**criteria)) 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('*'): if type.endswith('*'):
start = type[:-1] start = type[:-1]
end = start + '\x7f' end = start + '\x7f'
else: else:
start = end = type start = end = type
criteria = {'loops_type': (start, end),} criteria = {'loops_type': (start, end),}
if useTitle and text: if text:
criteria['loops_title'] = text criteria['loops_title'] = text
r2 = set(cat.searchResults(**criteria)) queue = list(cat.searchResults(**criteria))
result = r1.union(r2) concepts = []
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] while queue:
result.sort(key=lambda x: x.title.lower()) c = queue.pop(0)
return self.viewIterator(result) 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" <input type="hidden" name="search.resultsId" value="1.results"
tal:attributes="value resultsId" /> tal:attributes="value resultsId" />
<table cellpadding="3"> <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" /> <metal:row use-macro="macros/search_row" />
</tal:block> </tal:block>
<tr tal:condition="nothing"> <tr tal:condition="nothing">
@ -68,7 +68,7 @@
value param" /> value param" />
<input type="button" value="&minus;" <input type="button" value="&minus;"
title="Remove search parameter" 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>
<td> <td>
<div metal:use-macro="macros/?param" /> <div metal:use-macro="macros/?param" />
@ -84,7 +84,7 @@
<select name="text" <select name="text"
tal:attributes="name string:$namePrefix.text; tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;"> id string:$idPrefix.text;">
<tal:types repeat="type view/typesForSearch"> <tal:types repeat="type item/typesForSearch">
<option value="loops:*" <option value="loops:*"
i18n:translate="" i18n:translate=""
tal:attributes="value type/token" tal:attributes="value type/token"
@ -114,7 +114,7 @@
<label for="full" <label for="full"
tal:attributes="for string:$idPrefix.full">Full text</label>&nbsp;&nbsp; tal:attributes="for string:$idPrefix.full">Full text</label>&nbsp;&nbsp;
<label for="text" <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" <input type="text" name="text"
tal:attributes="name string:$namePrefix.text; tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;" /> id string:$idPrefix.text;" />
@ -125,23 +125,29 @@
</metal:text> </metal:text>
<!-- obsolete --> <metal:text define-macro="concept">
<tr metal:define-macro="search_row_selectable" id="search.row.1.1"> <div>
<td> <h3>Search via related conceps</h3>
<input type="button" value="&minus;" <label for="type"
title="Remove search parameter" />&nbsp; tal:attributes="for string:$idPrefix.type">Type:</label>
</td> <select name="type"
<td tal:define="selection search_selection | string:type"> tal:attributes="name string:$namePrefix.type;
<select> id string:$idPrefix.type;">
<option value="type" <tal:types repeat="type item/conceptTypesForSearch">
tal:attributes="selected python: selection=='type'">Type</option> <option value="loops:*"
<option value="text" i18n:translate=""
tal:attributes="selected python: selection=='text'">Text</option> tal:attributes="value type/token"
<option value="attribute">Attribute value</option> tal:content="type/title">Topic</option>
<option value="concept">Concept</option> </tal:types>
</select> </select>&nbsp;&nbsp;
<input metal:use-macro="template/macros/?selection" /> <label for="text"
</td> tal:attributes="for string:$idPrefix.text">Search text:</label>
</tr> <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>