summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt12
-rw-r--r--docs/narr/urldispatch.rst132
-rw-r--r--repoze/bfg/tests/test_urldispatch.py39
-rw-r--r--repoze/bfg/tests/test_zcml.py317
-rw-r--r--repoze/bfg/urldispatch.py13
-rw-r--r--repoze/bfg/zcml.py110
6 files changed, 411 insertions, 212 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 27467dcc7..6b5f56020 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -30,6 +30,18 @@ Features
dictionary-like methods in such a way that existing traverser code
which expects to be passed an environ will continue to work.
+- The ZCML ``route`` directive's attributes ``xhr``,
+ ``request_method``, ``path_info``, ``request_param``, ``header`` and
+ ``accept`` are now *route* predicates rather than *view* predicates.
+ If one or more of these predicates is specified in the route
+ configuration, all of the predicates must return true for the route
+ to match a request. If one or more of the route predicates
+ associated with a route returns ``False`` when checked during a
+ request, the route match fails, and the next match in the routelist
+ is tried. This differs from the previous behavior, where no route
+ predicates existed and all predicates were considered view
+ predicates, because in that scenario, the next route was not tried.
+
Documentation
-------------
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 26d4b4a93..968f1f0a6 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -85,6 +85,84 @@ view
view callable when this route matches.
e.g. ``mypackage.views.my_view``.
+xhr
+
+ Thie value should be either ``True`` or ``False``. If this value is
+ specified and is ``True``, the :term:`request` must possess an
+ ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this
+ route to match. This is useful for detecting AJAX requests issued
+ from jQuery, Prototype and other Javascript libraries. If this
+ predicate returns false, route matching continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+request_method
+
+ A string representing an HTTP method name, e.g. ``GET``, ``POST``,
+ ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified,
+ this route will match if the request has *any* request method. If
+ this predicate returns false, route matching continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+path_info
+
+ The value of this attribute represents a regular expression pattern
+ that will be tested against the ``PATH_INFO`` WSGI environment
+ variable. If the regex matches, this predicate will be true. If
+ this predicate returns false, route matching continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+request_param
+
+ This value can be any string. A view declaration with this
+ attribute ensures that the associated route will only match when the
+ request has a key in the ``request.params`` dictionary (an HTTP
+ ``GET`` or ``POST`` variable) that has a name which matches the
+ supplied value. If the value supplied to the attribute has a ``=``
+ sign in it, e.g. ``request_params="foo=123"``, then the key
+ (``foo``) must both exist in the ``request.params`` dictionary, and
+ the value must match the right hand side of the expression (``123``)
+ for the route to "match" the current request. If this predicate
+ returns false, route matching continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+header
+
+ The value of this attribute represents an HTTP header name or a
+ header name/value pair. If the value contains a ``:`` (colon), it
+ will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*``
+ or ``Host:localhost``). The *value* of an attribute that represent
+ a name/value pair should be a regular expression. If the value does
+ not contain a colon, the entire value will be considered to be the
+ header name (e.g. ``If-Modified-Since``). If the value evaluates to
+ a header name only without a value, the header specified by the name
+ must be present in the request for this predicate to be true. If
+ the value evaluates to a header name/value pair, the header
+ specified by the name must be present in the request *and* the
+ regular expression specified as the value must match the header
+ value. Whether or not the value represents a header name or a
+ header name/value pair, the case of the header name is not
+ significant. If this predicate returns false, route matching
+ continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
+accept
+
+ The value of this attribute represents a match query for one or more
+ mimetypes in the ``Accept`` HTTP request header. If this value is
+ specified, it must be in one of the following forms: a mimetype
+ match token in the form ``text/plain``, a wildcard mimetype match
+ token in the form ``text/*`` or a match-all wildcard mimetype match
+ token in the form ``*/*``. If any of the forms matches the
+ ``Accept`` header of the request, this predicate will be true. If
+ this predicate returns false, route matching continues.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
view_for
The Python dotted-path name to a class or an interface that the
@@ -112,18 +190,28 @@ view_permission
view_request_type
A dotted Python name to an interface representing a :term:`request
- type`. For backwards compatibility with :mod:`repoze.bfg` 1.0 and
- before, this may also be a string naming an HTTP ``REQUEST_METHOD``
- (any of ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT``). However,
- these values should really be specified in ``request_method``. If
- this argument is not specified, any request type will be considered
- a match for the view associated with this route.
+ type`. If this argument is not specified, any request type will be
+ considered a match for the view associated with this route.
If the ``view`` attribute is not provided, this attribute has no
effect.
This attribute can also be spelled as ``request_type``.
+view_containment
+
+ This value should be a Python dotted-path string representing the
+ class that a graph traversal parent object of the :term:`context`
+ must be an instance of (or :term:`interface` that a parent object
+ must provide) in order for this view to be found and called. Your
+ models must be "location-aware" to use this feature. See
+ :ref:`location_aware` for more information about location-awareness.
+
+ If the ``view`` attribute is not provided, this attribute has no
+ effect.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
+
view_request_method
A string representing an HTTP method name, e.g. ``GET``, ``POST``,
@@ -134,8 +222,6 @@ view_request_method
If the ``view`` attribute is not provided, this attribute has no
effect.
- This attribute can also be spelled as ``request_method``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
view_request_param
@@ -153,24 +239,6 @@ view_request_param
If the ``view`` attribute is not provided, this attribute has no
effect.
- This attribute can also be spelled as ``request_param``.
-
- .. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
-
-view_containment
-
- This value should be a Python dotted-path string representing the
- class that a graph traversal parent object of the :term:`context`
- must be an instance of (or :term:`interface` that a parent object
- must provide) in order for this view to be found and called. Your
- models must be "location-aware" to use this feature. See
- :ref:`location_aware` for more information about location-awareness.
-
- If the ``view`` attribute is not provided, this attribute has no
- effect.
-
- This attribute can also be spelled as ``containment``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
view_attr
@@ -188,8 +256,6 @@ view_attr
If the ``view`` attribute is not provided, this attribute has no
effect.
- This attribute can also be spelled as ``attr``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
view_renderer
@@ -211,8 +277,6 @@ view_renderer
If the ``view`` attribute is not provided, this attribute has no
effect.
- This attribute can also be spelled as ``renderer``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
view_xhr
@@ -224,7 +288,8 @@ view_xhr
requests issued from jQuery, Prototype and other Javascript
libraries.
- This attribute can also be spelled as ``xhr``.
+ If the ``view`` attribute is not provided, this attribute has no
+ effect.
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
@@ -238,8 +303,6 @@ view_accept
token in the form ``*/*``. If any of the forms matches the
``Accept`` header of the request, this predicate will be true.
- This attribute can also be spelled as ``accept``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
view_header
@@ -260,11 +323,8 @@ view_header
header name/value pair, the case of the header name is not
significant.
- This attribute can also be spelled as ``header``.
-
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
-
The Matchdict
-------------
diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py
index 99d5d1a98..c7dfdf196 100644
--- a/repoze/bfg/tests/test_urldispatch.py
+++ b/repoze/bfg/tests/test_urldispatch.py
@@ -105,6 +105,45 @@ class RoutesRootFactoryTests(unittest.TestCase):
self.assertEqual(request.matchdict, routing_args)
self.failUnless(req_iface.providedBy(request))
+ def test_route_matches_with_predicates(self):
+ root_factory = DummyRootFactory(123)
+ req_iface = self._registerRouteRequest('foo')
+ mapper = self._makeOne(root_factory)
+ mapper.connect('archives/:action/:article', 'foo',
+ predicates=[lambda *arg: True])
+ request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ result = mapper(request)
+ self.assertEqual(result, 123)
+ environ = request.environ
+ routing_args = environ['wsgiorg.routing_args'][1]
+ self.assertEqual(routing_args['action'], 'action1')
+ self.assertEqual(routing_args['article'], 'article1')
+ self.assertEqual(environ['bfg.routes.matchdict'], routing_args)
+ self.assertEqual(environ['bfg.routes.route'].name, 'foo')
+ self.assertEqual(request.matchdict, routing_args)
+ self.failUnless(req_iface.providedBy(request))
+
+ def test_route_fails_to_match_with_predicates(self):
+ root_factory = DummyRootFactory(123)
+ foo_iface = self._registerRouteRequest('foo')
+ bar_iface = self._registerRouteRequest('bar')
+ mapper = self._makeOne(root_factory)
+ mapper.connect('archives/:action/article1', 'foo',
+ predicates=[lambda *arg: True, lambda *arg: False])
+ mapper.connect('archives/:action/:article', 'bar')
+ request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ result = mapper(request)
+ self.assertEqual(result, 123)
+ environ = request.environ
+ routing_args = environ['wsgiorg.routing_args'][1]
+ self.assertEqual(routing_args['action'], 'action1')
+ self.assertEqual(routing_args['article'], 'article1')
+ self.assertEqual(environ['bfg.routes.matchdict'], routing_args)
+ self.assertEqual(environ['bfg.routes.route'].name, 'bar')
+ self.assertEqual(request.matchdict, routing_args)
+ self.failUnless(bar_iface.providedBy(request))
+ self.failIf(foo_iface.providedBy(request))
+
def test_root_route_matches(self):
root_factory = DummyRootFactory(123)
req_iface = self._registerRouteRequest('root')
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index 8106baa03..d534559f6 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -1542,9 +1542,9 @@ class TestConnectRouteFunction(unittest.TestCase):
def tearDown(self):
cleanUp()
- def _callFUT(self, path, name, factory):
+ def _callFUT(self, path, name, factory, predicates):
from repoze.bfg.zcml import connect_route
- return connect_route(path, name, factory)
+ return connect_route(path, name, factory, predicates)
def _registerRoutesMapper(self):
from zope.component import getSiteManager
@@ -1556,8 +1556,9 @@ class TestConnectRouteFunction(unittest.TestCase):
def test_defaults(self):
mapper = self._registerRoutesMapper()
- self._callFUT('path', 'name', 'factory')
- self.assertEqual(mapper.connections, [('path', 'name', 'factory')])
+ self._callFUT('path', 'name', 'factory', 'predicates')
+ self.assertEqual(mapper.connections, [('path', 'name', 'factory',
+ 'predicates')])
class TestRouteDirective(unittest.TestCase):
def setUp(self):
@@ -1582,10 +1583,12 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(
+ route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
def test_with_view(self):
from zope.interface import Interface
@@ -1622,10 +1625,12 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(
+ route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
def test_with_view_and_view_for(self):
from zope.component import getSiteManager
@@ -1658,10 +1663,11 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None,))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None,None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
def test_without_view(self):
from repoze.bfg.zcml import connect_route
@@ -1675,10 +1681,9 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args, ('path', 'name', None, []))
def test_with_view_request_type(self):
from zope.component import getSiteManager
@@ -1711,12 +1716,13 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None,None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_request_type_alias(self):
+ def test_with_view_request_method(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1725,7 +1731,8 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, request_type="GET")
+ self._callFUT(context, 'name', 'path', view=view,
+ view_request_method="GET")
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1746,12 +1753,13 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_request_method(self):
+ def test_with_view_containment(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1760,8 +1768,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view,
- view_request_method="GET")
+ self._callFUT(context, 'name', 'path', view=view, view_containment=True)
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1771,7 +1778,7 @@ class TestRouteDirective(unittest.TestCase):
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
- discrim = ('view', None, '', request_type, IView, None, None, 'GET',
+ discrim = ('view', None, '', request_type, IView, True, None, None,
'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
@@ -1782,12 +1789,13 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None,None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_request_method_alias(self):
+ def test_with_view_header(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1796,7 +1804,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, request_method="GET")
+ self._callFUT(context, 'name', 'path', view=view, view_header='Host')
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1805,10 +1813,45 @@ class TestRouteDirective(unittest.TestCase):
register()
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
+ view_discriminator = view_action['discriminator']
+ discrim = ('view', None, '', request_type, IView, None, None, None,
+ 'name', None, False, None, 'Host', None)
+ self.assertEqual(view_discriminator, discrim)
+ wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
+ self.failUnless(wrapped)
+
+ route_action = actions[1]
+ route_callable = route_action['callable']
+ route_discriminator = route_action['discriminator']
+ route_args = route_action['args']
+ self.assertEqual(route_callable, connect_route)
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None,None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
+ def test_with_view_path_info(self):
+ from zope.component import getSiteManager
+ from repoze.bfg.zcml import connect_route
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IRouteRequest
+
+ context = DummyContext()
+ def view(context, request):
+ """ """
+ self._callFUT(context, 'name', 'path', view=view, view_path_info='/foo')
+ actions = context.actions
+ self.assertEqual(len(actions), 2)
+
+ view_action = actions[0]
+ register = view_action['callable']
+ register()
+ sm = getSiteManager()
+ request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
- discrim = ('view', None, '', request_type, IView, None, None, 'GET',
- 'name', None, False, None, None, None)
+ discrim = ('view', None, '', request_type, IView, None, None, None,
+ 'name', None, False, None, None, '/foo')
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1818,12 +1861,13 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_containment(self):
+ def test_with_view_xhr(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1832,7 +1876,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, view_containment=True)
+ self._callFUT(context, 'name', 'path', view=view, view_xhr=True)
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1842,8 +1886,8 @@ class TestRouteDirective(unittest.TestCase):
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
- discrim = ('view', None, '', request_type, IView, True, None, None,
- 'name', None, False, None, None, None)
+ discrim = ('view', None, '', request_type, IView, None, None, None,
+ 'name', None, True, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1853,12 +1897,13 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_containment_alias(self):
+ def test_with_view_accept(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1867,7 +1912,8 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, containment=True)
+ self._callFUT(context, 'name', 'path', view=view,
+ view_accept='text/xml')
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1877,8 +1923,8 @@ class TestRouteDirective(unittest.TestCase):
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
- discrim = ('view', None, '', request_type, IView, True, None, None,
- 'name', None, False, None, None, None)
+ discrim = ('view', None, '', request_type, IView, None, None, None,
+ 'name', None, False, 'text/xml', None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1888,12 +1934,14 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(
+ route_discriminator,
+ ('route', 'name', False, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
- def test_with_view_header(self):
+ def test_with_request_type_GET(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1902,7 +1950,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, view_header='Host')
+ self._callFUT(context, 'name', 'path', view=view, request_type="GET")
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1912,8 +1960,8 @@ class TestRouteDirective(unittest.TestCase):
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
- discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, False, None, 'Host', None)
+ discrim = ('view', None, '', request_type, IView, None, None, 'GET',
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1923,12 +1971,15 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None, None, None,None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 0)
+
+ # route predicates
- def test_with_view_header_alias(self):
+ def test_with_xhr(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -1937,7 +1988,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, header='Host')
+ self._callFUT(context, 'name', 'path', view=view, xhr=True)
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1948,7 +1999,7 @@ class TestRouteDirective(unittest.TestCase):
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, False, None, 'Host', None)
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1958,21 +2009,25 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', True, None, None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.is_xhr = True
+ self.assertEqual(predicates[0](None, request), True)
- def test_with_view_path_info(self):
+ def test_with_request_method(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IRouteRequest
-
+
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, view_path_info='/foo')
+ self._callFUT(context, 'name', 'path', view=view, request_method="GET")
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -1981,9 +2036,10 @@ class TestRouteDirective(unittest.TestCase):
register()
sm = getSiteManager()
request_type = sm.getUtility(IRouteRequest, 'name')
+
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, False, None, None, '/foo')
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -1993,21 +2049,25 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, 'GET',None, None, None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.method = 'GET'
+ self.assertEqual(predicates[0](None, request), True)
- def test_with_view_xhr(self):
+ def test_with_path_info(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IRouteRequest
-
+
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, view_xhr=True)
+ self._callFUT(context, 'name', 'path', view=view, path_info='/foo')
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -2018,7 +2078,7 @@ class TestRouteDirective(unittest.TestCase):
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, True, None, None, None)
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -2028,12 +2088,16 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, '/foo',None,None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.path_info = '/foo'
+ self.assertEqual(predicates[0](None, request), True)
- def test_with_view_xhr_alias(self):
+ def test_with_request_param(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -2042,7 +2106,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view, xhr=True)
+ self._callFUT(context, 'name', 'path', view=view, request_param='abc')
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -2053,7 +2117,7 @@ class TestRouteDirective(unittest.TestCase):
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, True, None, None, None)
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -2063,12 +2127,16 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None,'abc', None, None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.params = {'abc':'123'}
+ self.assertEqual(predicates[0](None, request), True)
- def test_with_view_accept(self):
+ def test_with_header(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -2077,8 +2145,7 @@ class TestRouteDirective(unittest.TestCase):
context = DummyContext()
def view(context, request):
""" """
- self._callFUT(context, 'name', 'path', view=view,
- view_accept='text/xml')
+ self._callFUT(context, 'name', 'path', view=view, header='Host')
actions = context.actions
self.assertEqual(len(actions), 2)
@@ -2089,7 +2156,7 @@ class TestRouteDirective(unittest.TestCase):
request_type = sm.getUtility(IRouteRequest, 'name')
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, False, 'text/xml', None, None)
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -2099,12 +2166,16 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(route_discriminator,
+ ('route', 'name', False, None, None,None,'Host', None))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.headers = {'Host':'example.com'}
+ self.assertEqual(predicates[0](None, request), True)
- def test_with_view_accept_alias(self):
+ def test_with_accept(self):
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
from repoze.bfg.interfaces import IView
@@ -2125,7 +2196,7 @@ class TestRouteDirective(unittest.TestCase):
view_discriminator = view_action['discriminator']
discrim = ('view', None, '', request_type, IView, None, None, None,
- 'name', None, False, 'text/xml', None, None)
+ 'name', None, False, None, None, None)
self.assertEqual(view_discriminator, discrim)
wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='')
self.failUnless(wrapped)
@@ -2135,10 +2206,15 @@ class TestRouteDirective(unittest.TestCase):
route_discriminator = route_action['discriminator']
route_args = route_action['args']
self.assertEqual(route_callable, connect_route)
- self.assertEqual(len(route_discriminator), 2)
- self.assertEqual(route_discriminator[0], 'route')
- self.assertEqual(route_discriminator[1], 'name')
- self.assertEqual(route_args, ('path', 'name', None))
+ self.assertEqual(
+ route_discriminator,
+ ('route', 'name', False, None, None, None, None, 'text/xml'))
+ self.assertEqual(route_args[:3], ('path', 'name', None))
+ predicates = route_args[3]
+ self.assertEqual(len(predicates), 1)
+ request = DummyRequest()
+ request.accept = ['text/xml']
+ self.assertEqual(predicates[0](None, request), True)
class TestStaticDirective(unittest.TestCase):
def setUp(self):
@@ -2186,7 +2262,8 @@ class TestStaticDirective(unittest.TestCase):
discriminator = action['discriminator']
args = action['args']
self.assertEqual(callable, connect_route)
- self.assertEqual(discriminator, ('route', 'name'))
+ self.assertEqual(discriminator,
+ ('route', 'name', False, None, None, None, None, None))
self.assertEqual(args[0], 'name*subpath')
def test_package_relative(self):
@@ -2220,7 +2297,8 @@ class TestStaticDirective(unittest.TestCase):
discriminator = action['discriminator']
args = action['args']
self.assertEqual(callable, connect_route)
- self.assertEqual(discriminator, ('route', 'name'))
+ self.assertEqual(discriminator,
+ ('route', 'name', False, None, None, None, None, None))
self.assertEqual(args[0], 'name*subpath')
def test_here_relative(self):
@@ -2255,7 +2333,8 @@ class TestStaticDirective(unittest.TestCase):
discriminator = action['discriminator']
args = action['args']
self.assertEqual(callable, connect_route)
- self.assertEqual(discriminator, ('route', 'name'))
+ self.assertEqual(discriminator,
+ ('route', 'name', False, None, None, None, None, None))
self.assertEqual(args[0], 'name*subpath')
class TestResourceDirective(unittest.TestCase):
@@ -2564,8 +2643,8 @@ class DummyMapper:
def __init__(self):
self.connections = []
- def connect(self, *args):
- self.connections.append(args)
+ def connect(self, path, name, factory, predicates=()):
+ self.connections.append((path, name, factory, predicates))
class DummyRoute:
pass
diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py
index 4088cef12..185baa1a1 100644
--- a/repoze/bfg/urldispatch.py
+++ b/repoze/bfg/urldispatch.py
@@ -5,18 +5,20 @@ from zope.interface import directlyProvides
from repoze.bfg.interfaces import IRouteRequest
+from repoze.bfg.compat import all
+from repoze.bfg.encode import url_quote
from repoze.bfg.traversal import traversal_path
from repoze.bfg.traversal import quote_path_segment
-from repoze.bfg.encode import url_quote
_marker = object()
class Route(object):
- def __init__(self, path, name=None, factory=None):
+ def __init__(self, path, name=None, factory=None, predicates=()):
self.path = path
self.match, self.generate = _compile_route(path)
self.name = name
self.factory = factory
+ self.predicates = predicates
class RoutesRootFactory(object):
def __init__(self, default_root_factory):
@@ -30,8 +32,8 @@ class RoutesRootFactory(object):
def get_routes(self):
return self.routelist
- def connect(self, path, name, factory=None):
- route = Route(path, name, factory)
+ def connect(self, path, name, factory=None, predicates=()):
+ route = Route(path, name, factory, predicates)
self.routelist.append(route)
self.routes[name] = route
return route
@@ -69,6 +71,9 @@ class RoutesRootFactory(object):
for route in self.routelist:
match = route.match(path)
if match is not None:
+ preds = route.predicates
+ if preds and not all((p(None, request) for p in preds)):
+ continue
environ['wsgiorg.routing_args'] = ((), match)
environ['bfg.routes.route'] = route
environ['bfg.routes.matchdict'] = match
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index c39f06bb3..97af9112a 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -197,7 +197,7 @@ def _make_predicates(xhr=None, request_method=None, path_info=None,
except re.error, why:
raise ConfigurationError(why[0])
def path_info_predicate(context, request):
- return path_info_val.match(request.path_info)
+ return path_info_val.match(request.path_info) is not None
weight = weight - 30
predicates.append(path_info_predicate)
@@ -553,9 +553,19 @@ class IRouteDirective(Interface):
path = TextLine(title=u'path', required=True)
factory = GlobalObject(title=u'context factory', required=False)
view = GlobalObject(title=u'view', required=False)
+
view_for = GlobalObject(title=u'view_for', required=False)
+ # alias for view_for
+ for_ = GlobalObject(title=u'for', required=False)
+
view_permission = TextLine(title=u'view_permission', required=False)
+ # alias for view_permission
+ permission = TextLine(title=u'permission', required=False)
+
view_request_type = TextLine(title=u'view_request_type', required=False)
+ # alias for view_request_type
+ request_type = TextLine(title=u'request_type', required=False)
+
view_request_method = TextLine(title=u'view_request_method', required=False)
view_containment = GlobalObject(
title = u'Dotted name of a containment class or interface',
@@ -566,31 +576,12 @@ class IRouteDirective(Interface):
view_accept = TextLine(title=u'view_accept', required=False)
view_xhr = Bool(title=u'view_xhr', required=False)
view_path_info = TextLine(title=u'view_path_info', required=False)
- # alias for "view_for"
- for_ = GlobalObject(title=u'for', required=False)
- # alias for "view_permission"
- permission = TextLine(title=u'permission', required=False)
- # alias for "view_request_type"
- request_type = TextLine(title=u'request_type', required=False)
- # alias for "view_request_method"
+
request_method = TextLine(title=u'request_method', required=False)
- # alias for "view_request_param"
request_param = TextLine(title=u'request_param', required=False)
- # alias for "view_containment"
- containment = GlobalObject(
- title = u'Dotted name of a containment class or interface',
- required=False)
- # alias for "view_attr"
- attr = TextLine(title=u'attr', required=False)
- # alias for "view_renderer"
- renderer = TextLine(title=u'renderer', required=False)
- # alias for "view_header"
header = TextLine(title=u'header', required=False)
- # alias for "view_accept"
accept = TextLine(title=u'accept', required=False)
- # alias for "view_xhr"
xhr = Bool(title=u'xhr', required=False)
- # alias for "view_path_info"
path_info = TextLine(title=u'path_info', required=False)
class IRouteRequirementDirective(Interface):
@@ -601,59 +592,72 @@ class IRouteRequirementDirective(Interface):
def route(_context, name, path, view=None, view_for=None,
permission=None, factory=None, request_type=None, for_=None,
+ header=None, xhr=False, accept=None, path_info=None,
view_permission=None, view_request_type=None,
request_method=None, view_request_method=None,
- request_param=None, view_request_param=None, containment=None,
- view_containment=None, attr=None, view_attr=None, renderer=None,
- view_renderer=None, header=None, view_header=None, accept=None,
- view_accept=None, xhr=False, view_xhr=False,
- path_info=None, view_path_info=None):
+ request_param=None, view_request_param=None,
+ view_containment=None, view_attr=None,
+ view_renderer=None, view_header=None,
+ view_accept=None, view_xhr=False,
+ view_path_info=None):
""" Handle ``route`` ZCML directives
"""
# the strange ordering of the request kw args above is for b/w
# compatibility purposes.
- for_ = view_for or for_
- request_type = view_request_type or request_type
- permission = view_permission or permission
- request_method = view_request_method or request_method
- request_param = view_request_param or request_param
- containment = view_containment or containment
- attr = view_attr or attr
- renderer = view_renderer or renderer
- header = view_header or header
- accept = view_accept or accept
- xhr = view_xhr or xhr
- path_info = view_path_info or path_info
+ # these are route predicates; if they do not match, the next route
+ # in the routelist will be tried
+ _, predicates = _make_predicates(xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept)
sm = getSiteManager()
if request_type in ('GET', 'HEAD', 'PUT', 'POST', 'DELETE'):
# b/w compat for 1.0
- request_method = request_type
+ view_request_method = request_type
request_type = None
- if request_type is None:
- request_type = queryUtility(IRouteRequest, name=name)
- if request_type is None:
- request_type = route_request_iface(name)
- sm.registerUtility(request_type, IRouteRequest, name=name)
+ request_iface = queryUtility(IRouteRequest, name=name)
+ if request_iface is None:
+ request_iface = route_request_iface(name)
+ sm.registerUtility(request_iface, IRouteRequest, name=name)
if view:
- _view(_context, permission=permission, for_=for_, view=view, name='',
- request_type=request_type, route_name=name,
- request_method=request_method, request_param=request_param,
- containment=containment, attr=attr, renderer=renderer,
- header=header, accept=accept, xhr=xhr, path_info=path_info)
+ view_for = view_for or for_
+ view_request_type = view_request_type or request_type
+ view_permission = view_permission or permission
+ _view(
+ _context,
+ permission=view_permission,
+ for_=view_for,
+ view=view,
+ name='',
+ request_type=view_request_type,
+ route_name=name,
+ request_method=view_request_method,
+ request_param=view_request_param,
+ containment=view_containment,
+ attr=view_attr,
+ renderer=view_renderer,
+ header=view_header,
+ accept=view_accept,
+ xhr=view_xhr,
+ path_info=view_path_info,
+ )
_context.action(
- discriminator = ('route', name),
+ discriminator = ('route', name, xhr, request_method, path_info,
+ request_param, header, accept),
callable = connect_route,
- args = (path, name, factory),
+ args = (path, name, factory, predicates),
)
-def connect_route(path, name, factory):
+def connect_route(path, name, factory, predicates):
mapper = getUtility(IRoutesMapper)
- mapper.connect(path, name, factory)
+ mapper.connect(path, name, factory, predicates=predicates)
class IRendererDirective(Interface):
factory = GlobalObject(