diff options
| -rw-r--r-- | CHANGES.txt | 12 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 132 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 39 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 317 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 13 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 110 |
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( |
