Plone: reacting to object removal

前端 未结 4 1460
小蘑菇
小蘑菇 2021-01-06 06:56

I\'m wanting to redirect to a container\'s parent after deleting an item within it. To this end, I\'ve tried subscribing to zope.lifecycleevent\'s IObjectRemovedEvent<

相关标签:
4条回答
  • 2021-01-06 07:17

    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.

    0 讨论(0)
  • 2021-01-06 07:17

    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))
    
    0 讨论(0)
  • 2021-01-06 07:21

    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:

    Form Controller overview screen with Actions tab higlighted

    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:

    Add new action override form, prefilled with example

    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.

    0 讨论(0)
  • 2021-01-06 07:23

    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.

    0 讨论(0)
提交回复
热议问题