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:
parent
f8f9ee1fd8
commit
8ee75bbf77
9 changed files with 144 additions and 51 deletions
|
@ -25,12 +25,10 @@ $Id$
|
|||
from zope.app import zapi
|
||||
from zope.app.dublincore.interfaces import IZopeDublinCore
|
||||
from zope.app.form.browser.interfaces import ITerms
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.dottedname.resolve import resolve
|
||||
from zope.formlib import form
|
||||
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.interface import Interface, implements
|
||||
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.typology.interfaces import IType
|
||||
from loops.interfaces import IView
|
||||
from loops import util
|
||||
#from loops import util
|
||||
from loops.util import _
|
||||
|
||||
|
||||
|
@ -64,13 +62,13 @@ class IAddForm(Interface):
|
|||
)
|
||||
|
||||
|
||||
class AddForm(BaseAddForm):
|
||||
class AddForm(form.AddForm):
|
||||
|
||||
form_fields = FormFields(IAddForm)
|
||||
template = NamedTemplate('loops.pageform')
|
||||
|
||||
|
||||
class EditForm(BaseEditForm):
|
||||
class EditForm(form.EditForm):
|
||||
|
||||
template = NamedTemplate('loops.pageform')
|
||||
|
||||
|
@ -181,10 +179,6 @@ class BaseView(GenericView):
|
|||
# this may depend on system and user settings...
|
||||
return True
|
||||
|
||||
#@Lazy
|
||||
#def inlineEditable(self):
|
||||
# return self.inlineEditingActive and canWrite(self.context, 'title')
|
||||
|
||||
inlineEditable = False
|
||||
|
||||
def inlineEdit(self, id):
|
||||
|
|
|
@ -39,6 +39,9 @@ from zope.proxy import removeAllProxies
|
|||
from zope.security import canAccess, canWrite
|
||||
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.browser import configurator
|
||||
from cybertools.browser.view import GenericView
|
||||
|
@ -299,6 +302,7 @@ class InlineEdit(NodeView):
|
|||
def save(self):
|
||||
target = self.virtualTargetObject
|
||||
target.data = self.request.form['editorContent']
|
||||
notify(ObjectModifiedEvent(target, Attributes(IResource, 'data')))
|
||||
|
||||
|
||||
# special (named) views for nodes
|
||||
|
|
16
concept.py
16
concept.py
|
@ -76,13 +76,13 @@ class ResourceRelation(BaseRelation):
|
|||
|
||||
|
||||
# concept
|
||||
|
||||
|
||||
class Concept(Contained, Persistent):
|
||||
|
||||
implements(IConcept, IConceptManagerContained, IRelatable)
|
||||
|
||||
proxyInterface = IConceptView
|
||||
|
||||
|
||||
_title = u''
|
||||
def getTitle(self): return self._title
|
||||
def setTitle(self, title): self._title = title
|
||||
|
@ -132,7 +132,7 @@ class Concept(Contained, Persistent):
|
|||
relationships = [ConceptRelation(None, self, p) for p in predicates]
|
||||
# TODO: sort...
|
||||
return getRelations(first=parent, second=self, relationships=relationships)
|
||||
|
||||
|
||||
def getParents(self, predicates=None):
|
||||
return [r.first for r in self.getParentRelations(predicates)]
|
||||
|
||||
|
@ -158,7 +158,7 @@ class Concept(Contained, Persistent):
|
|||
|
||||
def deassignParent(self, parent, predicates=None):
|
||||
parent.deassignChild(self, predicates)
|
||||
|
||||
|
||||
# resource relations
|
||||
|
||||
def getResourceRelations(self, predicates=None, resource=None):
|
||||
|
@ -184,7 +184,7 @@ class Concept(Contained, Persistent):
|
|||
|
||||
|
||||
# concept manager
|
||||
|
||||
|
||||
class ConceptManager(BTreeContainer):
|
||||
|
||||
implements(IConceptManager, ILoopsContained)
|
||||
|
@ -211,7 +211,7 @@ class ConceptManager(BTreeContainer):
|
|||
|
||||
def getViewManager(self):
|
||||
return self.getLoopsRoot().getViewManager()
|
||||
|
||||
|
||||
|
||||
# adapters and similar components
|
||||
|
||||
|
@ -291,10 +291,12 @@ 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,))
|
||||
|
||||
def title(self):
|
||||
return self.text()
|
||||
context = self.context
|
||||
return ' '.join((zapi.getName(context), context.title,))
|
||||
|
||||
|
|
|
@ -276,12 +276,22 @@
|
|||
<require like_class="zope.app.dublincore.annotatableadapter.ZDCAnnotatableAdapter" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.concept.IndexAttributes" />
|
||||
<adapter factory="loops.resource.IndexAttributes" />
|
||||
<adapter factory="loops.concept.IndexAttributes" trusted="True" />
|
||||
<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" />
|
||||
<class class="loops.resource.IndexableResource">
|
||||
<require permission="zope.View"
|
||||
interface="loops.interfaces.IBaseResourceSchema" />
|
||||
<allow interface="textindexng.interfaces.IIndexableContent" />
|
||||
</class>
|
||||
<class class="textindexng.content.IndexContentCollector">
|
||||
<allow interface="textindexng.interfaces.IIndexContentCollector" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.resource.DocumentReadFileAdapter" />
|
||||
|
@ -350,6 +360,21 @@
|
|||
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=".organize" />
|
||||
<include package=".process" />
|
||||
|
|
|
@ -37,6 +37,9 @@ from zope import schema
|
|||
from persistent import Persistent
|
||||
from cStringIO import StringIO
|
||||
|
||||
from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
|
||||
from zope.event import notify
|
||||
|
||||
from textindexng.interfaces import IIndexableContent
|
||||
from textindexng.content import IndexContentCollector
|
||||
from cybertools.relation.registry import getRelations
|
||||
|
@ -205,6 +208,7 @@ class DocumentWriteFileAdapter(object):
|
|||
|
||||
def write(self, data):
|
||||
self.context.data = unicode(data.replace('\r', ''), 'UTF-8')
|
||||
notify(ObjectModifiedEvent(self.context, Attributes(IDocument, 'data')))
|
||||
|
||||
|
||||
class DocumentReadFileAdapter(object):
|
||||
|
@ -230,6 +234,7 @@ 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()
|
||||
|
|
|
@ -11,6 +11,7 @@ Let's do some basic set up
|
|||
>>> site = placefulSetUp(True)
|
||||
|
||||
>>> from zope import component, interface
|
||||
>>> from zope.interface import implements
|
||||
|
||||
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
|
||||
|
@ -81,3 +82,22 @@ 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")'
|
||||
|
||||
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
|
||||
[]
|
||||
|
|
|
@ -24,6 +24,7 @@ $Id$
|
|||
"""
|
||||
|
||||
from zope import interface, component
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
|
||||
|
@ -75,3 +76,22 @@ class SearchResults(BaseView):
|
|||
def __call__(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
|
||||
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
<metal:search define-macro="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;
|
||||
ondblclick item/openEditWindow">
|
||||
Search
|
||||
</h3>
|
||||
|
||||
<div metal:define-macro="search_form" class="searchForm"
|
||||
tal:define="idPrefix string:${view/itemNum}.search;
|
||||
formId string:$idPrefix.form">
|
||||
<div metal:define-macro="search_form" class="searchForm">
|
||||
<fieldset class="box">
|
||||
<form action="." method="post" id="1.search.form"
|
||||
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">
|
||||
<tal:block repeat="search_selection python: ['type', 'text']">
|
||||
<tal:block repeat="search_param python: ['type', 'text']">
|
||||
<metal:row use-macro="macros/search_row" />
|
||||
</tal:block>
|
||||
<tr>
|
||||
|
@ -24,10 +28,9 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" name="1.search" value="Search" class="submit"
|
||||
tal:attributes="name string:$idPrefix.submit;
|
||||
onclick python:
|
||||
item.submitReplacing('1.results', formId, view)" />
|
||||
<input type="submit" name="button.search" value="Search" class="submit"
|
||||
tal:attributes="onclick python:
|
||||
item.submitReplacing(resultsId, formId, view)" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -35,11 +38,17 @@
|
|||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div metal:define-macro="search_results"
|
||||
id="1.results">
|
||||
<fieldset class="box">
|
||||
Search results
|
||||
<p tal:content="python:request.get('2.search.1.text', 'nothing')" />
|
||||
<div metal:define-macro="search_results" id="1.search.results"
|
||||
tal:attributes="id resultsId | request/search.resultsId">
|
||||
<fieldset class="box"
|
||||
tal:condition="request/search.submitted | 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>
|
||||
</div>
|
||||
|
||||
|
@ -47,18 +56,22 @@
|
|||
</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;
|
||||
idPrefix string:$idPrefix.$rowNum;
|
||||
selection search_selection | item/searchSelection"
|
||||
namePrefix string:search.$rowNum;
|
||||
param search_param | item/searchParam"
|
||||
tal:attributes="id string:$idPrefix.row">
|
||||
<td style="width: 30px">
|
||||
<input type="hidden" name="paramtype" value="type"
|
||||
tal:attributes="name string:$namePrefix.paramtype;
|
||||
value param" />
|
||||
<input type="button" value="−"
|
||||
title="Remove search parameter"
|
||||
tal:condition="python: selection not in ['type', 'text']" />
|
||||
tal:condition="python: param not in ['type', 'text']" />
|
||||
</td>
|
||||
<td>
|
||||
<div metal:use-macro="macros/?selection" />
|
||||
<div metal:use-macro="macros/?param" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
@ -69,7 +82,7 @@
|
|||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">Type:</label>
|
||||
<select name="text"
|
||||
tal:attributes="name string:$idPrefix.text;
|
||||
tal:attributes="name string:$namePrefix.text;
|
||||
id string:$idPrefix.text;">
|
||||
<option value=".loops/concepts/topic">Topic</option>
|
||||
<option value="loops.resource.Document">Document</option>
|
||||
|
@ -84,21 +97,21 @@
|
|||
<div>
|
||||
<h3>Search text</h3>
|
||||
<input type="checkbox" checked
|
||||
name="title" id="title"
|
||||
tal:attributes="name string:$idPrefix.title;
|
||||
name="title" id="title" value="yes"
|
||||
tal:attributes="name string:$namePrefix.title;
|
||||
id string:$idPrefix.title;" />
|
||||
<label for="title"
|
||||
tal:attributes="for string:$idPrefix.title">Title</label>
|
||||
<input type="checkbox"
|
||||
name="full" id="full"
|
||||
tal:attributes="name string:$idPrefix.full;
|
||||
name="full" id="full" value="yes"
|
||||
tal:attributes="name string:$namePrefix.full;
|
||||
id string:$idPrefix.full;" />
|
||||
<label for="full"
|
||||
tal:attributes="for string:$idPrefix.full">Full text</label>
|
||||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">Search words:</label>
|
||||
<input type="text" name="text"
|
||||
tal:attributes="name string:$idPrefix.text;
|
||||
tal:attributes="name string:$namePrefix.text;
|
||||
id string:$idPrefix.text;" />
|
||||
<input type="button" value="+"
|
||||
title="Add search word" />
|
||||
|
|
22
target.py
22
target.py
|
@ -31,8 +31,10 @@ from zope.interface import implements
|
|||
from zope import schema
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
||||
from loops.interfaces import IResource
|
||||
from loops.interfaces import IDocument, IMediaAsset
|
||||
from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
|
||||
from zope.event import notify
|
||||
|
||||
from loops.interfaces import ILoopsObject, IResource, IDocument, IMediaAsset
|
||||
from loops.interfaces import IDocumentView, IMediaAssetView
|
||||
from loops.interfaces import IView
|
||||
from loops.interfaces import IConcept, IConceptView
|
||||
|
@ -52,10 +54,13 @@ class TargetProxy(object):
|
|||
@Lazy
|
||||
def target(self):
|
||||
return self.context.target
|
||||
|
||||
|
||||
def getTitle(self):
|
||||
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)
|
||||
|
||||
|
||||
|
@ -65,7 +70,9 @@ class ConceptProxy(TargetProxy):
|
|||
adapts(IConceptView)
|
||||
|
||||
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)
|
||||
|
||||
def getChildren(self, predicates=None):
|
||||
|
@ -81,6 +88,7 @@ class ConceptProxy(TargetProxy):
|
|||
class ResourceProxy(TargetProxy):
|
||||
|
||||
def setContentType(self, contentType):
|
||||
notify(ObjectModifiedEvent(self.target, Attributes(IResource, 'contentType')))
|
||||
self.target.contentType = contentType
|
||||
def getContentType(self): return self.target.contentType
|
||||
contentType = property(getContentType, setContentType)
|
||||
|
@ -91,7 +99,9 @@ class DocumentProxy(ResourceProxy):
|
|||
implements(IDocument)
|
||||
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
|
||||
data = property(getData, setData)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue