Log message for revision 69839: Made the __call__ method of ViewMixinForAttributes have the same signature as the original attribute. This aids some pathological request parameter marshalling. Thanks to Balazs Ree for the patch.
Changed: U Products.Five/trunk/CHANGES.txt U Products.Five/trunk/browser/metaconfigure.py U Products.Five/trunk/browser/tests/test_defaultview.py -=- Modified: Products.Five/trunk/CHANGES.txt =================================================================== --- Products.Five/trunk/CHANGES.txt 2006-08-29 14:09:42 UTC (rev 69838) +++ Products.Five/trunk/CHANGES.txt 2006-08-29 14:41:47 UTC (rev 69839) @@ -5,6 +5,10 @@ Five 1.5.1 (unreleased) ===================== +* Made the __call__ method of ViewMixinForAttributes have the same signature + as the original attribute. This aids some pathological request parameter + marshalling. + * Fixed #2168: Missing import Five 1.5 (2006-08-13) Modified: Products.Five/trunk/browser/metaconfigure.py =================================================================== --- Products.Five/trunk/browser/metaconfigure.py 2006-08-29 14:09:42 UTC (rev 69838) +++ Products.Five/trunk/browser/metaconfigure.py 2006-08-29 14:41:47 UTC (rev 69839) @@ -386,10 +386,7 @@ # this is technically not needed because ZPublisher finds our # attribute through __browser_default__; but we also want to be # able to call pages from python modules, PythonScripts or ZPT - def __call__(self, *args, **kw): - attr = self.__page_attribute__ - meth = getattr(self, attr) - return meth(*args, **kw) + __call__ = property(lambda self: getattr(self, self.__page_attribute__)) class ViewMixinForTemplates(BrowserView): Modified: Products.Five/trunk/browser/tests/test_defaultview.py =================================================================== --- Products.Five/trunk/browser/tests/test_defaultview.py 2006-08-29 14:09:42 UTC (rev 69838) +++ Products.Five/trunk/browser/tests/test_defaultview.py 2006-08-29 14:41:47 UTC (rev 69839) @@ -114,6 +114,98 @@ >>> tearDown() """ +def test_default_method_args_marshalling(): + """\ + Test the default call method of a view, with respect to possible + breakage of argument marshalling from other components + + This is not directly a bug in Five, just a change that enables + components have simpler code to imitate ZPublisher's arguments + marshalling strategy on default view methods. + + The ZPublisher marshalls arguments to called methods from the + request based on the method's signature. This however assumes + that it finds the real callable method. However in case of the + autogenerated __call__ method of a view, the real method is + wrapped. Although the publisher correctly handles this by + looking at the __browser_default__ and applying the request on + the real method, Plone's portal factory does not do this + correctly, thus causing these method calls fail with TypeError, + since no parameters will be marshalled to the browser default + methods if within the portal factory. + + The applied fix changes the __call__ in such a way that it is + not wrapper any more, but yields the original callable instead. + This test simply checks that this is so, in other words this is + a check that would have failed with the original version. + + First, we load the configuration file: + + >>> import Products.Five.tests + >>> from Products.Five import zcml + >>> zcml.load_config('meta.zcml', Products.Five) + >>> zcml.load_config("permissions.zcml", Products.Five) + >>> zcml.load_config('directives.zcml', Products.Five.tests) + + Define a view, with a single attribute and the name of the view + is the same as the attribute. Important is that we will use the + default browser view. + + >>> zcml.load_string(''' + ... <configure xmlns="http://namespaces.zope.org/zope" + ... xmlns:browser="http://namespaces.zope.org/browser"> + ... <browser:page + ... for="Products.Five.browser.tests.classes.IOne" + ... class="Products.Five.browser.tests.classes.ViewOne" + ... attribute="my_method" + ... name="my_method" + ... permission="zope2.Public" + ... /> + ... </configure> + ... ''') + + Create a context object and a request. Provide parameters on the + request. + + >>> from Products.Five.browser.tests.classes import One + >>> context = One() + >>> from zope.publisher.browser import TestRequest + >>> request = TestRequest(form={'arg1': 'A', 'arg2': 'B', 'kw1': 'C'}) + + Create the view. + + >>> from zope.component import getMultiAdapter + >>> from zope.interface import Interface + >>> view = getMultiAdapter((context, request), Interface, 'my_method') + + Check that the __call__ method's signature equals to the real + method's signature. They both should yield the four parameters. + + >>> def args(method): + ... f = method.im_func + ... c = f.func_code + ... defaults = f.func_defaults + ... names = c.co_varnames[1:c.co_argcount] + ... return names + >>> args(view.my_method) + ('arg1', 'arg2', 'kw1', 'kw2') + >>> args(view.__call__) + ('arg1', 'arg2', 'kw1', 'kw2') + + Finally, call the view's default method. Important is, if this + gives a TypeError then the portal factory will fail. This is in + effect the same as the previous argument check was. + + >>> from ZPublisher.mapply import mapply + >>> mapply(view.__call__, (), request) + CALLED A B C D + + Clean up adapter registry and others: + + >>> from zope.testing.cleanup import cleanUp + >>> cleanUp() + """ + def test_suite(): from Testing.ZopeTestCase import FunctionalDocTestSuite return FunctionalDocTestSuite() _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins