4

我想在删除其中的项目后重定向到容器的父级。为此,我尝试订阅zope.lifecycleevent's IObjectRemovedEvent

@grok.subscribe(ISite, IObjectRemovedEvent)
def redirect_to_trial_on_delete(obj, event):
    request = getattr(obj, 'REQUEST', None)
    if request:
        trial_url = obj.aq_parent.aq_parent.absolute_url()
        request.response.redirect(trial_url)

删除是通过单击触发的,container/id/delete_confirmation但是这会触发比我预期的更多的事件。我的订阅函数被调用了两次:一次是当我点击链接时,另一次是当我确认删除时。更令人困惑的是,如果我取消删除,它也会被调用。我希望只有当一个对象被从容器中移除时才会引发该事件。

所有三种情况下,事件对象都是相同的,oldName、oldParent 等具有相同的属性值。

如何区分请求删除项目、取消请求和实际删除项目?

更新:所以似乎调用了初始事件,因为从容器中删除了对象以检查链接完整性,此时有回滚。

4

4 回答 4

3

一位同事想出了一个可行的解决方案:

import transaction

def redirect_to_trial(trans, obj=None, parent=None):
    if obj.id not in parent:
        request = getattr(obj, 'REQUEST', None)
        if request:
            trial_url = obj.__parent__.__parent__.absolute_url()
            request.response.redirect(trial_url)

@grok.subscribe(ISite, IObjectRemovedEvent)
def on_site_delete(obj, event):
    kwargs = dict(
        obj = obj,
        parent = event.oldParent,
    )
    transaction.get().addAfterCommitHook(redirect_to_trial, kws=kwargs)

这会在提交之后检查以确保在执行重定向之前实际已删除对象。

不过,我们将不胜感激确认这是否是一种合适的方法。

于 2012-06-27T04:33:01.110 回答
1

这是另一种可能性,同样来自同一个天才同事:

from zope.interface import implements
from transaction.interfaces import ISavepointDataManager
from transaction._transaction import AbortSavepoint
import transaction

class RedirectDataManager(object):

    implements(ISavepointDataManager)

    def __init__(self, request, url):
        self.request = request
        self.url = url
        # Use the default thread transaction manager.
        self.transaction_manager = transaction.manager

    def tpc_begin(self, transaction):
        pass

    def tpc_finish(self, transaction):
        self.request.response.redirect(self.url)

    def tpc_abort(self, transaction):
        self.request.response.redirect(self.url)

    def commit(self, transaction):
        pass

    def abort(self, transaction):
        pass

    def tpc_vote(self, transaction):
        pass

    def sortKey(self):
        return id(self)

    def savepoint(self):
        """
        This is just here to make it possible to enter a savepoint with this manager active.
        """
        return AbortSavepoint(self, transaction.get())

def redirect_to_trial(obj, event):
    request = getattr(obj, 'REQUEST', None)
    if request:
        trial_url = obj.__parent__.__parent__.absolute_url()
        transaction.get().join(RedirectDataManager(request, trial_url))

我现在使用 zcml 进行订阅,以便更轻松地将其绑定到多种内容类型:

<subscriber
    zcml:condition="installed zope.lifecycleevent"
    for=".schema.ISite zope.lifecycleevent.IObjectRemovedEvent"
    handler=".base.redirect_to_trial"
/>

这是我最终采用的解决方案,因为我发现它比手动检查更明确地了解正在发生的事情,以确定我捕获的事件是否是我真正想要的事件。

于 2012-06-27T05:17:03.613 回答
1

您可以自定义操作,而不是使用事件处理程序delete_confirmation;这些甚至可以通过网络进行更改,并且可以根据类型进行定制。该delete_confirmation脚本是一个CMF 表单控制器脚本,有几个选项可以改变它的行为。

目前,动作定义如下:

[actions]
action.success=redirect_to:python:object.aq_inner.aq_parent.absolute_url()
action.confirm=traverse_to:string:delete_confirmation_page

例如,您可以通过定义来添加特定于类型的操作action.success.TypeName

要通过网络执行此操作,请访问 ZMI 并找到该portal_form_controller工具,然后单击Actions选项卡:

突出显示“操作”选项卡的表单控制器概览屏幕

正如您在此屏幕截图中所见,此处还提供有关该工具的文档。

在操作选项卡上有一个添加新操作的表单:

添加新的操作覆盖表单,预填充示例

如您所见,上下文类型是一个包含所有现有类型注册的下拉列表,以便更轻松地指定特定于类型的操作。我已经复制了常规操作(redirect_to由表达式指定的操作,python:并添加了一个额外.aq_parent的选项来选择容器父级。

.addFormAction您还可以使用工具上的方法添加这样的操作:

fctool = getToolByName(context, 'portal_form_controller')
fctool.addFormAction('delete_confirmation', 'success', 'Event', None,
     'redirect_to',
     'python:object.aq_inner.aq_parent.aq_parent.absolute_url()')

最后但同样重要的是,您可以cmfformcontroller.xml在 GenericSetup 配置文件的文件中指定此类自定义操作;这是基于上述操作的示例:

<?xml version="1.0" ?>
<cmfformcontroller>
  <action
      object_id="delete_confirmation" 
      status="success"
      context_type="Event"
      action_type="redirect_to"
      action_arg="python:object.aq_inner.aq_parent.aq_parent.absolute_url()"
      />
</cmfformcontroller>

这种格式是 Plone 中记录不足的内容之一;我从GS 导入和导出代码的 CMFFormController 源代码中得到了这个。

于 2012-06-27T11:45:56.000 回答
0

我也面临着我认为必须是一个常见用例的情况,其中本地 Plone 对象正在代理远程对象。删除 Plone 对象后,但仅在实际删除时,我想删除远程对象。

对我来说, addAfterCommitHook() 并没有避免任何问题,所以我采用了自定义 IDataManager 方法,它为类似的用例提供了一个很好的通用解决方案......

from transaction.interfaces import IDataManager
from uuid import uuid4

class FinishOnlyDataManager(object):

    implements(IDataManager)

    def __init__(self, callback, args=None, kwargs=None): 

        self.cb = callback
        self.args = [] if args is None else args
        self.kwargs = {} if kwargs is None else kwargs

        self.transaction_manager = transaction.manager
        self.key = str(uuid4())

    def sortKey(self): return self.key
    abort = commit = tpc_begin = tpc_vote = tpc_abort = lambda x,y: None

    def tpc_finish(self, tx): 

        # transaction.interfaces implies that exceptions are 
        # a bad thing.  assuming non-dire repercussions, and that
        # we're not dealing with remote (non-zodb) objects,  
        # swallow exceptions.

        try:
            self.cb(*self.args, **self.kwargs)
        except Exception, e:
            pass

以及相关的处理程序...

@grok.subscribe(IRemoteManaged, IObjectRemovedEvent)
def remove_plan(item, event): IRemoteManager(item).handle_remove()

class RemoteManager(object):     ... 

    def handle_remove(self):

        obj = self._retrieve_remote_object()

        def _do_remove():
            if obj:
                obj.delete()

        transaction.get().join(FinishOnlyDataManager(_do_remove))
于 2014-10-31T14:06:36.543 回答