跳转到主要内容

钩子,用于简化在Zope 2/CMF应用程序中管理自定义索引值

项目描述

简介

此包提供原语,帮助将ZCatalog索引操作委托给适配器。它本身并不做什么,但可以被需要允许单独索引值由对象本身提供,而不是由单独的适配器提供的目录实现使用。

变更日志

2.0.1 (2024-01-22)

内部

  • 更新配置文件。[plone 开发者] (6e36bcc4, 7723aeaf)

2.0.0 (2023-04-19)

破坏性变更

  • 放弃对python 2.7的支持。[gforcada] (#1)

内部

  • 更新配置文件。[plone 开发者] (5cc689e5)

1.0.7 (2020-04-20)

错误修复

  • 小包装更新。[#1]

1.0.6 (2019-04-29)

错误修复

  • 修复:Python 2上的doctests检查不正确。[maurits] (#7)

1.0.5 (2018-09-26)

修复

  • 修复 https://github.com/plone/Products.CMFPlone/issues/2469:“子对象是父对象的索引属性”。只允许直接属性和获取的PythonScripts,但不允许获取的属性。索引器和PythonScripts能够显式处理这种情况,因为它们获得的是获取包装的对象。[jensens]

  • 修复测试以在Python 3中工作 [pbauer]

1.0.4 (2016-02-25)

修复

  • 将已弃用的zope.testing.doctestunit导入替换为stdlib中的doctest模块。[thet]

  • 根据Plone风格指南重新格式化。[thet]

1.0.3 (2015-05-05)

  • 添加缺失的依赖项Products.ZCatalog。[gforcada]

1.0.2 (2013-01-13)

  • 更改了@indexer装饰器以维护包装函数的信息(__doc__,__module__,__name__等)。[dokai]

1.0.1 (2012-12-14)

1.0 - 2010-07-18

  • 修复了变更日志中的reSt标记。[hannosch]

  • 更新许可为仅GPL版本2。[hannosch]

1.0rc2 - 2009-04-05

  • 添加了_getWrappedObject()方法来获取底层对象。注意,这意味着你不能有一个具有此名称的索引/元数据列。[optilude]

  • 更正了IZCatalog导入位置以指向接口模块。[hannosch]

1.0rc1 - 2009-03-26

  • 更新了接口以匹配CMF trunk上类似功能的开发。这意味着索引器现在是(对象,目录)上的多适配器,关键字参数(包括隐式的“portal”参数)已消失。[optilude]

1.0a1 - 2009-03-05

  • 初始发布

编写索引器

索引器是一个命名适配器,它适配对象的类型,并在目录尝试使用该名称索引属性时提供要索引的值。

例如,假设我们有两种类型,页面和新闻条目

>>> from zope.interface import Interface
>>> from zope.interface import implementer
>>> from zope import schema

>>> class IPage(Interface):
...     text = schema.Text(title=u"Body text")

>>> @implementer(IPage)
... class Page(object):
...     def __init__(self, text):
...         self.text = text

>>> class INewsItem(Interface):
...     summary = schema.TextLine(title=u"Short summary")
...     story = schema.Text(title=u"Body text")
...     audience = schema.TextLine(title=u"Audience")

>>> @implementer(INewsItem)
... class NewsItem(object):
...     def __init__(self, summary, story, audience):
...         self.summary = summary
...         self.story = story
...         self.audience = audience

现在,假设我们的目录有一个索引“description”,对于页面,它应该包含正文的前10个字符,而对于新闻条目,它应该包含“摘要”字段的全部内容。此外,还有一个索引“audience”,它应该包含新闻条目相应字段的值,全部大写。对于页面,它应该不执行任何操作。

我们可以像这样为所有这些编写索引器

>>> from plone.indexer import indexer

>>> @indexer(IPage)
... def page_description(object):
...     return object.text[:10]

>>> @indexer(INewsItem)
... def newsitem_description(object):
...     return object.summary

>>> @indexer(INewsItem)
... def newsitem_audience(object):
...     return object.audience.upper()

这些需要注册为命名适配器,其中名称对应于索引名称。在ZCML中,可能是这样的

<adapter name="description" factory=".indexers.page_description" />
<adapter name="description" factory=".indexers.newsitem_description" />
<adapter name="audience" factory=".indexers.newsitem_audience" />

我们可以省略“for”属性,因为我们将其传递给了@indexer装饰器,并且我们可以省略“provides”属性,因为装饰器返回的东西实际上是一个提供所需IIndexer接口的类。

为了后续测试的目的,我们将直接注册这些

>>> from zope.component import provideAdapter
>>> provideAdapter(page_description, name='description')
>>> provideAdapter(newsitem_description, name='description')
>>> provideAdapter(newsitem_audience, name='audience')

测试您的索引器(或直接调用它们)

如果您正在为索引器编写测试(您应该这样做!),那么您应该了解以下内容

当 @indexer 装饰器返回时,它将您的函数转换为类型为 DelegatingIndexerFactory 的实例。这是一个适配器工厂,可以创建一个 DelegatingIndexer,它反过来会在需要执行索引操作时调用您的函数。

这意味着您不能直接调用您的函数来测试索引器。相反,您需要实例化适配器,然后使用门户根作为第一个参数调用委托索引器。例如

>>> test_page = Page(text=u"My page with some text")
>>> page_description(test_page)()
'My page wi'

这在大多数情况下就足够了。请注意,实际上还有一个第二个参数,即目录,默认值为 None。如果您需要编写作用于目录的索引器,您需要注册一个传统适配器,如下一节所述。

注册索引器的其他方法

最终,索引器只是一个从可索引对象(例如上面的 INewsItem 或 IPage)和目录(通常在 CMF 应用程序中为 portal_catalog)到 IIndexer 的命名多适配器,其中名称是目录中索引属性的名字。因此,您可以将您的索引器注册为更传统的适配器

>>> from plone.indexer.interfaces import IIndexer
>>> from Products.ZCatalog.interfaces import IZCatalog
>>> from zope.component import adapter
>>> from zope.interface import implementer

>>> @implementer(IIndexer)
... @adapter(IPage, IZCatalog)
... class LengthIndexer(object):
...     """Index the length of the body text
...     """
...     def __init__(self, context, catalog):
...         self.context = context
...         self.catalog = catalog
...
...     def __call__(self):
...         return len(self.context.text)

我们通常只使用 IZCatalog 来进行目录适配,以应用于任何目录。但是,如果您想为不同类型的目录提供不同的索引器,本测试的后面有一个例子。

您可以使用以下方式使用 ZCML 进行注册

<adapter factory=".indexers.LengthIndexer" name="length" />

或者在测试中

>>> provideAdapter(LengthIndexer, name="length")

如果您只对如何编写索引器感兴趣,您可能可以在这里停止。如果您想了解更多关于它们的工作原理以及它们如何连接到框架的信息,请继续阅读。

将索引器连接到框架

以下是基于 Plone 中的 ZCatalog.catalog_object() 重写的一个模拟实现。我们将为此进行测试。我们不会忙于完整的 ZCatalog 接口,只有 catalog_object(),我们还将模拟一些内容。这真的是仅用于说明用途,以显示预期的使用模式。

在 CMF 2.2 中,在 Products.CMFCore.interfaces 中定义了一个 IIndexableObject 标记接口。我们在这个包中有一个兼容性别名,用于与 CMF 2.1 一起使用。

>>> from OFS.interfaces import IItem
>>> from plone.indexer.interfaces import IIndexableObject
>>> from Products.ZCatalog.interfaces import IZCatalog
>>> from zope.component import queryMultiAdapter

>>> @implementer(IZCatalog, IItem)
... class FauxCatalog(object):
...
...     def catalog_object(self, object, uid, idxs=[]):
...         """Pretend to index 'object' under the key 'uid'. We'll
...         print the results of the indexing operation to the screen .
...         """
...
...         if not IIndexableObject.providedBy(object):
...             wrapper = queryMultiAdapter((object, self,), IIndexableObject)
...             if wrapper is not None:
...                 object = wrapper
...
...         # Perform the actual indexing of attributes in the idxs list
...         for idx in idxs:
...             try:
...                 indexed_value = getattr(object, idx)
...                 if callable(indexed_value):
...                     indexed_value = indexed_value()
...                 print("{0} = {1}".format(idx, indexed_value))
...             except (AttributeError, TypeError,):
...                 pass

这里的重要之处在于

  • 我们尝试获取要索引的对象的 IIndexableObject。这只是获取此接口实现(我们稍后将注册一个)并允许一些粗粒度覆盖的一种方式。

  • 编目涉及在可索引对象包装器上查找与索引名称匹配的属性(在真实的 ZCatalog 中,这实际上是解耦合的,但我们不要过于激动)。如果它们是可调用的,它们应该被调用。这只是模仿 ZCatalog 的实现所做的。

此包提供了一个 IIndexableObject 适配器的实现,它知道如何委托给 IIndexer。现在让我们将其注册为默认的 IIndexableObject 包装器适配器,这样上面的代码就可以找到它

>>> from plone.indexer.interfaces import IIndexableObject
>>> from plone.indexer.wrapper import IndexableObjectWrapper
>>> provideAdapter(factory=IndexableObjectWrapper, adapts=(Interface, IZCatalog,), provides=IIndexableObject)

看到它的实际应用

现在来进行测试。首先,我们需要一个模拟的目录

>>> catalog = FauxCatalog()

最后,让我们创建一些要索引的对象

>>> page = Page(u"The page body text here")
>>> news = NewsItem(u"News summary", u"News body text", u"Audience")

首先,让我们演示我们的索引器是如何工作的,以及它们只适用于它们已注册的类型

>>> catalog.catalog_object(page, 'p1', idxs=['description', 'audience', 'length'])
description = The page b
length = 23

>>> catalog.catalog_object(news, 'n1', idxs=['description', 'audience', 'length'])
description = News summary
audience = AUDIENCE

我们的自定义可索引对象包装器能够查找如果 portal_workflow 工具可用的工作流变量。为了测试目的,我们将创建一个假的最小工作流工具并将其存入假目录中,以便可以通过 getToolByName 找到。在现实生活中,当然可以像平常一样获取它

>>> @implementer(IItem)
... class FauxWorkflowTool(object):
...     def getCatalogVariablesFor(self, object):
...         return dict(review_state='published', audience='Somebody')
>>> catalog.portal_workflow = FauxWorkflowTool()

如果我们现在索引 'review_state',它将从工作流变量中获取。但是,自定义索引器仍然会覆盖工作流变量

>>> catalog.catalog_object(news, 'n1', idxs=['description', 'audience', 'review_state'])
description = News summary
audience = AUDIENCE
review_state = published

最后,如果没有找到适配器,我们将回退到对象上的 getattr()

>>> catalog.catalog_object(page, 'p3', idxs=['description', 'text'])
description = The page b
text = The page body text here

根据目录类型自定义索引器

可以为不同类型的目录提供自定义索引器。为了测试这一点,让我们创建一个二级目录并使用标记接口对其进行标记

>>> from zope.interface import Interface
>>> class IAlternateCatalog(Interface):
...     pass
>>> from zope.interface import alsoProvides
>>> catalog2 = FauxCatalog()
>>> alsoProvides(catalog2, IAlternateCatalog)

假设我们不想在这里将新闻条目受众名称转换为大写。我们可以为这个目录提供一个自定义索引器

>>> @indexer(INewsItem, IAlternateCatalog)
... def alternate_newsitem_audience(object):
...     return object.audience.lower()
>>> provideAdapter(alternate_newsitem_audience, name='audience')

这不会影响第一个目录

>>> catalog.catalog_object(news, 'n1', idxs=['description', 'audience', 'length'])
description = News summary
audience = AUDIENCE

然而,第二个目录会得到小写的受众

>>> catalog2.catalog_object(news, 'n1', idxs=['description', 'audience', 'length'])
description = News summary
audience = audience

包装器提供的接口

可索引对象包装器有一个特别的功能:包装器的实例将提供与包装对象实例相同的接口。例如

>>> from plone.indexer.interfaces import IIndexableObject
>>> from plone.indexer.interfaces import IIndexableObjectWrapper

>>> wrapper = IndexableObjectWrapper(page, catalog)
>>> IIndexableObjectWrapper.providedBy(wrapper)
True
>>> IIndexableObject.providedBy(wrapper)
True
>>> IPage.providedBy(wrapper)
True
>>> INewsItem.providedBy(wrapper)
False

>>> wrapper = IndexableObjectWrapper(news, catalog)
>>> IIndexableObjectWrapper.providedBy(wrapper)
True
>>> IPage.providedBy(wrapper)
False
>>> INewsItem.providedBy(wrapper)
True

拆箱

可以从包装器中获取包装对象

>>> wrapper = IndexableObjectWrapper(page, catalog)
>>> wrapper._getWrappedObject() is page
True

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分发

plone.indexer-2.0.1.tar.gz (17.0 kB 查看哈希值)

上传时间

构建分发

plone.indexer-2.0.1-py3-none-any.whl (15.2 kB 查看哈希值)

上传时间 Python 3

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面