基于持久化和会话的Zope3搜索表单
项目描述
此包提供了一种实现基于z3c.indexer构建Zope3搜索表单的解决方案。持久性搜索条件可以存储在会话中或作为预定义的筛选查询对象在应用程序中。
详细文档
README
此包提供了一个持久性搜索查询实现。此搜索查询实现为一个筛选对象,可以使用搜索条件来构建搜索查询。此包还提供了一些基于z3c.form的管理视图,使我们能够管理搜索筛选器和其搜索条件。让我们定义一个具有索引的网站,使我们能够构建搜索筛选器。
注意,此包依赖于新的z3c.indexer包,它提供了一种模块化索引概念。但是,您也可以使用此包与zope.app.catalog包一起使用。您只需要构建自己的搜索标准。
启动简单测试设置
设置一些助手
>>> import zope.component >>> from zope.site import folder >>> from zope.site import LocalSiteManager >>> from z3c.indexer.interfaces import IIndex
设置一个网站
>>> class SiteStub(folder.Folder): ... """Sample site.""" >>> site = SiteStub()>>> root['site'] = site >>> sm = LocalSiteManager(site) >>> site.setSiteManager(sm)
并将网站设置为当前网站。这通常通过遍历到网站来完成。
>>> from zope.app.component import hooks >>> hooks.setSite(site)
设置IIntIds工具
>>> from zope.intid import IntIds >>> from zope.intid.interfaces import IIntIds >>> intids = IntIds() >>> sm['default']['intids'] = intids >>> sm.registerUtility(intids, IIntIds)
文本索引
设置文本索引
>>> from z3c.indexer.index import TextIndex >>> textIndex = TextIndex() >>> sm['default']['textIndex'] = textIndex >>> sm.registerUtility(textIndex, IIndex, name='textIndex')
字段索引
设置字段索引
>>> from z3c.indexer.index import FieldIndex >>> fieldIndex = FieldIndex() >>> sm['default']['fieldIndex'] = fieldIndex >>> sm.registerUtility(fieldIndex, IIndex, name='fieldIndex')
值索引
设置值索引
>>> from z3c.indexer.index import ValueIndex >>> valueIndex = ValueIndex() >>> sm['default']['valueIndex'] = valueIndex >>> sm.registerUtility(valueIndex, IIndex, name='valueIndex')
设置索引
设置集合索引
>>> from z3c.indexer.index import SetIndex >>> setIndex = SetIndex() >>> sm['default']['setIndex'] = setIndex >>> sm.registerUtility(setIndex, IIndex, name='setIndex')
DemoContent
定义内容对象
>>> import persistent >>> import zope.interface >>> from zope.app.container import contained >>> from zope.schema.fieldproperty import FieldProperty>>> class IDemoContent(zope.interface.Interface): ... """Demo content.""" ... title = zope.schema.TextLine( ... title=u'Title', ... default=u'') ... ... body = zope.schema.TextLine( ... title=u'Body', ... default=u'') ... ... field = zope.schema.TextLine( ... title=u'a field', ... default=u'') ... ... value = zope.schema.TextLine( ... title=u'A value', ... default=u'') ... ... iterable = zope.schema.Tuple( ... title=u'A sequence of values', ... default=())>>> class DemoContent(persistent.Persistent, contained.Contained): ... """Demo content.""" ... zope.interface.implements(IDemoContent) ... ... title = FieldProperty(IDemoContent['title']) ... body = FieldProperty(IDemoContent['body']) ... field = FieldProperty(IDemoContent['field']) ... value = FieldProperty(IDemoContent['value']) ... iterable = FieldProperty(IDemoContent['iterable']) ... ... def __init__(self, title=u''): ... self.title = title ... ... def __repr__(self): ... return '<%s %r>' % (self.__class__.__name__, self.title)
创建并添加内容对象到网站
>>> demo = DemoContent(u'Title') >>> demo.body = u'Body text' >>> demo.field = u'Field' >>> demo.value = u'Value' >>> demo.iterable = (1, 2, 'Iterable') >>> site['demo'] = demo
用于 __setitem__ 的 zope 事件订阅者应调用我们的内容对象的 IIntIds 注册方法。但我们没有设置相关的订阅者,所以在这里进行设置
>>> uid = intids.register(demo)
索引器
为我们的内容对象设置一个索引器适配器。
>>> from z3c.indexer.indexer import MultiIndexer >>> class DemoIndexer(MultiIndexer): ... zope.component.adapts(IDemoContent) ... ... def doIndex(self): ... ... # index context in valueIndex ... valueIndex = self.getIndex('textIndex') ... txt = '%s %s' % (self.context.title, self.context.body) ... valueIndex.doIndex(self.oid, txt) ... ... # index context in fieldIndex ... fieldIndex = self.getIndex('fieldIndex') ... fieldIndex.doIndex(self.oid, self.context.field) ... ... # index context in setIndex ... setIndex = self.getIndex('setIndex') ... setIndex.doIndex(self.oid, self.context.iterable) ... ... # index context in valueIndex ... valueIndex = self.getIndex('valueIndex') ... valueIndex.doIndex(self.oid, self.context.value)
将索引器适配器注册为命名适配器
>>> zope.component.provideAdapter(DemoIndexer, name='DemoIndexer')
索引
在开始索引之前,我们检查索引
>>> textIndex.documentCount() 0>>> fieldIndex.documentCount() 0>>> setIndex.documentCount() 0>>> valueIndex.documentCount() 0
现在我们可以索引我们的演示对象
>>> from z3c.indexer.indexer import index >>> index(demo)
并检查我们的索引
>>> textIndex.documentCount() 1>>> fieldIndex.documentCount() 1>>> setIndex.documentCount() 1>>> valueIndex.documentCount() 1
搜索过滤器
现在我们准备好开始我们的搜索过滤器实现。下面的搜索过滤器默认返回无结果,因为它将 NoTerm 定义为 getDefaultQuery。如果您有一组大量数据并且希望在未选择任何条件时从空查询开始,这将很有用。
>>> from z3c.searcher import interfaces >>> from z3c.searcher.filter import EmptyTerm >>> from z3c.searcher.filter import SearchFilter>>> class IContentSearchFilter(interfaces.ISearchFilter): ... """Search filter for content objects.""">>> class ContentSearchFilter(SearchFilter): ... """Content search filter.""" ... ... zope.interface.implements(IContentSearchFilter) ... ... def getDefaultQuery(self): ... return EmptyTerm()
搜索条件
我们为我们的演示内容定义了一个条件。此文本搜索条件使用上面注册为 textIndex 的文本索引
>>> from z3c.searcher import criterium >>> class TextCriterium(criterium.TextCriterium): ... """Full text search criterium for ``textIndex`` index.""" ... ... indexOrName = 'textIndex'
这样的条件可以在我们的索引中进行搜索。让我们从一个空搜索查询开始
>>> from z3c.indexer.search import SearchQuery >>> searchQuery = SearchQuery()
您可以看到搜索查询返回一个空结果。
>>> len(searchQuery.searchResults()) 0
展示
现在我们可以创建一个条件实例并为其提供一个值
>>> sampleCriterium = TextCriterium() >>> sampleCriterium.value = u'Bod*'
现在条件能够在其相关索引中搜索给定的值,在给定的(空)搜索查询中。这个空查询仅用作可链查询对象。每个结果根据其连接符 And、OR 或 Not 从链中添加或删除
>>> searchQuery = sampleCriterium.search(searchQuery)
现在您可以看到我们的条件从文本索引中找到了一个结果
>>> len(searchQuery.searchResults()) 1>>> content = list(searchQuery.searchResults())[0] >>> content.body u'Body text'
搜索条件工厂
上面的测试向您展示了条件如何搜索索引。但这还不是全部。我们的概念提供了一种搜索过滤器,可以管理多个搜索条件。一个条件是一个过滤器的适配器。这意味着我们需要创建一个适配器工厂,并将其注册为过滤器的适配器。现在让我们创建这个条件适配器工厂
>>> textCriteriumFactory = criterium.factory(TextCriterium, 'fullText')
此搜索条件工厂类实现了 ISearchCriteriumFactory
>>> interfaces.ISearchCriteriumFactory.implementedBy(textCriteriumFactory) True
并为我们内容搜索过滤器注册了这个适配器
>>> zope.component.provideAdapter(textCriteriumFactory, ... (IContentSearchFilter,), name='fullText')
展示
现在您可以看到我们的内容搜索过滤器知道有关搜索条件工厂的信息
>>> contentSearchFilter = ContentSearchFilter() >>> contentSearchFilter.criteriumFactories [(u'fullText', <z3c.searcher.criterium.TextCriteriumFactory object at ...>)]
由于搜索条件工厂是我们搜索过滤器的适配器,因此工厂可以适配我们的 contentSearchFilter
>>> textCriteriumFactory = textCriteriumFactory(contentSearchFilter) >>> textCriteriumFactory <z3c.searcher.criterium.TextCriteriumFactory object at ...>
现在我们可以调用工厂,并返回我们的搜索条件实例
>>> textCriterium = textCriteriumFactory() >>> textCriterium <TextCriterium object at ...>
我们的搜索条件提供了 ISearchCriterium
>>> interfaces.ISearchCriterium.providedBy(textCriterium) True
搜索示例
现在我们准备好在我们的过滤器构造中进行搜索。首先让我们创建一个纯内容搜索过滤器
>>> sampleFilter = ContentSearchFilter()
然后让我们通过其工厂名称添加一个条件
>>> sampleCriterium = sampleFilter.createCriterium('fullText')
现在我们可以为条件设置一个值
>>> sampleCriterium.value = u'Title'
并将条件添加到我们的过滤器中
>>> sampleFilter.addCriterium(sampleCriterium)
就是这样,现在我们的过滤器可以生成查询
>>> sampleQuery = sampleFilter.generateQuery()
并且示例搜索查询可以返回结果
>>> len(sampleQuery.searchResults()) 1>>> content = list(sampleQuery.searchResults())[0] >>> content.title u'Title'
搜索会话
在我们展示如何在 z3c.form 组件中使用条件和过滤器之前,我们将展示搜索会话是如何工作的。让我们注册并创建一个搜索会话
>>> from z3c.searcher import session >>> zope.component.provideAdapter(session.SearchSession)
现在我们可以创建一个测试请求,并获取会话作为请求的适配器
>>> import z3c.form.testing >>> request = z3c.form.testing.TestRequest() >>> searchSession = interfaces.ISearchSession(request) >>> searchSession <z3c.searcher.session.SearchSession object at ...>
搜索会话提供了一个 API 来存储和管理过滤器
>>> searchSession.addFilter('foo', sampleFilter)
我们可以通过名称从搜索会话中获取这样的过滤器。
>>> searchSession.getFilter('foo') <ContentSearchFilter object at ...>
或者我们可以获取在此会话中存储的所有搜索过滤器
>>> searchSession.getFilters() [<ContentSearchFilter object at ...>]
我们还可以通过名称删除一个过滤器
>>> searchSession.removeFilter('foo') >>> searchSession.getFilters() []
在搜索会话方法中还有一个名为 key 的另一个参数。此参数可以用作命名空间。如果您需要只为一个特定对象实例支持特定过滤器,则可以使用唯一标识该对象的键作为区分器。
>>> myFilter = ContentSearchFilter() >>> searchSession.addFilter('foo', myFilter, key='myKey')
只有在使用正确的 键 时,这些过滤器才可用
>>> searchSession.getFilter('foo') is None True>>> searchSession.getFilter('foo', key='myKey') <ContentSearchFilter object at ...>>>> searchSession.getFilters() []
现在让我们清理搜索会话并删除由键存储的过滤器
>>> searchSession.getFilters('myKey') [<ContentSearchFilter object at ...>]>>> searchSession.removeFilter('foo', 'myKey') >>> searchSession.getFilters('myKey') []
标准形式
现在我们将向您展示表单部分的工作方式。每个标准都可以在表单中自行渲染。我们提供了一个 CriteriumForm 类来执行此操作。让我们创建并渲染这样一个标准表单
>>> import z3c.form.testing >>> from z3c.searcher import form >>> criteriumRow = form.CriteriumForm(textCriterium, request) >>> criteriumRow <z3c.searcher.form.CriteriumForm object at ...>
我们还需要设置一个前缀,这通常通过调用 setupCriteriumRows 由搜索表单来完成。通常,标准位于搜索过滤器中。我们只需要一个标准 __name__
>>> textCriterium.__name__ = u'1' >>> criteriumRow.prefix = 'form.criterium.%s' % str(textCriterium.__name__) >>> criteriumRow.prefix 'form.criterium.1'
在我们可以渲染表单之前,我们需要注册模板
>>> from zope.configuration import xmlconfig >>> import zope.component >>> import zope.viewlet >>> import zope.app.component >>> import zope.app.security >>> import zope.app.publisher.browser >>> import z3c.template >>> import z3c.macro >>> import z3c.formui >>> xmlconfig.XMLConfig('meta.zcml', zope.component)() >>> xmlconfig.XMLConfig('meta.zcml', zope.viewlet)() >>> xmlconfig.XMLConfig('meta.zcml', zope.app.component)() >>> xmlconfig.XMLConfig('meta.zcml', zope.app.security)() >>> xmlconfig.XMLConfig('meta.zcml', zope.app.publisher.browser)() >>> xmlconfig.XMLConfig('meta.zcml', z3c.macro)() >>> xmlconfig.XMLConfig('meta.zcml', z3c.template)() >>> xmlconfig.XMLConfig('div-form.zcml', z3c.formui)() >>> context = xmlconfig.file('meta.zcml', z3c.template) >>> context = xmlconfig.string(""" ... <configure ... xmlns:z3c="http://namespaces.zope.org/z3c"> ... <configure package="z3c.searcher"> ... <z3c:template ... template="filter.pt" ... for=".form.FilterForm" ... /> ... <z3c:template ... template="criterium.pt" ... for=".form.CriteriumForm" ... /> ... <z3c:template ... template="search.pt" ... for=".form.SearchForm" ... /> ... <z3c:template ... template="table.pt" ... for=".table.SearchTable" ... /> ... </configure> ... </configure> ... """, context=context)
我们还需要一些 z3c.form 的小部件
>>> import z3c.form.testing >>> z3c.form.testing.setupFormDefaults()
现在我们可以渲染标准表单了
>>> criteriumRow.update() >>> print criteriumRow.render() <tr> <td style="padding-right:5px;"> <span>Text</span> </td> <td style="padding-right:5px;"> <b>matches</b> </td> <td style="padding-right:5px;"> <input id="form-criterium-1-widgets-value" name="form.criterium.1.widgets.value" class="text-widget required textline-field" value="" type="text" /> <span class="option"> <label for="form-criterium-1-widgets-connectorName-0"> <input id="form-criterium-1-widgets-connectorName-0" name="form.criterium.1.widgets.connectorName:list" class="radio-widget required choice-field" value="OR" checked="checked" type="radio" /> <span class="label">or</span> </label> </span> <span class="option"> <label for="form-criterium-1-widgets-connectorName-1"> <input id="form-criterium-1-widgets-connectorName-1" name="form.criterium.1.widgets.connectorName:list" class="radio-widget required choice-field" value="AND" type="radio" /> <span class="label">and</span> </label> </span> <span class="option"> <label for="form-criterium-1-widgets-connectorName-2"> <input id="form-criterium-1-widgets-connectorName-2" name="form.criterium.1.widgets.connectorName:list" class="radio-widget required choice-field" value="NOT" type="radio" /> <span class="label">not</span> </label> </span> <input name="form.criterium.1.widgets.connectorName-empty-marker" type="hidden" value="1" /> </td> <td style="padding-right:5px;"> <input id="form-criterium-1-buttons-remove" name="form.criterium.1.buttons.remove" class="submit-widget button-field" value="Remove" type="submit" /> </td> </tr>
过滤器表单
还有一个过滤器表单,它可以表示 SearchFilter。此表单包含 CriteriumForm 部分。请注意,我们使用一个虚拟上下文,因为您在何处渲染此表单并不重要,因为表单将从会话中获取过滤器。
>>> filterForm = form.FilterForm(object(), request) >>> filterForm <z3c.searcher.form.FilterForm object at ...>
但在我们可以使用表单之前,我们需要将我们的搜索过滤器类设置为工厂。因为只有这个搜索过滤器知道我们的标准
>>> filterForm.filterFactory = ContentSearchFilter
现在我们可以渲染我们的过滤器表单了
>>> filterForm.update() >>> print filterForm.render() <fieldset> <legend>Filter</legend> <div> <label for="filterformnewCriterium"> New Criterium </label> <select name="filterformnewCriterium" size="1"> <option value="fullText">fullText</option> </select> <input id="filterform-buttons-add" name="filterform.buttons.add" class="submit-widget button-field" value="Add" type="submit" /> </div> <div> <input id="filterform-buttons-search" name="filterform.buttons.search" class="submit-widget button-field" value="Search" type="submit" /> <input id="filterform-buttons-clear" name="filterform.buttons.clear" class="submit-widget button-field" value="Clear" type="submit" /> </div> </fieldset>
搜索表单
还有一个搜索表单,允许您简单地定义一个搜索页面。此搜索表单使用标准和过滤器表单,并允许您简单地创建一个搜索页面。让我们定义一个自定义搜索页面
>>> class ContentSearchForm(form.SearchForm): ... ... filterFactory = ContentSearchFilter
在我们可以使用表单之前,我们的请求需要提供表单 UI 层
>>> from zope.interface import alsoProvides >>> from z3c.formui.interfaces import IDivFormLayer >>> alsoProvides(request, IDivFormLayer)
这就是编写简单搜索表单所需的所有内容。此表单使用它自己的内容搜索过滤器,当然还有为此过滤器配置的标准。
>>> searchForm = ContentSearchForm(object(), request) >>> searchForm.update() >>> print searchForm.render() <form action="http://127.0.0.1" method="post" enctype="multipart/form-data" class="edit-form" name="form" id="form"> <div class="viewspace"> <div> <fieldset> <legend>Filter</legend> <div> <label for="filterformnewCriterium"> New Criterium </label> <select name="filterformnewCriterium" size="1"> <option value="fullText">fullText</option> </select> <input id="filterform-buttons-add" name="filterform.buttons.add" class="submit-widget button-field" value="Add" type="submit" /> </div> <div> <input id="filterform-buttons-search" name="filterform.buttons.search" class="submit-widget button-field" value="Search" type="submit" /> <input id="filterform-buttons-clear" name="filterform.buttons.clear" class="submit-widget button-field" value="Clear" type="submit" /> </div> </fieldset> </div> <div> </div> </div> <div> <div class="buttons"> </div> </div> </form>
搜索表
还有一个搜索表。此搜索表使用标准和过滤器表单,并允许您简单地创建一个搜索页面,该页面将以表格形式列出结果。让我们定义一个自定义搜索表
>>> from z3c.searcher import table >>> class ContentSearchTable(table.SearchTable): ... ... filterFactory = ContentSearchFilter
在我们可以使用表单之前,我们的请求需要提供表单 UI 层
>>> from zope.interface import alsoProvides >>> from z3c.formui.interfaces import IDivFormLayer >>> alsoProvides(request, IDivFormLayer)
这就是编写简单搜索表单所需的所有内容。此表单使用它自己的内容搜索过滤器,当然还有为此过滤器配置的标准。
>>> searchTable = ContentSearchTable(object(), request) >>> searchTable.update() >>> print searchTable.render() <form action="http://127.0.0.1" method="post" enctype="multipart/form-data" class="edit-form" name="formTable" id="formTable"> <div class="viewspace"> <div> <div class="filterForm"> <fieldset> <legend>Filter</legend> <div> <label for="filterformnewCriterium"> New Criterium </label> <select name="filterformnewCriterium" size="1"> <option value="fullText">fullText</option> </select> <input id="filterform-buttons-add" name="filterform.buttons.add" class="submit-widget button-field" value="Add" type="submit" /> </div> <div> <input id="filterform-buttons-search" name="filterform.buttons.search" class="submit-widget button-field" value="Search" type="submit" /> <input id="filterform-buttons-clear" name="filterform.buttons.clear" class="submit-widget button-field" value="Clear" type="submit" /> </div> </fieldset> </div> <div> </div> </div> </div> <div> <div class="buttons"> </div> </div> </form>
变更
0.6.0 (2009-09-20)
错误修复:标准没有正确地定位在 SearchFilter 中。标准的 __name__ 总是一个空的 Unicode 值。
错误修复:当使用多个标准时,标准过滤器不起作用。现在的搜索表单将定位过滤器表单,并为标准表单设置一个单独的前缀。如果您使用自定义标准表单,您可能需要审查您的自定义实现。特别是审查 setupCriteriumRows 方法中的前缀设置。
调整测试,反映最新更改,并修复在 z3c.form 中更改的元素属性顺序
0.5.2 (2009-03-10)
清理依赖关系。将软件包的邮件列表地址从已退役的地址更改为 zope-dev at zope.org。
0.5.1 (2009-02-22)
修复:在 z3c.searcher.table 中添加了缺失的 zope.interface 导入,并为 SearchTable 添加了测试
0.5.0 (2009-02-22)
添加初始生成配置文件
初始发布