summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-10-18 05:50:54 +0000
committerChris McDonough <chrism@agendaless.com>2009-10-18 05:50:54 +0000
commit1dc3907e59995852d5ee0251c1c92a360f03ed35 (patch)
tree38da7879503f2b55b995cee89a2aebe6c7d6f076 /repoze
parent2cce431f02a37c119eacfc3dfa94af9fe3305de1 (diff)
downloadpyramid-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__.py30
-rw-r--r--repoze/bfg/tests/test_integration.py26
-rw-r--r--repoze/bfg/tests/test_zcml.py94
-rw-r--r--repoze/bfg/view.py43
-rw-r--r--repoze/bfg/zcml.py62
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):