Plone: ​​response to object deletion

I want to redirect the parent container after deleting the element inside it. To this end, I tried to subscribe to zope.lifecycleevent 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) 

An exception is container/id/delete_confirmation by clicking container/id/delete_confirmation , however this causes more events than I expected. My signed function is called twice: once when I click on the link, then again when I confirm the deletion. Even more confusing, it is also called if I cancel the deletion. I expected the event to be raised only if the object was, you know, removed from the container.

In all three cases, the event object is the same, with the same property values ​​for oldName, oldParent, etc.

How can I distinguish between a request to delete an item, a cancellation of this request and the actual removal of the item?

Update: so it seems that the initial event is being raised because the object has been removed from the container to check the integrity of the channel, followed by a rollback.

+4
source share
4 answers

The employee 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 is checked after commit to ensure that the object is indeed deleted before doing the redirection.

Some confirmation of whether this is a suitable approach will be appreciated.

+3
source

Here is another opportunity, again from the same brilliant employee:

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

Now I use zcml for subscription to make it easier to associate it with several types of content:

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

This is the solution I came across because I find it more explicit in what is happening than doing manual checks to work out if the event that I caught is the event that I really want.

+1
source

Instead of using an event handler, you can configure delete_confirmation actions; they can be changed even through the network and can be configured for each type. delete_confirmation script is a CMF Form Controller script , and there are several options for changing its behavior.

Currently, 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 can add a specific type action, for example by defining action.success.TypeName .

To do this over the Internet, go to ZMI and find the portal_form_controller tool, then go to the Actions tab:

Form Controller overview screen with Actions tab higlighted

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

On the actions tab there is a form for adding new actions:

Add new action override form, prefilled with example

As you can see, a context type is a drop-down list with all existing type registers to simplify specifying the type action. I copied into the regular action (a redirect_to action specified by the python: expression, and added an extra .aq_parent to select the parent container.

You can also add such an action using 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 these custom actions in the cmfformcontroller.xml file in the 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 underestimated things in Plone; I got this from the CMFFormController source code for the GS import and export code .

+1
source

I came across what I think should be a common use case where a local Plone object is proxying to a remote object. After deleting the Plone object, but only with the actual deletion, I want to delete the deleted object.

For me, addAfterCommitHook () did not escape any problems, so I applied the IDataManager user approach, which provides a good general solution for use in simlar ...

 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
source

All Articles