Plone:对物体移除做出反应

时间:2012-06-27 01:40:33

标签: python plone

我想在删除其中的项目后重定向到容器的父级。为此,我尝试订阅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 个答案:

答案 0 :(得分: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)

这在提交之后进行检查,以确保在执行重定向之前已经删除了对象。

虽然可以理解是否这是一种合适的方法。

答案 1 :(得分: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"
/>

这是我最终会采用的解决方案,因为我发现它比正在进行的操作更明确,如果我抓到的事件是我真正想要的事件,可以解决问题。

答案 2 :(得分:1)

您可以 自定义delete_confirmation操作,而不是使用事件处理程序;这些可以通过网络进行更改,并且可以按类型进行自定义delete_confirmation脚本是CMF Form Controller script,有几个选项可以改变它的行为。

目前,行动的定义如下:

[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标签:

Form Controller overview screen with Actions tab higlighted

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

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

Add new action override form, prefilled with example

如您所见,上下文类型是一个包含所有现有类型注册的下拉列表,可以更轻松地指定特定于类型的操作。我已经复制了常规操作(由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()')

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

<?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中未记录的内容之一;我从CMFFormController sourcecode for the GS import and export code得到了这个。

答案 3 :(得分: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))