diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-10-18 05:50:54 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-10-18 05:50:54 +0000 |
| commit | 1dc3907e59995852d5ee0251c1c92a360f03ed35 (patch) | |
| tree | 38da7879503f2b55b995cee89a2aebe6c7d6f076 /repoze | |
| parent | 2cce431f02a37c119eacfc3dfa94af9fe3305de1 (diff) | |
| download | pyramid-1dc3907e59995852d5ee0251c1c92a360f03ed35.tar.gz pyramid-1dc3907e59995852d5ee0251c1c92a360f03ed35.tar.bz2 pyramid-1dc3907e59995852d5ee0251c1c92a360f03ed35.zip | |
- The ``@bfg_view`` decorator can now be used against a class method::
from webob import Response
from repoze.bfg.view import bfg_view
class MyView(object):
def __init__(self, context, request):
self.context = context
self.request = request
@bfg_view(name='hello')
def amethod(self):
return Response('hello from %s!' % self.context)
When the bfg_view decorator is used against a class method, a view
is registered for the *class* (it's a "class view" where the "attr"
happens to be the method they're attached to), so the view class
must have a suitable constructor.
Diffstat (limited to 'repoze')
| -rw-r--r-- | repoze/bfg/tests/grokkedapp/__init__.py | 30 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_integration.py | 26 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 94 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 43 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 62 |
5 files changed, 225 insertions, 30 deletions
diff --git a/repoze/bfg/tests/grokkedapp/__init__.py b/repoze/bfg/tests/grokkedapp/__init__.py index 5a00adbeb..5d2843885 100644 --- a/repoze/bfg/tests/grokkedapp/__init__.py +++ b/repoze/bfg/tests/grokkedapp/__init__.py @@ -52,6 +52,29 @@ class Foo(object): grokked_instance = Foo() grokked_instance = bfg_view(name='grokked_instance')(grokked_instance) +class Base(object): + @bfg_view(name='basemethod') + def basemethod(self): + """ """ + +class MethodViews(Base): + def __init__(self, context, request): + self.context = context + self.request = request + + @bfg_view(name='method1') + def method1(self): + return 'method1' + + @bfg_view(name='method2') + def method2(self): + return 'method2' + + @bfg_view(name='stacked_method2') + @bfg_view(name='stacked_method1') + def stacked(self): + return 'stacked_method' + # ungrokkable A = 1 @@ -59,4 +82,9 @@ B = {} def stuff(): """ """ - + +class Whatever(object): + pass + +class Whatever2: + pass diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py index 691d64e24..f05601031 100644 --- a/repoze/bfg/tests/test_integration.py +++ b/repoze/bfg/tests/test_integration.py @@ -109,9 +109,8 @@ class TestGrokkedApp(unittest.TestCase): actions = zcml_configure('configure.zcml', package) actions.sort() - num = 18 + num = 23 - action_names = [actions[x][0][2] for x in range(len(actions[:num]))] action_types = [(actions[x][0][1], actions[x][0][3], actions[x][0][4]) for x in range(len(actions[:num]))] @@ -119,6 +118,9 @@ class TestGrokkedApp(unittest.TestCase): for typ in action_types: self.assertEqual(typ, (None, IRequest, IView)) + action_names = [actions[x][0][2] for x in range(len(actions[:num]))] + action_names.sort() + self.assertEqual( action_names, [ '', @@ -132,13 +134,18 @@ class TestGrokkedApp(unittest.TestCase): 'another_stacked2', 'another_stacked_class1', 'another_stacked_class2', + 'basemethod', 'grokked_class', 'grokked_instance', + 'method1', + 'method2', 'oldstyle_grokked_class', 'stacked1', 'stacked2', 'stacked_class1', 'stacked_class2', + 'stacked_method1', + 'stacked_method2', ] ) @@ -206,6 +213,21 @@ class TestGrokkedApp(unittest.TestCase): result = render_view_to_response(ctx, req, 'another_stacked_class2') self.assertEqual(result, 'another_stacked_class') + self.assertRaises(TypeError, + render_view_to_response, ctx, req, 'basemethod') + + result = render_view_to_response(ctx, req, 'method1') + self.assertEqual(result, 'method1') + + result = render_view_to_response(ctx, req, 'method2') + self.assertEqual(result, 'method2') + + result = render_view_to_response(ctx, req, 'stacked_method1') + self.assertEqual(result, 'stacked_method') + + result = render_view_to_response(ctx, req, 'stacked_method2') + self.assertEqual(result, 'stacked_method') + class DummyContext(object): pass diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 7538072ca..282c27472 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -2388,7 +2388,7 @@ class TestBFGViewGrokker(unittest.TestCase): request_type=IRequest, route_name=None, request_method=None, request_param=None, containment=None, attr=None, renderer=None, - wrapper_viewname=None, xhr=False, header=None, + wrapper=None, xhr=False, header=None, accept=None) obj.__bfg_view_settings__ = [settings] context = DummyContext() @@ -2413,6 +2413,98 @@ class TestBFGViewGrokker(unittest.TestCase): actions = context.actions self.assertEqual(len(actions), 0) +class TestBFGClassGrokker(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _getTargetClass(self): + from repoze.bfg.zcml import BFGClassGrokker + return BFGClassGrokker + + def _makeOne(self, *arg, **kw): + return self._getTargetClass()(*arg, **kw) + + def test_calls_bfg_view_grokker(self): + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from zope.interface import Interface + class obj: + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' + settings = dict(permission='foo', for_=Interface, name='foo.html', + request_type=IRequest, route_name=None, + request_method=None, request_param=None, + containment=None, attr=None, renderer=None, + wrapper=None, xhr=False, header=None, + accept=None) + obj.__bfg_view_settings__ = [settings] + grokker = self._makeOne() + context = DummyContext() + result = grokker.grok('name', obj, context=context) + self.assertEqual(result, True) + actions = context.actions + self.assertEqual(len(actions), 1) + register = actions[0]['callable'] + register() + sm = getSiteManager() + wrapped = sm.adapters.lookup((Interface, IRequest), IView, + name='foo.html') + self.assertEqual(wrapped(None, None), 'OK') + + def test_it(self): + from repoze.bfg.interfaces import IRequest + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IView + class base(object): + def basemethod(self): + """ """ + basemethod.__bfg_view_settings__ = [ + dict(permission=None, for_=Interface, name='basemethod', + request_type=IRequest, route_name=None, + request_method=None, request_param=None, + containment=None, attr=None, renderer=None, + wrapper=None, xhr=False, header=None, + accept=None) + ] + + class obj(base): + def __init__(self, context, request): + pass + def foo(self): + return 'OK' + foo.__bfg_view_settings__ = [ + dict(permission=None, for_=Interface, name='foo', + request_type=IRequest, route_name=None, + request_method=None, request_param=None, + containment=None, attr=None, renderer=None, + wrapper=None, xhr=False, header=None, + accept=None) + ] + + grokker = self._makeOne() + context = DummyContext() + result = grokker.grok('name', obj, context=context) + self.assertEqual(result, True) + actions = context.actions + self.assertEqual(len(actions), 1) + register = actions[0]['callable'] + register() + sm = getSiteManager() + wrapped = sm.adapters.lookup((Interface, IRequest), IView, + name='foo') + self.assertEqual(wrapped(None, None), 'OK') + wrapped = sm.adapters.lookup((Interface, IRequest), IView, + name='basemethod') + # base methods not grokked + self.assertEqual(wrapped, None) + class TestZCMLScanDirective(unittest.TestCase): def setUp(self): cleanUp() diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 7f71f2c57..8c1106537 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -369,6 +369,47 @@ class bfg_view(object): .. warning:: Using a class as a view is a new feature in 0.8.1+. + The bfg_view decorator can also be used against a class method:: + + from webob import Response + from repoze.bfg.view import bfg_view + + class MyView(object): + def __init__(self, context, request): + self.context = context + self.request = request + + @bfg_view(name='hello') + def amethod(self): + return Response('hello from %s!' % self.context) + + When the bfg_view decorator is used against a class method, a view + is registered for the *class* (as described above), so the class + constructor must accept either ``request`` or ``context, + request``. The method which is decorated must return a response + (or rely on a :term:`renderer` to generate one). Using the + decorator against a particular method of a class is equivalent to + using the ``attr`` parameter in a decorator attached to the class + itself. For example, the above registration implied by the + decorator being used against the ``amethod`` method could be + spelled equivalently as:: + + from webob import Response + from repoze.bfg.view import bfg_view + + @bfg_view(attr='amethod', name='hello') + class MyView(object): + def __init__(self, context, request): + self.context = context + self.request = request + + def amethod(self): + return Response('hello from %s!' % self.context) + + .. warning:: The ability to use the ``bfg_view`` decorator as a + method decorator is new in :mod:`repoze.bfg` version + 1.1. + To make use of any bfg_view declaration, you *must* insert the following boilerplate into your application registry's ZCML:: @@ -388,7 +429,7 @@ class bfg_view(object): self.containment = containment self.attr = attr self.renderer = renderer - self.wrapper_viewname = wrapper + self.wrapper = wrapper self.xhr = xhr self.accept = accept self.header = header diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 681c4da1e..cbe438560 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,6 +1,8 @@ +import inspect import re import sys +from zope.interface import implements from zope.component import getSiteManager from zope.component import getUtility from zope.component import queryUtility @@ -17,6 +19,7 @@ from zope.schema import Int from zope.schema import TextLine import martian +import martian.interfaces from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy @@ -675,47 +678,56 @@ def scan(_context, package, martian=martian): # martian overrideable only for unit tests multi_grokker = BFGMultiGrokker() multi_grokker.register(BFGViewGrokker()) + multi_grokker.register(BFGClassGrokker()) module_grokker = martian.ModuleGrokker(grokker=multi_grokker) martian.grok_dotted_name(package.__name__, grokker=module_grokker, context=_context, exclude_filter=exclude) ################# utility stuff #################### -class BFGViewMarker(object): +class Class(object): + pass + +class AnythingButAClass(object): pass class BFGMultiGrokker(martian.core.MultiInstanceOrClassGrokkerBase): def get_bases(self, obj): - if hasattr(obj, '__bfg_view_settings__'): - return [BFGViewMarker] + if inspect.isclass(obj): + return [Class] + elif hasattr(obj, '__bfg_view_settings__'): + return [AnythingButAClass] return [] +class BFGClassGrokker(object): + implements(martian.interfaces.IGrokker) + martian = martian # for unit tests + martian.component(Class) + def grok(self, name, class_, module_info=None, **kw): + # The class itself may be decorated, so we feed it to BFGViewGrokker + found = BFGViewGrokker().grok(name, class_, **kw) + + # grok any decorations attached to the class' method (direct + # methods only, not methods of any base class) + methods = inspect.getmembers(class_, inspect.ismethod) + basemethods = class_.__dict__.keys() + for method_name, method in methods: + if method_name in basemethods: + # it's not an inherited method + config = getattr(method, '__bfg_view_settings__', []) + for settings in config: + settings = dict(settings) + settings['attr'] = settings['attr'] or method_name + view(kw['context'], view=class_, **settings) + found = True + return found + class BFGViewGrokker(martian.InstanceGrokker): - martian.component(BFGViewMarker) + martian.component(AnythingButAClass) def grok(self, name, obj, **kw): config = getattr(obj, '__bfg_view_settings__', []) for settings in config: - permission = settings['permission'] - for_ = settings['for_'] - name = settings['name'] - request_type = settings['request_type'] - route_name = settings['route_name'] - request_method = settings['request_method'] - request_param = settings['request_param'] - containment = settings['containment'] - wrapper = settings['wrapper_viewname'] - attr = settings['attr'] - renderer = settings['renderer'] - xhr = settings['xhr'] - accept = settings['accept'] - header = settings['header'] - context = kw['context'] - view(context, permission=permission, for_=for_, - view=obj, name=name, request_type=request_type, - route_name=route_name, request_method=request_method, - request_param=request_param, containment=containment, - attr=attr, renderer=renderer, wrapper=wrapper, - xhr=xhr, accept=accept, header=header) + view(kw['context'], view=obj, **settings) return bool(config) def exclude(name): |
