创建一个 portal_user_catalog 并使用它(克隆)

Create a portal_user_catalog and have it used (Plone)

我正在为我的 Plone 站点创建一个分支(已经很长时间没有分支了)。该站点有一个用于用户配置文件的特殊目录对象(一种特殊的基于原型的对象类型),称为 portal_user_catalog:

$ bin/instance debug
>>> portal = app.Plone
>>> print [d for d in portal.objectMap() if d['meta_type'] == 'Plone Catalog Tool']
[{'meta_type': 'Plone Catalog Tool', 'id': 'portal_catalog'},
 {'meta_type': 'Plone Catalog Tool', 'id': 'portal_user_catalog'}]

这看起来很合理,因为用户配置文件没有 "normal" 对象的大部分索引,但有一小部分自己的索引。

因为我找不到如何从头开始创建这个对象的方法,所以我将它从旧站点(如 portal_user_catalog.zexp)导出并导入到新站点。这似乎可行,但我无法将对象添加到导入的目录中,甚至无法显式调用 catalog_object 方法。相反,用户配置文件被添加到标准 portal_catalog.

现在我在我的产品中发现了一个模块,似乎可以达到目的 (Products/myproduct/exportimport/catalog.py):

"""Catalog tool setup handlers.

$Id: catalog.py 77004 2007-06-24 08:57:54Z yuppie $
"""

from Products.GenericSetup.utils import exportObjects
from Products.GenericSetup.utils import importObjects

from Products.CMFCore.utils import getToolByName

from zope.component import queryMultiAdapter
from Products.GenericSetup.interfaces import IBody

def importCatalogTool(context):
    """Import catalog tool.
    """
    site = context.getSite()
    obj = getToolByName(site, 'portal_user_catalog')
    parent_path=''

    if obj and not obj():
        importer = queryMultiAdapter((obj, context), IBody)
        path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
        __traceback_info__ = path
        print [importer]
        if importer:
            print importer.name
            if importer.name:
                path = '%s%s' % (parent_path, 'usercatalog')
                print path

            filename = '%s%s' % (path, importer.suffix)
            print filename
            body = context.readDataFile(filename)
            if body is not None:
                importer.filename = filename # for error reporting
                importer.body = body

        if getattr(obj, 'objectValues', False):
            for sub in obj.objectValues():
                importObjects(sub, path+'/', context)

def exportCatalogTool(context):
    """Export catalog tool.
    """
    site = context.getSite()
    obj = getToolByName(site, 'portal_user_catalog', None)
    if tool is None:
        logger = context.getLogger('catalog')
        logger.info('Nothing to export.')
        return

    parent_path=''

    exporter = queryMultiAdapter((obj, context), IBody)
    path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
    if exporter:
        if exporter.name:
            path = '%s%s' % (parent_path, 'usercatalog')
        filename = '%s%s' % (path, exporter.suffix)
        body = exporter.body
        if body is not None:
            context.writeDataFile(filename, body, exporter.mime_type)

    if getattr(obj, 'objectValues', False):
        for sub in obj.objectValues():
            exportObjects(sub, path+'/', context)

我尝试使用它,但我不知道它应该如何完成; 我不能称之为 TTW(我应该尝试发布方法吗?!)。 我在 debug 会话中尝试过:

$ bin/instance debug
>>> portal = app.Plone
>>> from Products.myproduct.exportimport.catalog import exportCatalogTool
>>> exportCatalogTool(portal)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File ".../Products/myproduct/exportimport/catalog.py", line 58, in exportCatalogTool
    site = context.getSite()
AttributeError: getSite

所以,如果这是要走的路,看来我需要一个 "real" 上下文。

更新: 为了获得这个上下文,我尝试了 External Method

# -*- coding: utf-8 -*-
from Products.myproduct.exportimport.catalog import exportCatalogTool
from pdb import set_trace

def p(dt, dd):
    print '%-16s%s' % (dt+':', dd)

def main(self):
    """
    Export the portal_user_catalog
    """
    g = globals()
    print '#' * 79
    for a in ('__package__', '__module__'):
        if a in g:
            p(a, g[a])
    p('self', self)
    set_trace()
    exportCatalogTool(self)

然而,当我调用它时,我得到了与 main 函数的参数相同的 <PloneSite at /Plone> 对象,它没有 getSite 属性。也许我的站点没有正确调用此类外部方法?

或者我是否需要在 configure.zcml 中以某种方式提及此模块,但如何?我在我的目录树(尤其是 Products/myproduct/profiles 下)搜索了 exportimport、模块名称和其他几个字符串,但我找不到任何东西;也许曾经有过整合,但被打破了......

那么我该如何使这个 portal_user_catalog 工作呢? 谢谢!

更新:另一个debug会话表明问题的根源是一些transaction问题:

>>> portal = app.Plone
>>> puc = portal.portal_user_catalog
>>> puc._catalog()
[]
>>> profiles_folder = portal.some_folder_with_profiles
>>> for o in profiles_folder.objectValues():
...     puc.catalog_object(o)
...
>>> puc._catalog()
[<Products.ZCatalog.Catalog.mybrains object at 0x69ff8d8>, ...]

portal_user_catalog 的人口不会持续存在;在 debug 会话结束并开始 fg 之后,大脑就消失了。

看来问题确实与交易有关。 我有

import transaction
...
class Browser(BrowserView):
    ...
    def processNewUser(self):
        ....
        transaction.commit()

以前,但显然这还不够好(and/or 可能做得不正确)。

现在我使用 transaction.begin() 显式开始事务,使用 transaction.savepoint() 保存中间结果,使用 transaction.abort() 显式中止事务以防出现错误 (try / except), 且最后恰好有一个transaction.commit(), 则成功。似乎一切正常。

当然,Plone仍然没有考虑到这个非标准目录;当我 "clear and rebuild" 它时,它是空的。但对于我的应用程序来说,它工作得很好。