summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-06-19 09:24:49 +0000
committerChris McDonough <chrism@agendaless.com>2009-06-19 09:24:49 +0000
commitb606d97195187bdb33e334a7a40df501b30e2f48 (patch)
tree487a09938f408e02757846d62796b582b0902ce3
parent65697f35f2670fc93e695a7ddf083320f8a11f1a (diff)
downloadpyramid-b606d97195187bdb33e334a7a40df501b30e2f48.tar.gz
pyramid-b606d97195187bdb33e334a7a40df501b30e2f48.tar.bz2
pyramid-b606d97195187bdb33e334a7a40df501b30e2f48.zip
- A new ZCML directive was added named ``notfound``. This ZCML
directive can be used to name a view that should be invoked when the request can't otherwise be resolved to a view callable. For example:: <notfound view="helloworld.views.notfound_view"/> - A new ZCML directive was added named ``forbidden``. This ZCML directive can be used to name a view that should be invoked when a view callable for a request is found, but cannot be invoked due to an authorization failure. For example:: <forbidden view="helloworld.views.forbidden_view"/>
-rw-r--r--CHANGES.txt31
-rw-r--r--repoze/bfg/includes/meta.zcml12
-rw-r--r--repoze/bfg/router.py6
-rw-r--r--repoze/bfg/tests/test_router.py2
-rw-r--r--repoze/bfg/tests/test_zcml.py60
-rw-r--r--repoze/bfg/zcml.py30
6 files changed, 135 insertions, 6 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 476048dc5..43ea07b32 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,21 @@ Next release
Features
--------
+- A new ZCML directive was added named ``notfound``. This ZCML
+ directive can be used to name a view that should be invoked when the
+ request can't otherwise be resolved to a view callable. For example::
+
+ <notfound
+ view="helloworld.views.notfound_view"/>
+
+- A new ZCML directive was added named ``forbidden``. This ZCML
+ directive can be used to name a view that should be invoked when a
+ view callable for a request is found, but cannot be invoked due to
+ an authorization failure. For example::
+
+ <forbidden
+ view="helloworld.views.forbidden_view"/>
+
- Allow views to be *optionally* defined as callables that accept only
a request object, instead of both a context and a request (which
still works, and always will). The following types work as views in
@@ -81,6 +96,19 @@ Features
contain a key ``bfg.routes.route`` (the Route object which matched),
and a key ``bfg.routes.matchdict`` (the result of calling route.match).
+Deprecations
+------------
+
+- Utility registrations against
+ ``repoze.bfg.interfaces.INotFoundView`` and
+ ``repoze.bfg.interfaces.IForbiddenView`` are now deprecated. Use
+ the ``notfound`` and ``forbidden`` ZCML directives instead (see the
+ "Hooks" chapter for more information). Such registrations will
+ continue to work, but the notfound and forbidden directives do
+ "extra work" to ensure that the callable named by the directive can
+ be called by the router even if it's a class or
+ request-argument-only view.
+
Removals
--------
@@ -136,6 +164,9 @@ Bug Fixes
Documentation
-------------
+- A "router" chapter explaining the request/response lifecycle at a
+ high level was added.
+
- Replaced all mentions and explanations of a routes "context factory"
with equivalent explanations of a "root factory" (context factories
have been disused).
diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml
index 83fb48e87..36129c5e3 100644
--- a/repoze/bfg/includes/meta.zcml
+++ b/repoze/bfg/includes/meta.zcml
@@ -16,6 +16,18 @@
handler="repoze.bfg.zcml.scan"
/>
+ <meta:directive
+ name="notfound"
+ schema="repoze.bfg.zcml.INotFoundViewDirective"
+ handler="repoze.bfg.zcml.notfound"
+ />
+
+ <meta:directive
+ name="forbidden"
+ schema="repoze.bfg.zcml.IForbiddenViewDirective"
+ handler="repoze.bfg.zcml.forbidden"
+ />
+
</meta:directives>
<meta:groupingDirective
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index d85f599b2..145e0ad7c 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -99,13 +99,13 @@ class Router(object):
warning = (
'Instead of registering a utility against the '
'repoze.bfg.interfaces.INotFoundAppFactory interface '
- 'to return a custom notfound response, you should register '
- 'a repoze.bfg.interfaces.INotFoundView. The '
+ 'to return a custom notfound response, you should use the '
+ '"notfound_view" ZCML directive. The '
'INotFoundAppFactory interface was deprecated in'
'repoze.bfg 0.9 and will be removed in a subsequent version '
'of repoze.bfg. See the "Hooks" chapter of the repoze.bfg '
'documentation for more information about '
- 'INotFoundView.')
+ 'the "notfound_view" directive.')
self.logger and self.logger.warn(warning)
def notfound(context, request):
app = notfound_app_factory()
diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py
index 195d1bba7..1049fa752 100644
--- a/repoze/bfg/tests/test_router.py
+++ b/repoze/bfg/tests/test_router.py
@@ -184,7 +184,7 @@ class RouterTests(unittest.TestCase):
self.registry.registerUtility(factory, INotFoundAppFactory)
router = self._makeOne()
self.assertEqual(len(logger.messages), 1)
- self.failUnless('INotFoundView' in logger.messages[0])
+ self.failUnless('notfound_view' in logger.messages[0])
class DummyRequest:
def get_response(self, app):
return app
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index d537cf581..7fe33e56b 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -520,6 +520,62 @@ class TestViewDirective(unittest.TestCase):
self.assertRaises(ConfigurationError, self._callFUT, context,
'repoze.view', None, view, '', None, 'foo')
+class TestNotFoundDirective(unittest.TestCase):
+ def _callFUT(self, context, view):
+ from repoze.bfg.zcml import notfound
+ return notfound(context, view)
+
+ def test_it(self):
+ context = DummyContext()
+ def view(request):
+ return 'OK'
+ self._callFUT(context, view)
+ actions = context.actions
+ from repoze.bfg.interfaces import INotFoundView
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 1)
+
+ regadapt = actions[0]
+ regadapt_discriminator = ('notfound_view',)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerUtility')
+ derived_view = regadapt['args'][1]
+ self.assertEqual(derived_view(None, None), 'OK')
+ self.assertEqual(derived_view.__name__, view.__name__)
+ self.assertEqual(regadapt['args'][2], INotFoundView)
+ self.assertEqual(regadapt['args'][3], '')
+ self.assertEqual(regadapt['args'][4], None)
+
+class TestForbiddenDirective(unittest.TestCase):
+ def _callFUT(self, context, view):
+ from repoze.bfg.zcml import forbidden
+ return forbidden(context, view)
+
+ def test_it(self):
+ context = DummyContext()
+ def view(request):
+ return 'OK'
+ self._callFUT(context, view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IForbiddenView
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 1)
+
+ regadapt = actions[0]
+ regadapt_discriminator = ('notfound_view',)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerUtility')
+ derived_view = regadapt['args'][1]
+ self.assertEqual(derived_view(None, None), 'OK')
+ self.assertEqual(derived_view.__name__, view.__name__)
+ self.assertEqual(regadapt['args'][2], IForbiddenView)
+ self.assertEqual(regadapt['args'][3], '')
+ self.assertEqual(regadapt['args'][4], None)
+
class TestDeriveView(unittest.TestCase):
def _callFUT(self, view):
from repoze.bfg.zcml import derive_view
@@ -787,7 +843,7 @@ class TestConnectRouteFunction(unittest.TestCase):
'conditions':{'sub_domain':['a', 'b']}
})
-class TestRoute(unittest.TestCase):
+class TestRouteDirective(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -978,7 +1034,7 @@ class TestBFGViewFunctionGrokker(unittest.TestCase):
actions = context.actions
self.assertEqual(len(actions), 0)
-class TestZCMLScanFunction(unittest.TestCase):
+class TestZCMLScanDirective(unittest.TestCase):
def setUp(self):
cleanUp()
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index e53a359da..1b66839d6 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -20,6 +20,8 @@ from zope.schema import TextLine
from repoze.bfg.interfaces import IRoutesMapper
from repoze.bfg.interfaces import IViewPermission
+from repoze.bfg.interfaces import INotFoundView
+from repoze.bfg.interfaces import IForbiddenView
from repoze.bfg.interfaces import IView
from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES
@@ -84,6 +86,20 @@ def view(
derived_view, (for_, request_type), IView, name, _context.info),
)
+def view_utility(_context, view, iface):
+ derived_view = derive_view(view)
+ _context.action(
+ discriminator = ('notfound_view',),
+ callable = handler,
+ args = ('registerUtility', derived_view, iface, '', _context.info),
+ )
+
+def notfound(_context, view):
+ view_utility(_context, view, INotFoundView)
+
+def forbidden(_context, view):
+ view_utility(_context, view, IForbiddenView)
+
def derive_view(view):
derived_view = view
if inspect.isclass(view):
@@ -316,6 +332,20 @@ class IViewDirective(Interface):
title = u'The route that must match for this view to be used',
required = False)
+class INotFoundViewDirective(Interface):
+ view = GlobalObject(
+ title=u"",
+ description=u"The notfound view callable",
+ required=True,
+ )
+
+class IForbiddenViewDirective(Interface):
+ view = GlobalObject(
+ title=u"",
+ description=u"The forbidden view callable",
+ required=True,
+ )
+
class IRouteRequirementDirective(Interface):
""" The interface for the ``requirement`` route subdirective """
attr = TextLine(title=u'attr', required=True)