Plone: reacting to object removal

六眼飞鱼酱① 提交于 2019-11-30 22:58:37

A co-worker came up with a working solution:

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)

This checks after the commit to ensure the object actually has been removed, before performing the redirection.

Some confirmation of whether this is a suitable approach would be appreciated, though.

Here's another possibility, again from the same genius co-worker:

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))

I'm now using zcml for subscription to more easily bind it to multiple content types:

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

This is the solution I've ended up going with, as I find it more explicit about what's happening than doing manual checks to work out if the event I've caught is the event I really want.

Instead of using a event handler, you could customize the delete_confirmation actions; these can be altered through the web even, and can be customized per type. The delete_confirmation script is a CMF Form Controller script and there are several options to alter it's behaviour.

Currently, the actions are defined as such:

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

You could add a type specific action by defining action.success.TypeName, for example.

To do so through-the-web, visit the ZMI and find the portal_form_controller tool, then click the Actions tab:

As you can see in this screenshot there is also documentation on the tool available here.

On the actions tab there is a form to add new actions:

As you can see, the context type is a drop-down with all existing type registrations to make it easier to specify a type-specific action. I've copied in the regular action (a redirect_to action specified by a python: expression and added an extra .aq_parent to select the container parent.

You could also add such an action with the .addFormAction method on the tool:

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()')

Last, but not least, you can specify such custom actions in the cmfformcontroller.xml file in a GenericSetup profile; here is an example based on the above action:

<?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>

This format is one of those under-documented things in Plone; I got this from the CMFFormController sourcecode for the GS import and export code.

I'm facing what I think must be a common use case as well, where a local Plone object is proxying for a remote object. Upon removal of the Plone object, but only on actual removal, I want to remove the remote object.

For me, addAfterCommitHook() didn't avoid any of the issues, so I took a custom IDataManager approach, which provides a nice generic solution to simlar use cases...

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

And the associated handler...

@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))
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!