我发现了 Plone 的 RegistrationTool 和 PasswordResetTool 的问题
即使没有发出 HTTPRequest,也会呈现模板 mail_password_response.pt。该模板是一个依赖于 main_template 的全帧 Plone 页面。main_template 具有依赖于真实 HTTPRequest 对象的存在的 viewlet,即其 ACTUAL_URL 参数。
如何解决这种情况?显然,如果没有浏览器来显示结果,则不应呈现 mail_password_response.pt。但是如何在深层 Plone 代码中检查 HTTPRequest 是否是“真实的”,这应该屈服于 UI 响应,比如可怕的 ZMI manage_xxx hacks?
解决这个问题的正确方法是什么?是让 RegistrationTool 不呈现模板还是让 viewlet 对不完整的测试存根 HTTPRequest 对象安全?
这是在 palayout.viewlets.common 中需要 ACTUAL_URL 的 viewlets 位
class ContentViewsViewlet(ViewletBase):
@memoize
def prepareObjectTabs(self, default_tab='view', sort_first=['folderContents']):
"""Prepare the object tabs by determining their order and working
out which tab is selected. Used in global_contentviews.pt
"""
context = aq_inner(self.context)
context_url = context.absolute_url()
request_url = self.request['ACTUAL_URL']
request_url_path = request_url[len(context_url):]
if request_url_path.startswith('/'):
request_url_path = request_url_path[1:]
这是 CMFPlone RegistrationTool 中的违规调用,main_template.pt
即使没有消费者,它也会触发渲染完整:
security.declarePublic('registeredNotify')
def registeredNotify(self, new_member_id):
""" Wrapper around registeredNotify """
membership = getToolByName(self, 'portal_membership')
utils = getToolByName(self, 'plone_utils')
member = membership.getMemberById(new_member_id)
...
return self.mail_password_response(self, self.REQUEST)
示例回溯:
Error in test test_logout_smartcard (xxxtesting.ui.test_smartcard.TestSmartcard)
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 318, in run
self.setUp()
File "/Users/mikko/code/xxx-dev/src/xxx-packages/xxxtesting/xxxtesting/ui/test_smartcard.py", line 193, in setUp
self.xxx_helper.create_all()
File "/Users/mikko/code/xxx-dev/src/xxx-packages/xxxtesting/xxxtesting/xxxhelper.py", line 594, in create_all
self.create_xxx_manager("xxx_manager")
File "/Users/mikko/code/xxx-dev/src/xxx-packages/xxxtesting/xxxtesting/xxxhelper.py", line 412, in create_xxx_manager
mem.update(**data)
File "/Users/mikko/code/buildout-cache/eggs/Products.remember-1.9.1-py2.7.egg/Products/remember/content/member.py", line 653, in update
triggerAutomaticTransitions(self)
File "/Users/mikko/code/buildout-cache/eggs/Products.remember-1.9.1-py2.7.egg/Products/remember/Extensions/workflow.py", line 34, in triggerAutomaticTransitions
wf_tool.doActionFor(ob, 'trigger')
File "/Users/mikko/code/buildout-cache/eggs/Products.CMFCore-2.2.6-py2.7.egg/Products/CMFCore/WorkflowTool.py", line 241, in doActionFor
wfs, ob, action, wf.doActionFor, (ob, action) + args, kw)
File "/Users/mikko/code/buildout-cache/eggs/Products.CMFCore-2.2.6-py2.7.egg/Products/CMFCore/WorkflowTool.py", line 552, in _invokeWithNotification
res = func(*args, **kw)
File "/Users/mikko/code/buildout-cache/eggs/Products.DCWorkflow-2.2.4-py2.7.egg/Products/DCWorkflow/DCWorkflow.py", line 282, in doActionFor
self._changeStateOf(ob, tdef, kw)
File "/Users/mikko/code/buildout-cache/eggs/Products.DCWorkflow-2.2.4-py2.7.egg/Products/DCWorkflow/DCWorkflow.py", line 421, in _changeStateOf
sdef = self._executeTransition(ob, tdef, kwargs)
File "/Users/mikko/code/buildout-cache/eggs/Products.DCWorkflow-2.2.4-py2.7.egg/Products/DCWorkflow/DCWorkflow.py", line 474, in _executeTransition
script(sci) # May throw an exception.
File "/Users/mikko/code/buildout-cache/eggs/Products.ExternalMethod-2.13.0-py2.7.egg/Products/ExternalMethod/ExternalMethod.py", line 241, in __call__
return f(self.aq_parent.this(), *args, **kw)
- __traceback_info__: ((<Products.DCWorkflow.Expression.StateChangeInfo instance at 0x107492e18>,), {}, None)
File "/Users/mikko/code/buildout-cache/eggs/Products.remember-1.9.1-py2.7.egg/Products/remember/Extensions/workflow.py", line 64, in register
return obj.register()
File "/Users/mikko/code/xxx-dev/src/xxx-eggs/Products.xxxHospital/Products/xxxHospital/content/xxxUser.py", line 194, in register
BaseMember.register(self)
File "/Users/mikko/code/buildout-cache/eggs/Products.remember-1.9.1-py2.7.egg/Products/remember/content/member.py", line 718, in register
rtool.registeredNotify(self.getId())
File "/Users/mikko/code/buildout-cache/eggs/Products.CMFPlone-4.1.6-py2.7.egg/Products/CMFPlone/RegistrationTool.py", line 345, in registeredNotify
return self.mail_password_response(self, self.REQUEST)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Shared/DC/Scripts/Bindings.py", line 322, in __call__
return self._bindAndExec(args, kw, None)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Shared/DC/Scripts/Bindings.py", line 359, in _bindAndExec
return self._exec(bound_data, args, kw)
File "/Users/mikko/code/buildout-cache/eggs/Products.CMFCore-2.2.6-py2.7.egg/Products/CMFCore/FSPageTemplate.py", line 237, in _exec
result = self.pt_render(extra_context=bound_names)
File "/Users/mikko/code/buildout-cache/eggs/Products.CMFCore-2.2.6-py2.7.egg/Products/CMFCore/FSPageTemplate.py", line 177, in pt_render
self, source, extra_context
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/PageTemplates/PageTemplate.py", line 79, in pt_render
showtal=showtal)
File "/Users/mikko/code/buildout-cache/eggs/zope.pagetemplate-3.5.2-py2.7.egg/zope/pagetemplate/pagetemplate.py", line 113, in pt_render
strictinsert=0, sourceAnnotations=sourceAnnotations)()
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 271, in __call__
self.interpret(self.program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 888, in do_useMacro
self.interpret(macro)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 533, in do_optTag_tal
self.do_optTag(stuff)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 518, in do_optTag
return self.no_tag(start, program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 513, in no_tag
self.interpret(program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 954, in do_defineSlot
self.interpret(block)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 533, in do_optTag_tal
self.do_optTag(stuff)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 518, in do_optTag
return self.no_tag(start, program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 513, in no_tag
self.interpret(program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 858, in do_defineMacro
self.interpret(macro)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 852, in do_condition
self.interpret(block)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 742, in do_insertStructure_tal
structure = self.engine.evaluateStructure(expr)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/PageTemplates/Expressions.py", line 218, in evaluateStructure
text = super(ZopeContext, self).evaluateStructure(expr)
File "/Users/mikko/code/buildout-cache/eggs/zope.tales-3.5.2-py2.7.egg/zope/tales/tales.py", line 696, in evaluate
return expression(self)
- file:/Users/mikko/code/xxx-dev/src/xxx-packages/plonetheme.xxxexternal/plonetheme/xxxexternal/skins/plonetheme_xxxexternal_custom_templates/main_template.pt
- Line 83, Column 20
- Expression: <StringExpr u'plone.contentviews'>
- Names:
{'container': <PloneSite at /plone>,
'context': <RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
'default': <object object at 0x10204e970>,
'here': <RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
'loop': {},
'nothing': None,
'options': {'args': (<RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
<HTTPRequest, URL=http://localhost:55001>)},
'repeat': <Products.PageTemplates.Expressions.SafeMapping object at 0x107704c00>,
'request': <HTTPRequest, URL=http://localhost:55001>,
'root': <Application at >,
'template': <FSPageTemplate at /plone/mail_password_response used for /plone/portal_registration>,
'traverse_subpath': [],
'user': <PloneUser 'admin'>}
File "/Users/mikko/code/buildout-cache/eggs/zope.contentprovider-3.7.2-py2.7.egg/zope/contentprovider/tales.py", line 80, in __call__
return provider.render()
File "/Users/mikko/code/buildout-cache/eggs/plone.app.viewletmanager-2.0.2-py2.7.egg/plone/app/viewletmanager/manager.py", line 154, in render
return BaseOrderedViewletManager.render(self)
File "/Users/mikko/code/buildout-cache/eggs/plone.app.viewletmanager-2.0.2-py2.7.egg/plone/app/viewletmanager/manager.py", line 85, in render
return u'\n'.join([viewlet.render() for viewlet in self.viewlets])
File "/Users/mikko/code/buildout-cache/eggs/plone.app.layout-2.1.13-py2.7.egg/plone/app/layout/viewlets/common.py", line 48, in render
return self.index()
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/Five/browser/pagetemplatefile.py", line 125, in __call__
return self.im_func(im_self, *args, **kw)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/Five/browser/pagetemplatefile.py", line 59, in __call__
sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0),
File "/Users/mikko/code/buildout-cache/eggs/zope.pagetemplate-3.5.2-py2.7.egg/zope/pagetemplate/pagetemplate.py", line 113, in pt_render
strictinsert=0, sourceAnnotations=sourceAnnotations)()
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 271, in __call__
self.interpret(self.program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 852, in do_condition
self.interpret(block)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 531, in do_optTag_tal
self.no_tag(stuff[-2], stuff[-1])
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 513, in no_tag
self.interpret(program)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 343, in interpret
handlers[opcode](self, args)
File "/Users/mikko/code/buildout-cache/eggs/zope.tal-3.5.2-py2.7.egg/zope/tal/talinterpreter.py", line 583, in do_setLocal_tal
self.engine.setLocal(name, self.engine.evaluateValue(expr))
File "/Users/mikko/code/buildout-cache/eggs/zope.tales-3.5.2-py2.7.egg/zope/tales/tales.py", line 696, in evaluate
return expression(self)
- /Users/mikko/code/buildout-cache/eggs/plone.app.layout-2.1.13-py2.7.egg/plone/app/layout/viewlets/contentviews.pt
- Line 6, Column 4
- Expression: <PathExpr standard:u'view/prepareObjectTabs'>
- Names:
{'args': (),
'container': <RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
'context': <RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
'default': <object object at 0x10204e970>,
'here': <RegistrationTool at /plone/portal_registration used for /plone/portal_memberdata/xxx_manager>,
'loop': {},
'nothing': None,
'options': {},
'repeat': <Products.PageTemplates.Expressions.SafeMapping object at 0x107afe050>,
'request': <HTTPRequest, URL=http://localhost:55001>,
'root': <Application at >,
'template': <Products.Five.browser.pagetemplatefile.ViewPageTemplateFile object at 0x105dfb810>,
'traverse_subpath': [],
'user': <PloneUser 'admin'>,
'view': <Products.Five.viewlet.metaconfigure.ContentViewsViewlet object at 0x10868c050>,
'views': <Products.Five.browser.pagetemplatefile.ViewMapper object at 0x107ff1f90>}
File "/Users/mikko/code/buildout-cache/eggs/zope.tales-3.5.2-py2.7.egg/zope/tales/expressions.py", line 217, in __call__
return self._eval(econtext)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/PageTemplates/Expressions.py", line 155, in _eval
return render(ob, econtext.vars)
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/Products/PageTemplates/Expressions.py", line 117, in render
ob = ob()
File "/Users/mikko/code/buildout-cache/eggs/plone.memoize-1.1.1-py2.7.egg/plone/memoize/view.py", line 47, in memogetter
value = cache[key] = func(*args, **kwargs)
File "/Users/mikko/code/buildout-cache/eggs/plone.app.layout-2.1.13-py2.7.egg/plone/app/layout/viewlets/common.py", line 258, in prepareObjectTabs
request_url = self.request['ACTUAL_URL']
File "/Users/mikko/code/buildout-cache/eggs/Zope2-2.13.15-py2.7.egg/ZPublisher/HTTPRequest.py", line 1372, in __getitem__
raise KeyError, key
KeyError: 'ACTUAL_URL'