=================
dolmen.app.search
=================
`dolmen.app.search` provides components to search objects inside a
Grok application.
Searchers
=========
`dolmen.app.search` introduces a new component : the ISearcher. This
component is dedicated in searching and returning a set of objects,
using a search term.
The provided API is::
>>> from dolmen.app.search.interfaces import ISearchAPI
>>> interfaceDescription(ISearchAPI)
ISearcher: A component dedicated to search
ICatalogSearcher: A specialized ISearcher querying a catalog
Test environment
----------------
In order to test our searchers, we need an operationnal application
with at least one index.
We prepare out imports::
>>> import grok
>>> from grok import index
>>> from zope.index.text.interfaces import ISearchableText
>>> from zope.interface import Interface
>>> from zope.schema import TextLine
We create a model that will be cataloged::
>>> class IRipper(Interface):
... """A serial killer.
... """
... searchabletext = TextLine(title=u"Name of the ripper")
>>> class Ripper(grok.Model):
... grok.implements(IRipper)
...
... def __init__(self, searchabletext):
... self.searchabletext = searchabletext
We create a Grok application, that will be out site manager::
>>> class Backstreet(grok.Container, grok.Application):
... """A dark alley.
... """
>>> grok.testing.grok_component('application', Backstreet)
True
We define an index that will be the base for our search::
>>> class RipperIndexes(grok.Indexes):
... grok.site(Backstreet)
... grok.context(IRipper)
... searchabletext = index.Text()
>>> grok.testing.grok_component('indexes', RipperIndexes)
True
Now we persist our application and set it as the default site::
>>> from zope.site.hooks import setSite
>>> app = Backstreet()
>>> root = getRootFolder()
>>> root['berner_street'] = app
>>> setSite(app)
The Grok application has created the catalog::
>>> from zope.component import getUtility
>>> from zope.catalog.interfaces import ICatalog
>>> catalog = getUtility(ICatalog)
>>> catalog
<zope.catalog.catalog.Catalog object at ...>
Our index is there::
>>> catalog['searchabletext']
<zope.catalog.text.TextIndex object at ...>
>>> catalog['searchabletext'].documentCount()
0
Indexing
--------
The catalog is ready. Now, if we create a content and persist it, the
cataloging mechanism will do the work for us::
>>> jack = Ripper(u"Jack the knife")
>>> grok.notify(grok.ObjectCreatedEvent(jack))
>>> app['jack'] = jack
>>> catalog['searchabletext'].documentCount()
1
The searchabletext is set. That is what is cataloged in our site catalog::
>>> jack.searchabletext
u'Jack the knife'
Searching
---------
An ISearcher can be used as an utility. `dolmen.app.search` provides a
default implementation of an ICatalogSearcher. This component is used
to query the site catalog::
>>> from dolmen.app.search import ICatalogSearcher
>>> searcher = getUtility(ICatalogSearcher, "searcher.sitecatalog")
>>> searcher
<dolmen.app.search.searchers.SiteCatalogSearcher object at ...>
>>> searcher.catalog == catalog
True
The search method of the ICatalogSearcher takes a search term and the
name of the index. By default, it uses the `searchabletext` index::
>>> result = searcher.search(term="Jack")
>>> result
<dolmen.app.search.sets.PermissionAwareResultSet instance at ...>
>>> list(result)
[<dolmen.app.search.tests.Ripper object at ...>]
If we provide a non existing index name, an error is raised::
>>> result = searcher.search(term="Jack", index="non-existing")
Traceback (most recent call last):
...
ValueError: Index 'non-existing' does not exist
Wildcard
~~~~~~~~
A wildcard can be given, when searching a text index::
>>> result = searcher.search("Ja")
>>> list(result)
[]
>>> result = searcher.search("Ja*")
>>> list(result)
[<dolmen.app.search.tests.Ripper object at ...>]
Permission
~~~~~~~~~~
By default, our searcher checks the `zope.View` permission on the
objects of the result set. We can provide another permission
explicitly::
>>> result = searcher.search("knife", permission="i-do-not-exist")
>>> list(result)
[]
A `grok.Permision` class can be used instead of a string::
>>> from dolmen.app.security import CanViewContent
>>> result = searcher.search("knife", permission=CanViewContent)
>>> list(result)
[<dolmen.app.search.tests.Ripper object at ...>]
If permission is set to None, nothing is checked::
>>> result = searcher.search("knife", permission=None)
>>> list(result)
[<dolmen.app.search.tests.Ripper object at ...>]
View and viewlet
================
`dolmen.app.search` comes with two browser components. A search form
viewlet and a result page.
Search viewlet
--------------
A search viewlet is registered to display a search form input. In
order to test the output of the viewlet, we need a view::
>>> class GasLamp(grok.View):
... """A view where the air's cold and damp
... """
... grok.context(IRipper)
>>> grok.testing.grok_component('view', GasLamp)
True
We get the view to render the viewlet::
>>> from zope.publisher.browser import TestRequest
>>> from zope.component import getMultiAdapter
>>> request = TestRequest()
>>> view = getMultiAdapter((jack, request), name="gaslamp")
The `Search` viewlet is registered for the `dolmen.app.layout.Top`
manager. We construct this manager::
>>> from dolmen.app.layout import Top
>>> manager = Top(jack, request, view)
We can now call, update and render the Search viewlet::
>>> from dolmen.app.search.browser import Search
>>> search = Search(jack, request, view, manager)
>>> search.update()
>>> print search.render()
<form id="searchbox" method="post"
action="http://127.0.0.1/berner_street/search.result">
<input type="text" autocomplete="off" name="search_term"
id="search-widget" value="" />
<input type="submit" name="search_button"
id="search-button" title="Search" alt="Search"
value="Search" />
</form>
Result page
-----------
The viewlet posts the data to the `search.result` view. This view
fetches the `search_term` from the request, queries the
ICatalogSearcher and displays the results::
>>> request = TestRequest(form = {'search_term': 'jack'})
>>> results = getMultiAdapter((jack, request), name="search.result")
>>> results
<dolmen.app.search.browser.Results object at ...>
>>> results.update()
>>> print results.content()
<div class="search-result">
<div class="search-header">
<h1>Search</h1>
<h3>Found 1 results for jack</h3>
</div>
<dl class="search-results content-listing">
<dt>
<a href="http://127.0.0.1/berner_street/jack"
title="jack">
<span>jack</span>
</a>
</dt>
</dl>
</div>
Changelog
=========
0.4 (2010-11-05)
----------------
- The package now works for Grok 1.2.
- The icon is now using the "icon" and no longer "contenttype_icon" view.
- The dependencies have been reviewed and slimmed down.
0.3.1 (2010-07-24)
------------------
- 0.3 release was broken because of missing locales directory.
0.3 (2010-07-24)
----------------
- Added French translation.
0.2.2 (2009-01-08)
------------------
- Declare all dependencies and some cleanup.
- Require dolmen.content.View permission for Search viewlet.
0.2.1 (2009-12-01)
------------------
- Removed all imports from zope.app.* for intid and catalog. Using now
the new packages. This makes dolmen.app.search compatible with
grok1.1a2.
0.2 (2009-12-01)
----------------
- Modified the imports to make it compatible with grok 1.1 (ztk 1.0)
0.1 (2009-11-08)
----------------
- Initial release