moved query logic to query module; use standard text index instead of TextIndexNG

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1344 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-09-17 11:01:16 +00:00
parent 2d25015202
commit 92f6a06e01
9 changed files with 191 additions and 120 deletions

View file

@ -317,8 +317,8 @@ Index attributes adapter
>>> from loops.resource import IndexAttributes
>>> idx = IndexAttributes(doc1)
>>> idx.text()
u'doc1 Zope Info'
>>> idx.text() is None
True
>>> idx.title()
u'doc1 Zope Info'

View file

@ -539,6 +539,15 @@
permission="zope.View"
/>
<zope:adapter
name="listchildren"
for="loops.interfaces.INode
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.browser.node.ListChildren"
permission="zope.View"
/>
<!-- inner HTML views -->
<page

View file

@ -72,10 +72,14 @@ class NodeView(BaseView):
cm.register('portlet_left', 'navigation', title='Navigation',
subMacro=self.template.macros['menu'])
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
cm.register('portlet_right', 'actions', title='Actions',
subMacro=self.template.macros['actions'])
cm.register('portlet_right', 'personal', title='Personal Items',
subMacro=self.template.macros['personal'])
cm.register('portlet_right', 'clipboard', title='Clipboard',
subMacro=self.template.macros['clipboard'])
# this belongs to loops.organize; how to register portlets
# from sub- (other) packages?
# see controller / configurator: use multiple configurators;
# register additional configurators (adapters) from within package.
cm.register('portlet_right', 'worklist', title='Worklist',
subMacro=self.template.macros['worklist'])
@Lazy
def view(self):
@ -316,26 +320,27 @@ class InlineEdit(NodeView):
# special (named) views for nodes
class ListPages(NodeView):
class SpecialNodeView(NodeView):
macroName = None # to be provided by subclass
@Lazy
def macro(self):
return self.template.macros['listpages']
return self.template.macros[self.macroName]
@Lazy
def view(self):
return self
class ListResources(NodeView):
class ListPages(SpecialNodeView):
macroName = 'listpages'
@Lazy
def macro(self):
return self.template.macros['listresources']
class ListResources(SpecialNodeView):
macroName = 'listresources'
@Lazy
def view(self):
return self
class ListChildren(SpecialNodeView):
macroName = 'listchildren'
class ConfigureView(NodeView):

View file

@ -144,6 +144,28 @@
</metal:resources>
<metal:children define-macro="listchildren"
tal:define="target nocall:item/target">
<div class="content-1"
tal:content="structure item/body"
tal:attributes="ondblclick python:
target and target.openEditWindow('configure.html')
or item.openEditWindow();
id string:${view/itemNum}.body;">
Listing
</div><br />
<div tal:attributes="ondblclick python: target.openEditWindow('configure.html')"
tal:define="item nocall:item/target"
tal:condition="nocall:target">
<div tal:repeat="related item/children">
<a href="#"
tal:attributes="href string:${view/url}/.target${related/uniqueId}"
tal:content="related/title">Resource Title</a>
</div>
</div>
</metal:children>
<!-- menu -->
<metal:menu define-macro="menu"
@ -171,13 +193,13 @@
<!-- portlets -->
<metal:actions define-macro="actions">
<div class="menu-2">Add Resource</div>
<metal:actions define-macro="clipboard">
<div class="menu-2">loops Development</div>
</metal:actions>
<metal:actions define-macro="personal">
<div class="menu-2">Clipboard</div>
<metal:actions define-macro="worklist">
<div class="menu-2">Create Resource...</div>
</metal:actions>
@ -193,10 +215,6 @@
</form>
</div>
<!-- dojoType="Editor"
items="formatblock;|;insertunorderedlist;insertorderedlist;|;bold;italic;|;createLink;"
-->
<!-- edit and other links -->

View file

@ -291,9 +291,9 @@ class IndexAttributes(object):
def __init__(self, context):
self.context = context
# obsolete, use TextIndexNG (with indexableContent() method) instead
def text(self):
context = self.context
# TODO: include attributes provided by concept type
return ' '.join((zapi.getName(context), context.title,))
def title(self):

100
query.py
View file

@ -22,9 +22,11 @@ Query management stuff.
$Id$
"""
from zope import schema, component
from zope.interface import Interface, Attribute, implements
from zope.app import traversing
from zope.cachedescriptors.property import Lazy
from zope import schema
from zope.app.catalog.interfaces import ICatalog
from loops.interfaces import IConcept
from loops.common import AdapterBase
@ -41,6 +43,102 @@ class IQuery(Interface):
"""
class BaseQuery(object):
implements(IQuery)
def __init__(self, context):
self.context = context
@Lazy
def catalog(self):
return component.getUtility(ICatalog)
@Lazy
def loopsRoot(self):
return self.context.context.getLoopsRoot()
def queryConcepts(self, title=None, type=None):
if type.endswith('*'):
start = type[:-1]
end = start + '\x7f'
else:
start = end = type
cat = self.catalog
if title:
result = cat.searchResults(loops_type=(start, end), loops_title=title)
else:
result = cat.searchResults(loops_type=(start, end))
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
return result
def queryConceptsWithChildren(self, title=None, type=None):
if title: # there are a few characters that the index doesn't like
title = title.replace('(', ' ').replace(')', ' ')
if not title and (type is None or '*' in type):
return None
result = set()
queue = list(self.queryConcepts(title=title, type=type))
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
class FullQuery(BaseQuery):
def query(self, text=None, type=None, useTitle=True, useFull=False,
conceptTitle=None, conceptType=None):
result = set()
rc = self.queryConceptsWithChildren(title=conceptTitle, type=conceptType)
if not rc and not text and '*' in type: # there should be some sort of selection...
return result
if text or type != 'loops:*': # TODO: this may be highly inefficient!
cat = self.catalog
if useFull and text:
criteria = {'loops_text': text}
r1 = set(cat.searchResults(**criteria))
else:
r1 = set()
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)
if rc is not None:
if result:
result = result.intersection(rc)
else:
result = rc
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
return result
class ConceptQuery(BaseQuery):
""" Find concepts of type `type` whose title starts with `title`.
"""
def query(self, title=None, type=None):
if title and not title.endswith('*'):
title += '*'
return self.queryConcepts(title=title, type=type)
# QueryConcept: concept objects that allow querying the database.
class IQueryConcept(Interface):
""" The schema for the query type.
"""

View file

@ -234,10 +234,14 @@ class IndexAttributes(object):
def __init__(self, context):
self.context = context
# obsolete, use TextIndexNG (with indexableContent() method) instead
def text(self):
context = self.context
return ' '.join((zapi.getName(context), context.title, context.data)).strip()
if not context.contentType.startswith('text'):
return None
data = context.data
# TODO: transform to plain text
#return ' '.join((zapi.getName(context), context.title, data)).strip()
return data
def title(self):
context = self.context
@ -245,6 +249,8 @@ class IndexAttributes(object):
class IndexableResource(object):
""" Used for TextIndexNG.
"""
implements(IIndexableContent)
adapts(IResource)

View file

@ -152,8 +152,8 @@ purposes fairly primitive) catalog and a resource we can search for:
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:
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')
@ -166,11 +166,11 @@ Search via related concepts
>>> 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:
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'}
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text_selected': u'zope'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
@ -179,7 +179,7 @@ Search via related concepts
>>> results[0].context.__name__
u'plone'
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'}
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text_selected': u'zope3'}
>>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results)
@ -187,3 +187,11 @@ Search via related concepts
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:
TODO...

View file

@ -23,9 +23,8 @@ loops.search package.
$Id$
"""
from zope.app import zapi
from zope import interface, component
from zope.app.catalog.interfaces import ICatalog
from zope.app import traversing
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
@ -34,6 +33,7 @@ from zope.i18nmessageid import MessageFactory
from cybertools.ajax import innerHtml
from cybertools.typology.interfaces import ITypeManager
from loops.browser.common import BaseView
from loops.query import ConceptQuery, FullQuery
from loops import util
from loops.util import _
@ -51,10 +51,6 @@ class Search(BaseView):
def macro(self):
return template.macros['search']
@Lazy
def catalog(self):
return component.getUtility(ICatalog)
@property
def rowNum(self):
""" Return the rowNum to be used for identifying the current search
@ -84,19 +80,11 @@ class Search(BaseView):
"""
request = self.request
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
text = request.get('searchString', '').replace('(', ' ').replace(')', ' ')
title = request.get('searchString', '').replace('(', ' ').replace(')', ' ')
type = request.get('searchType') or 'loops:concept:*'
if type.endswith('*'):
start = type[:-1]
end = start + '\x7f'
else:
start = end = type
cat = self.catalog
if text:
result = cat.searchResults(loops_type=(start, end), loops_text=text+'*')
else:
result = cat.searchResults(loops_type=(start, end))
return str(sorted([[`o.title`[2:-1], `zapi.getName(o)`[2:-1]]
result = ConceptQuery(self).query(title=title, type=type)
# simple way to provide JSON format:
return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]]
for o in result])).replace('\\\\x', '\\x')
def submitReplacing(self, targetId, formId, view):
@ -116,79 +104,18 @@ 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:*')
text = request.get('search.2.text')
if not r3 and not text and '*' in type: # there should be some sort of selection...
return result
#if r3 and type != 'loops:*':
# typeName = type.split(':')[-1]
# r3 = set(o for o in r3 if self.isType(o, typeName))
#if text or not '*' in loops:
if text or type != 'loops:*': # TODO: this may be highly inefficient! see above
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
useTitle = request.get('search.2.title')
useFull = request.get('search.2.full')
conceptType = request.get('search.3.type', 'loops:concept:*')
conceptTitle = request.get('search.3.text_selected')
result = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull,
conceptTitle=conceptTitle, conceptType= conceptType)
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 text: # there are a few characters that the index doesn't like
text = text.replace('(', ' ').replace(')', ' ')
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 text:
criteria['loops_title'] = text
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