Python Plone:对移除对象作出反应
我想在删除容器中的项目后重定向到容器的父级。为此,我尝试订阅了Python Plone:对移除对象作出反应,python,plone,Python,Plone,我想在删除容器中的项目后重定向到容器的父级。为此,我尝试订阅了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_pa
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)
单击容器/id/delete\u确认
会触发删除,但这会触发比我预期的更多的事件。我的订阅函数被调用两次:一次是在我点击链接时,另一次是在我确认删除时。更令人困惑的是,如果我取消删除,它也会被调用。我希望只有当一个对象从容器中移除时才会引发该事件
在这三种情况下,事件对象都是相同的,oldName、oldParent等具有相同的属性值
如何区分请求删除项目、取消请求和实际删除项目
更新:因此调用初始事件似乎是因为从容器中移除对象以检查链接完整性,此时会出现回滚。一位同事想出了一个可行的解决方案:
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)
这将在提交后进行检查,以确保在执行重定向之前确实已删除对象
不过,如果能证实这是否是一种合适的方法,我们将不胜感激。这里还有另一种可能性,同样来自同一位genius同事:
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 for subscription更轻松地将其绑定到多种内容类型:
<subscriber
zcml:condition="installed zope.lifecycleevent"
for=".schema.ISite zope.lifecycleevent.IObjectRemovedEvent"
handler=".base.redirect_to_trial"
/>
这就是我最终采用的解决方案,因为我发现它比手动检查我捕获的事件是否是我真正想要的事件更清楚地说明发生了什么。您可以自定义
删除确认操作,而不是使用事件处理程序;这些甚至可以通过web进行更改,并且可以根据类型进行自定义。delete\u确认
脚本是一个脚本,有几个选项可以改变它的行为
目前,这些行动的定义如下:
[actions]
action.success=redirect_to:python:object.aq_inner.aq_parent.absolute_url()
action.confirm=traverse_to:string:delete_confirmation_page
例如,您可以通过定义action.success.TypeName
来添加特定于类型的操作
要通过web执行此操作,请访问ZMI并找到portal\u form\u controller
工具,然后单击Actions
选项卡:
正如您在这个屏幕截图中看到的,这里还有关于该工具的文档
在“操作”选项卡上,有一个用于添加新操作的表单:
如您所见,上下文类型是包含所有现有类型注册的下拉列表,以便更容易指定特定于类型的操作。我复制了常规操作(aredirect\u到python:
表达式指定的操作,并添加了一个额外的.aq\u 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
文件中指定此类自定义操作;以下是基于上述操作的示例:
.我也面临着一个我认为很常见的用例,即本地Plone对象代理远程对象。移除Plone对象后,但仅在实际移除时,我想移除远程对象
对我来说,addAfterCommitHook()并没有避免任何问题,所以我采用了自定义IDataManager方法,它为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
和相关的处理程序
@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))
“addAfterCommitHook”--太棒了!我不知道这个:)谢谢分享,效果非常好!FWIW,我需要使用addBeforeCommitHook
而不是afterCommitHook
,然后得到“redirect_to_trial()至少需要1个参数(2个给定)”,这是可以解决的,通过另外传递一个空的args
-dict。整行是:trans.addBeforeCommitHook(redirect_to_to_trial,args=(),kws=kwargs)
谢谢Martijn。我天生就是一名程序员,所以我总是更喜欢编程方法,但我认为这也很方便。@MatthewTrevor:这里也是一样,但是使用事务管理器进行重定向,我们可以说,是一个解决问题的大锤,当有一个更简单的方法时..+1并且感谢您指出GS的可能性,很高兴知道这一点。