From 92f6a06e0104627fb67ceefa6cc174543633ece9 Mon Sep 17 00:00:00 2001 From: helmutm Date: Sun, 17 Sep 2006 11:01:16 +0000 Subject: [PATCH] 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 --- README.txt | 4 +- browser/configure.zcml | 9 ++++ browser/node.py | 31 +++++++------ browser/node_macros.pt | 34 ++++++++++---- concept.py | 2 +- query.py | 100 ++++++++++++++++++++++++++++++++++++++++- resource.py | 10 ++++- search/README.txt | 22 ++++++--- search/browser.py | 99 ++++++---------------------------------- 9 files changed, 191 insertions(+), 120 deletions(-) diff --git a/README.txt b/README.txt index 00fa563..1bb2288 100755 --- a/README.txt +++ b/README.txt @@ -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' diff --git a/browser/configure.zcml b/browser/configure.zcml index 85909a1..e1fef37 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -539,6 +539,15 @@ permission="zope.View" /> + + + +
+ Listing +

+
+ +
+
+ + - - + + - - + + @@ -193,10 +215,6 @@ - - diff --git a/concept.py b/concept.py index 7594388..9e47451 100644 --- a/concept.py +++ b/concept.py @@ -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): diff --git a/query.py b/query.py index 83f690a..fed9ee0 100644 --- a/query.py +++ b/query.py @@ -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. """ diff --git a/resource.py b/resource.py index 301dd86..e8b6dbd 100644 --- a/resource.py +++ b/resource.py @@ -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) diff --git a/search/README.txt b/search/README.txt index c26e2fe..877f655 100755 --- a/search/README.txt +++ b/search/README.txt @@ -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... + + diff --git a/search/browser.py b/search/browser.py index 9ecea95..2f01439 100644 --- a/search/browser.py +++ b/search/browser.py @@ -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 -