first working version of fulltext and title search

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1317 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-08-19 15:42:29 +00:00
parent f8f9ee1fd8
commit 8ee75bbf77
9 changed files with 144 additions and 51 deletions

View file

@ -25,12 +25,10 @@ $Id$
from zope.app import zapi from zope.app import zapi
from zope.app.dublincore.interfaces import IZopeDublinCore from zope.app.dublincore.interfaces import IZopeDublinCore
from zope.app.form.browser.interfaces import ITerms from zope.app.form.browser.interfaces import ITerms
from zope.app.intid.interfaces import IIntIds
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.formlib import form
from zope.formlib.form import FormFields from zope.formlib.form import FormFields
from zope.formlib.form import EditForm as BaseEditForm
from zope.formlib.form import AddForm as BaseAddForm
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import Interface, implements from zope.interface import Interface, implements
from zope.app.publisher.browser import applySkin from zope.app.publisher.browser import applySkin
@ -44,7 +42,7 @@ from cybertools.browser.view import GenericView
from cybertools.relation.interfaces import IRelationRegistry from cybertools.relation.interfaces import IRelationRegistry
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.interfaces import IView from loops.interfaces import IView
from loops import util #from loops import util
from loops.util import _ from loops.util import _
@ -64,13 +62,13 @@ class IAddForm(Interface):
) )
class AddForm(BaseAddForm): class AddForm(form.AddForm):
form_fields = FormFields(IAddForm) form_fields = FormFields(IAddForm)
template = NamedTemplate('loops.pageform') template = NamedTemplate('loops.pageform')
class EditForm(BaseEditForm): class EditForm(form.EditForm):
template = NamedTemplate('loops.pageform') template = NamedTemplate('loops.pageform')
@ -181,10 +179,6 @@ class BaseView(GenericView):
# this may depend on system and user settings... # this may depend on system and user settings...
return True return True
#@Lazy
#def inlineEditable(self):
# return self.inlineEditingActive and canWrite(self.context, 'title')
inlineEditable = False inlineEditable = False
def inlineEdit(self, id): def inlineEdit(self, id):

View file

@ -39,6 +39,9 @@ from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
from zope.event import notify
from cybertools.ajax import innerHtml from cybertools.ajax import innerHtml
from cybertools.browser import configurator from cybertools.browser import configurator
from cybertools.browser.view import GenericView from cybertools.browser.view import GenericView
@ -299,6 +302,7 @@ class InlineEdit(NodeView):
def save(self): def save(self):
target = self.virtualTargetObject target = self.virtualTargetObject
target.data = self.request.form['editorContent'] target.data = self.request.form['editorContent']
notify(ObjectModifiedEvent(target, Attributes(IResource, 'data')))
# special (named) views for nodes # special (named) views for nodes

View file

@ -291,10 +291,12 @@ class IndexAttributes(object):
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
# obsolete, use TextIndexNG (with indexableContent() method) instead
def text(self): def text(self):
context = self.context context = self.context
return ' '.join((zapi.getName(context), context.title,)) return ' '.join((zapi.getName(context), context.title,))
def title(self): def title(self):
return self.text() context = self.context
return ' '.join((zapi.getName(context), context.title,))

View file

@ -276,12 +276,22 @@
<require like_class="zope.app.dublincore.annotatableadapter.ZDCAnnotatableAdapter" /> <require like_class="zope.app.dublincore.annotatableadapter.ZDCAnnotatableAdapter" />
</class> </class>
<adapter factory="loops.concept.IndexAttributes" /> <adapter factory="loops.concept.IndexAttributes" trusted="True" />
<adapter factory="loops.resource.IndexAttributes" /> <class class="loops.concept.IndexAttributes">
<allow interface="loops.interfaces.IIndexAttributes" />
</class>
<adapter factory="loops.resource.IndexAttributes" trusted="True" />
<class class="loops.resource.IndexAttributes">
<allow interface="loops.interfaces.IIndexAttributes" />
</class>
<adapter factory="loops.resource.IndexableResource" trusted="True" /> <adapter factory="loops.resource.IndexableResource" trusted="True" />
<class class="loops.resource.IndexableResource"> <class class="loops.resource.IndexableResource">
<require permission="zope.View" <allow interface="textindexng.interfaces.IIndexableContent" />
interface="loops.interfaces.IBaseResourceSchema" /> </class>
<class class="textindexng.content.IndexContentCollector">
<allow interface="textindexng.interfaces.IIndexContentCollector" />
</class> </class>
<adapter factory="loops.resource.DocumentReadFileAdapter" /> <adapter factory="loops.resource.DocumentReadFileAdapter" />
@ -350,6 +360,21 @@
name="loops.PredicateSource" name="loops.PredicateSource"
/> />
<!-- textindexng converters -->
<utility
provides="textindexng.interfaces.IConverter"
component="textindexng.converters.null.NullConverter"
name="text/structured"
/>
<utility
provides="textindexng.interfaces.IConverter"
component="textindexng.converters.null.NullConverter"
name="text/restructured"
/>
<include package=".knowledge" /> <include package=".knowledge" />
<include package=".organize" /> <include package=".organize" />
<include package=".process" /> <include package=".process" />

View file

@ -37,6 +37,9 @@ from zope import schema
from persistent import Persistent from persistent import Persistent
from cStringIO import StringIO from cStringIO import StringIO
from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
from zope.event import notify
from textindexng.interfaces import IIndexableContent from textindexng.interfaces import IIndexableContent
from textindexng.content import IndexContentCollector from textindexng.content import IndexContentCollector
from cybertools.relation.registry import getRelations from cybertools.relation.registry import getRelations
@ -205,6 +208,7 @@ class DocumentWriteFileAdapter(object):
def write(self, data): def write(self, data):
self.context.data = unicode(data.replace('\r', ''), 'UTF-8') self.context.data = unicode(data.replace('\r', ''), 'UTF-8')
notify(ObjectModifiedEvent(self.context, Attributes(IDocument, 'data')))
class DocumentReadFileAdapter(object): class DocumentReadFileAdapter(object):
@ -230,6 +234,7 @@ class IndexAttributes(object):
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
# obsolete, use TextIndexNG (with indexableContent() method) instead
def text(self): def text(self):
context = self.context context = self.context
return ' '.join((zapi.getName(context), context.title, context.data)).strip() return ' '.join((zapi.getName(context), context.title, context.data)).strip()

View file

@ -11,6 +11,7 @@ Let's do some basic set up
>>> site = placefulSetUp(True) >>> site = placefulSetUp(True)
>>> from zope import component, interface >>> from zope import component, interface
>>> from zope.interface import implements
and setup a simple loops site with a concept manager and some concepts 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 (with all the type machinery, what in real life is done via standard
@ -81,3 +82,22 @@ 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")'
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.
>>> from zope.app.catalog.interfaces import ICatalog
>>> class DummyCat(object):
... implements(ICatalog)
... def searchResults(self, **criteria):
... return []
>>> component.provideUtility(DummyCat())
>>> resultsView.results
[]

View file

@ -24,6 +24,7 @@ $Id$
""" """
from zope import interface, component from zope import interface, component
from zope.app.catalog.interfaces import ICatalog
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
@ -75,3 +76,22 @@ class SearchResults(BaseView):
def __call__(self): def __call__(self):
return innerHtml(self) return innerHtml(self)
@Lazy
def results(self):
request = self.request
text = request.get('search.2.text')
if not text:
return set()
useTitle = request.get('search.2.title')
useFull = request.get('search.2.full')
r1 = r2 = set()
cat = component.getUtility(ICatalog)
if useFull:
criteria = {'loops_resource_textng': {'query': text},}
r1 = set(cat.searchResults(**criteria))
if useTitle:
criteria = {'loops_title': text,}
r2 = set(cat.searchResults(**criteria))
result = [BaseView(r, request) for r in r1.union(r2)]
return result

View file

@ -1,19 +1,23 @@
<metal:search define-macro="search"> <metal:search define-macro="search">
<div id="search" <div id="search"
tal:define="macros item/template/macros"> tal:define="macros item/template/macros;
idPrefix string:${view/itemNum}.search;
formId string:$idPrefix.form;
resultsId string:$idPrefix.results">
<h3 tal:attributes="class string:content-$level; <h3 tal:attributes="class string:content-$level;
ondblclick item/openEditWindow"> ondblclick item/openEditWindow">
Search Search
</h3> </h3>
<div metal:define-macro="search_form" class="searchForm" <div metal:define-macro="search_form" class="searchForm">
tal:define="idPrefix string:${view/itemNum}.search;
formId string:$idPrefix.form">
<fieldset class="box"> <fieldset class="box">
<form action="." method="post" id="1.search.form" <form action="." method="post" id="1.search.form"
tal:attributes="id formId"> 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"> <table cellpadding="3">
<tal:block repeat="search_selection python: ['type', 'text']"> <tal:block repeat="search_param python: ['type', 'text']">
<metal:row use-macro="macros/search_row" /> <metal:row use-macro="macros/search_row" />
</tal:block> </tal:block>
<tr> <tr>
@ -24,10 +28,9 @@
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<input type="submit" name="1.search" value="Search" class="submit" <input type="submit" name="button.search" value="Search" class="submit"
tal:attributes="name string:$idPrefix.submit; tal:attributes="onclick python:
onclick python: item.submitReplacing(resultsId, formId, view)" />
item.submitReplacing('1.results', formId, view)" />
</td> </td>
</tr> </tr>
</table> </table>
@ -35,11 +38,17 @@
</fieldset> </fieldset>
</div> </div>
<div metal:define-macro="search_results" <div metal:define-macro="search_results" id="1.search.results"
id="1.results"> tal:attributes="id resultsId | request/search.resultsId">
<fieldset class="box"> <fieldset class="box"
Search results tal:condition="request/search.submitted | nothing">
<p tal:content="python:request.get('2.search.1.text', 'nothing')" /> <legend>Search results</legend>
<div tal:repeat="row view/results">
<div>
<a tal:attributes="href string:${view/url}/.target${row/uniqueId}"
tal:content="row/title" />
</div>
</div>
</fieldset> </fieldset>
</div> </div>
@ -47,18 +56,22 @@
</metal:search> </metal:search>
<tr metal:define-macro="search_row" id="search.row.1.1" <tr metal:define-macro="search_row" id="1.1.row"
tal:define="rowNum item/rowNum; tal:define="rowNum item/rowNum;
idPrefix string:$idPrefix.$rowNum; idPrefix string:$idPrefix.$rowNum;
selection search_selection | item/searchSelection" namePrefix string:search.$rowNum;
param search_param | item/searchParam"
tal:attributes="id string:$idPrefix.row"> tal:attributes="id string:$idPrefix.row">
<td style="width: 30px"> <td style="width: 30px">
<input type="hidden" name="paramtype" value="type"
tal:attributes="name string:$namePrefix.paramtype;
value param" />
<input type="button" value="&minus;" <input type="button" value="&minus;"
title="Remove search parameter" title="Remove search parameter"
tal:condition="python: selection not in ['type', 'text']" />&nbsp; tal:condition="python: param not in ['type', 'text']" />&nbsp;
</td> </td>
<td> <td>
<div metal:use-macro="macros/?selection" /> <div metal:use-macro="macros/?param" />
</td> </td>
</tr> </tr>
@ -69,7 +82,7 @@
<label for="text" <label for="text"
tal:attributes="for string:$idPrefix.text">Type:</label> tal:attributes="for string:$idPrefix.text">Type:</label>
<select name="text" <select name="text"
tal:attributes="name string:$idPrefix.text; tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;"> id string:$idPrefix.text;">
<option value=".loops/concepts/topic">Topic</option> <option value=".loops/concepts/topic">Topic</option>
<option value="loops.resource.Document">Document</option> <option value="loops.resource.Document">Document</option>
@ -84,21 +97,21 @@
<div> <div>
<h3>Search text</h3> <h3>Search text</h3>
<input type="checkbox" checked <input type="checkbox" checked
name="title" id="title" name="title" id="title" value="yes"
tal:attributes="name string:$idPrefix.title; tal:attributes="name string:$namePrefix.title;
id string:$idPrefix.title;" /> id string:$idPrefix.title;" />
<label for="title" <label for="title"
tal:attributes="for string:$idPrefix.title">Title</label> tal:attributes="for string:$idPrefix.title">Title</label>
<input type="checkbox" <input type="checkbox"
name="full" id="full" name="full" id="full" value="yes"
tal:attributes="name string:$idPrefix.full; tal:attributes="name string:$namePrefix.full;
id string:$idPrefix.full;" /> id string:$idPrefix.full;" />
<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 words:</label>
<input type="text" name="text" <input type="text" name="text"
tal:attributes="name string:$idPrefix.text; tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text;" /> id string:$idPrefix.text;" />
<input type="button" value="+" <input type="button" value="+"
title="Add search word" />&nbsp; title="Add search word" />&nbsp;

View file

@ -31,8 +31,10 @@ from zope.interface import implements
from zope import schema from zope import schema
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from loops.interfaces import IResource from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
from loops.interfaces import IDocument, IMediaAsset from zope.event import notify
from loops.interfaces import ILoopsObject, IResource, IDocument, IMediaAsset
from loops.interfaces import IDocumentView, IMediaAssetView from loops.interfaces import IDocumentView, IMediaAssetView
from loops.interfaces import IView from loops.interfaces import IView
from loops.interfaces import IConcept, IConceptView from loops.interfaces import IConcept, IConceptView
@ -55,7 +57,10 @@ class TargetProxy(object):
def getTitle(self): def getTitle(self):
return self.target.title return self.target.title
def setTitle(self, title): self.target.title = title def setTitle(self, title):
self.target.title = title
notify(ObjectModifiedEvent(self.target,
Attributes(ILoopsObject, 'title')))
title = property(getTitle, setTitle) title = property(getTitle, setTitle)
@ -65,7 +70,9 @@ class ConceptProxy(TargetProxy):
adapts(IConceptView) adapts(IConceptView)
def getConceptType(self): return self.target.conceptType def getConceptType(self): return self.target.conceptType
def setConceptType(self, conceptType): self.target.conceptType = conceptType def setConceptType(self, conceptType):
self.target.conceptType = conceptType
notify(ObjectModifiedEvent(self.target, Attributes(IConcept, 'conceptType')))
conceptType = property(getConceptType, setConceptType) conceptType = property(getConceptType, setConceptType)
def getChildren(self, predicates=None): def getChildren(self, predicates=None):
@ -81,6 +88,7 @@ class ConceptProxy(TargetProxy):
class ResourceProxy(TargetProxy): class ResourceProxy(TargetProxy):
def setContentType(self, contentType): def setContentType(self, contentType):
notify(ObjectModifiedEvent(self.target, Attributes(IResource, 'contentType')))
self.target.contentType = contentType self.target.contentType = contentType
def getContentType(self): return self.target.contentType def getContentType(self): return self.target.contentType
contentType = property(getContentType, setContentType) contentType = property(getContentType, setContentType)
@ -91,7 +99,9 @@ class DocumentProxy(ResourceProxy):
implements(IDocument) implements(IDocument)
adapts(IDocumentView) adapts(IDocumentView)
def setData(self, data): self.target.data = data def setData(self, data):
self.target.data = data
notify(ObjectModifiedEvent(self.target, Attributes(IDocument, 'data')))
def getData(self): return self.target.data def getData(self): return self.target.data
data = property(getData, setData) data = property(getData, setData)