diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-05-16 18:48:02 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-05-16 18:48:02 +0000 |
| commit | 29e01279ff0b13623a6b1b769351632f12bafb35 (patch) | |
| tree | b53e44b99df1f4fdf5f3437e7228b53652e53fc0 | |
| parent | dfad1fbd9fa46c67aee8de3c3d3b36c0af1ec7bf (diff) | |
| download | pyramid-29e01279ff0b13623a6b1b769351632f12bafb35.tar.gz pyramid-29e01279ff0b13623a6b1b769351632f12bafb35.tar.bz2 pyramid-29e01279ff0b13623a6b1b769351632f12bafb35.zip | |
- The ``RoutesMapper`` class in ``repoze.bfg.urldispatch`` has been
removed, as well as its documentation. It had been deprecated since
0.6.3. Code in ``repoze.bfg.urldispatch.RoutesModelTraverser``
which catered to it has also been removed.
- The semantics of the ``route`` ZCML directive have been simplified.
Previously, it was assumed that to use a route, you wanted to map a
route to an externally registered view. The new ``route`` directive
instead has a ``view`` attribute which is required, specifying the
dotted path to a view callable. When a route directive is
processed, a view is *registered* using the name attribute of the
route directive as its name and the callable as its value. The
``view_name`` and ``provides`` attributes of the ``route`` directive
are therefore no longer used. Effectively, if you were previously
using the ``route`` directive, it means you must change a pair of
ZCML directives that look like this::
<route
name="home"
path=""
view_name="login"
factory=".models.root.Root"
/>
<view
for=".models.root.Root"
name="login"
view=".views.login_view"
/>
To a ZCML directive that looks like this::
<route
name="home"
path=""
view=".views.login_view"
factory=".models.root.Root"
/>
In other words, to make old code work, remove the ``view``
directives that were only there to serve the purpose of backing
``route`` directives, and move their ``view=`` attribute into the
``route`` directive itself.
This change also necessitated that the ``name`` attribute of the
``route`` directive is now required. If you were previously using
``route`` directives without a ``name`` attribute, you'll need to
add one (the name is arbitrary, but must be unique among all
``route`` and ``view`` statements).
The ``provides`` attribute of the ``route`` directive has also been
removed. This directive specified a sequence of interface types
that the generated context would be decorated with. Since route
views are always generated now for a single interface
(``repoze.bfg.IRoutesContext``) as opposed to being looked up
arbitrarily, there is no need to decorate any context to ensure a
view is found.
- The Routes ``Route`` object used to resolve the match is now put
into the environment as ``bfg.route`` when URL dispatch is used.
| -rw-r--r-- | CHANGES.txt | 64 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 247 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 2 | ||||
| -rw-r--r-- | repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml | 14 | ||||
| -rw-r--r-- | repoze/bfg/tests/routesapp/configure.zcml | 13 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 212 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 128 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 114 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 91 |
9 files changed, 381 insertions, 504 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index a72220217..0a7610a7c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,67 @@ 0.8dev ====== +Backwards Incompatibilities +--------------------------- + +- The ``RoutesMapper`` class in ``repoze.bfg.urldispatch`` has been + removed, as well as its documentation. It had been deprecated since + 0.6.3. Code in ``repoze.bfg.urldispatch.RoutesModelTraverser`` + which catered to it has also been removed. + +- The semantics of the ``route`` ZCML directive have been simplified. + Previously, it was assumed that to use a route, you wanted to map a + route to an externally registered view. The new ``route`` directive + instead has a ``view`` attribute which is required, specifying the + dotted path to a view callable. When a route directive is + processed, a view is *registered* using the name attribute of the + route directive as its name and the callable as its value. The + ``view_name`` and ``provides`` attributes of the ``route`` directive + are therefore no longer used. Effectively, if you were previously + using the ``route`` directive, it means you must change a pair of + ZCML directives that look like this:: + + <route + name="home" + path="" + view_name="login" + factory=".models.root.Root" + /> + + <view + for=".models.root.Root" + name="login" + view=".views.login_view" + /> + + To a ZCML directive that looks like this:: + + <route + name="home" + path="" + view=".views.login_view" + factory=".models.root.Root" + /> + + In other words, to make old code work, remove the ``view`` + directives that were only there to serve the purpose of backing + ``route`` directives, and move their ``view=`` attribute into the + ``route`` directive itself. + + This change also necessitated that the ``name`` attribute of the + ``route`` directive is now required. If you were previously using + ``route`` directives without a ``name`` attribute, you'll need to + add one (the name is arbitrary, but must be unique among all + ``route`` and ``view`` statements). + + The ``provides`` attribute of the ``route`` directive has also been + removed. This directive specified a sequence of interface types + that the generated context would be decorated with. Since route + views are always generated now for a single interface + (``repoze.bfg.IRoutesContext``) as opposed to being looked up + arbitrarily, there is no need to decorate any context to ensure a + view is found. + Documentation ------------- @@ -26,6 +87,9 @@ Features Routes areused). This template can be used via ``paster create -t bfg_alchemy``. +- The Routes ``Route`` object used to resolve the match is now put + into the environment as ``bfg.route`` when URL dispatch is used. + 0.8a6 (2009-05-11) ================== diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 5bb81350c..028c184db 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -9,19 +9,15 @@ also map URLs to code via :term:`URL dispatch` using the :term:`Routes` framework. The :term:`Routes` framework is a Python reimplementation of the `Rails routes system <http://manuals.rubyonrails.com/read/chapter/65>`_. It is a mechanism -which allows you to declaratively map URLs to code. Both traversal -and URL dispatch have the same goal: to find the context and the view -name. +which allows you to declaratively map URLs to code. .. note:: In common :term:`Routes` lingo, the code that it maps URLs to is defined by a *controller* and an *action*. However, neither concept (controller nor action) exists within :mod:`repoze.bfg`. Instead, when you map a URL pattern to - code in bfg, you will map the URL patterm to a - :term:`context` and a :term:`view name`. Once the context - and view name are found, the same :term:`view` lookup which - is detailed in :ref:`traversal_chapter` will be done using - the context and view name found via a route. + code in bfg, you will map the URL patterm to a :term:`view`. + Once the context and view name are found, the view will be + called with a :term:`context` and a :term:`request`. It often makes a lot of sense to use :term:`URL dispatch` instead of :term:`traversal` in an application that has no natural hierarchy. @@ -51,19 +47,20 @@ allows you to inject ``route`` ZCML directives into your application's :mod:`repoze.bfg`. When any ``route`` ZCML directive is present in an application's -``configure.zcml``, "under the hood" :mod:`repoze.bfg` wraps the "root -factory" in a special ``RoutesRootFactory`` instance. The wrapper -instance then acts as the root factory. When it acts as a root -factory, it is willing to check the requested URL against a *routes -map* to find the :term:`context` and the :term:`view name` before -traversal has a chance to find it first. If it finds a context and a -view name via a route, :mod:`repoze.bfg` will attempt to look up and -call a :mod:`repoze.bfg` :term:`view` that matches the context and the -view name. If no route matches, :mod:`repoze.bfg` will fail over to -calling the root factory callable passed to the application in it's -``make_app`` function (usually a traversal function). By configuring -your ZCML ``route`` statements appropriately, you can mix and match -URL dispatch and traversal in this way. +``configure.zcml``, "under the hood" :mod:`repoze.bfg` wraps the +:term:`root factory` in a special ``RoutesRootFactory`` instance. The +wrapper instance then acts as the effective root factory. When it +acts as a root factory, it is willing to check the requested URL +against a *routes map* to find a :term:`context` and a :term:`view` +before traversal has a chance to find it first. If a route matches, a +:term:`context` is generated and :mod:`repoze.bfg` will call the +:term:`view` specified with the context and the request. + +If no route matches, :mod:`repoze.bfg` will fail over to calling the +root factory callable passed to the application in it's ``make_app`` +function (usually a traversal function). By configuring your ZCML +``route`` statements appropriately, you can mix and match URL dispatch +and traversal in this way. A root factory is not required for purely URL-dispatch-based apps: if the root factory callable is ``None``, :mod:`repoze.bfg` will return a @@ -88,25 +85,27 @@ name The `route name <http://routes.groovie.org/manual.html#route-name>`_, - e.g. ``myroute``. + e.g. ``myroute``. This attribute is required. -view_name +view - The :mod:`repoze.bfg` :term:`view name` that should be looked up - when this route matches a URL. + The Python dotted-path name to a function that will be used as a + view callable when this route matches. + e.g. ``mypackage.views.my_view``. This attribute is required. -factory +permission - The Python dotted-path name to a function that will generate a - :mod:`repoze.bfg` context object when this route matches. By - default, a ``repoze.bfg.urldispatch.DefaultRoutesContext`` object - will be constructed if a factory is not provided. + The permission name required to invoke the view. + e.g. ``edit``. (see :ref:`using_security_with_urldispatch` for more + information about permissions). -provides +factory - One or more Python-dotted path names to :term:`interface` objects - that the context should be decorated with when it's constructed - (allowing it to be found by a particular view lookup). + The Python dotted-path name to a function that will generate a + :mod:`repoze.bfg` context object when this route matches. + e.g. ``mypackage.models.MyFactoryClass``. By default, a + ``repoze.bfg.urldispatch.DefaultRoutesContext`` object will be + constructed if a factory is not provided. encoding @@ -179,39 +178,85 @@ that allows you to specify Routes `requirement For example: .. code-block:: xml + :linenos: - <route path="archives/:year/:month"> + <route + name="archive" + path="archives/:year/:month" + view=".views.archive_view"> <requirement attr="year" - expr="d{2,4}"/> + expr="d{2,4}" + /> <requirement attr="month" - expr="d{1,2}"/> + expr="d{1,2}" + /> </route> Example 1 --------- -Below is an example of some route statements you might add to your -``configure.zcml``: +The simplest route delcaration: .. code-block:: xml :linenos: <route + name="idea" + path="hello.html" + view="mypackage.views.hello_view" + /> + +When the URL matches ``/hello.html``, the view callable at the Python +dotted path name ``mypackage.views.hello_view`` will be called with a +default context object and the request. See :ref:`views_chapter` for +more information about views. + +The ``mypackage.views`` module referred to above might look like so: + +.. code-block:: python + :linenos: + + from webob import Response + + def hello_view(context, request): + return Response('Hello!') + +In this case the context object passed to the view will be an instance +of the ``repoze.bfg.urldispatch.DefaultRoutesContext``. This is the +type of obejct created for a context when there is no "factory" +specified in the ``route`` declaration. + +Example 2 +--------- + +Below is an example of some more complicated route statements you +might add to your ``configure.zcml``: + +.. code-block:: xml + :linenos: + + <route + name="idea" path="ideas/:idea" - view_name="ideas"/> + view="mypackage.views.idea_view" + /> <route + name="user" path="users/:user" - view_name="users"/> + view="mypackage.views.user_view" + /> - <route + <route + name="tag" path="tags/:tag" - view_name="tags"/> + view="mypackage.views.tag_view" + /> The above configuration will allow :mod:`repoze.bfg` to service URLs in these forms: @@ -224,10 +269,12 @@ in these forms: /tags/<tagname> When a URL matches the pattern ``/ideas/<ideaname>``, the view -registered with the name ``ideas`` for the interface -``repoze.bfg.interfaces.IRoutesContext`` will be called. An error -will be raised if no view can be found with that interface type and -view name combination. +registered with the name ``idea`` will be called. This will be the +view available at the dotted Python pathname +``mypackage.views.idea_view``. + +Example 3 +--------- The context object passed to a view found as the result of URL dispatch will by default be an instance of the @@ -243,46 +290,31 @@ An example of using a route with a factory: :linenos: <route + name="idea" path="ideas/:idea" + view=".views.idea_view" factory=".models.Idea" - view_name="ideas"/> + /> The above route will manufacture an ``Idea`` model as a context, -assuming that ``.models.Idea`` resolves to a class that accepts -arbitrary key/value pair arguments. +assuming that ``mypackage.models.Idea`` resolves to a class that +accepts arbitrary key/value pair arguments. .. note:: Values prefixed with a period (``.``) for the ``factory`` and ``provides`` attributes of a ``route`` (such as - ``.models.Idea`` above) mean "relative to the Python package - directory in which this :term:`ZCML` file is stored". So if the - above ``route`` declaration was made inside a ``configure.zcml`` - file that lived in the ``hello`` package, you could replace the - relative ``.models.Idea`` with the absolute ``hello.models.Idea`` - Either the relative or absolute form is functionally equivalent. - It's often useful to use the relative form, in case your package's - name changes. It's also shorter to type. + ``.models.Idea`` and ``.views.idea_view``) above) mean "relative to + the Python package directory in which this :term:`ZCML` file is + stored". So if the above ``route`` declaration was made inside a + ``configure.zcml`` file that lived in the ``hello`` package, you + could replace the relative ``.models.Idea`` with the absolute + ``hello.models.Idea`` Either the relative or absolute form is + functionally equivalent. It's often useful to use the relative + form, in case your package's name changes. It's also shorter to + type. All context objects manufactured via URL dispatch will be decorated by default with the ``repoze.bfg.interfaces.IRoutesContext`` -:term:`interface`. To decorate a context found via a route with other -interfaces, you can use a ``provides`` attribute on the ZCML -statement. It should be a space-separated list of dotted Python names -that point at interface definitions. - -An example of using a route with a set of ``provides`` interfaces: - -.. code-block:: xml - :linenos: - - <route - path="ideas/:idea" - provides=".interfaces.IIdea .interfaces.IContent" - view_name="ideas"/> - -The above route will manufacture an instance of -``DefaultRoutesContext`` as a context; it will be decorate with the -``.interfaces.IIdea`` and ``.interfaces.IContent`` interfaces, as long -as those dotted names resolve to interfaces. +:term:`interface`. If no route matches in the above configuration, :mod:`repoze.bfg` will call the "fallback" ``get_root`` callable provided to it during @@ -294,7 +326,7 @@ error will be raised when no route matches. context. You can also map classes to views; interfaces are not used then. -Example 2 +Example 4 --------- An example of configuring a ``view`` declaration in ``configure.zcml`` @@ -304,24 +336,14 @@ function is as follows: .. code-block:: xml :linenos: - <view - for=".interfaces.ISomeContext" - view=".views.articles_view" - name="articles" - /> - <route + name="article" path="archives/:article" - view_name="articles" + view=".views.article_view" factory=".models.Article" - provides=".interfaces.ISomeContext" /> -All context objects found via Routes URL dispatch will provide the -``IRoutesContext`` interface (attached dynamically). The above -``route`` statement will also cause contexts generated by the route to -have the ``.interfaces.ISomeContext`` interface as well. The -``.models`` modulemight look like so: +The ``.models`` module referred to above might look like so: .. code-block:: python :linenos: @@ -330,6 +352,16 @@ have the ``.interfaces.ISomeContext`` interface as well. The def __init__(self, **kw): self.__dict__.update(kw) +The ``.views`` module referred to above might look like so: + +.. code-block:: python + :linenos: + + from webob import Response + + def article_view(context, request): + return Response('Article with name' % context.article) + The effect of this configuration: when this :mod:`repoze.bfg` application runs, if any URL matches the pattern ``archives/:article``, the ``.views.articles_view`` view will be @@ -343,29 +375,6 @@ In this case in particular, when a user visits Article class and it will have an ``article`` attribute with the value of ``something``. -Example 3 ---------- - -You can also make the ``view_name`` into a routes path argument -instead of specifying it as an argument: - -.. code-block:: xml - :linenos: - - <view - for="repoze.bfg.interfaces.IRoutesContext" - view=".views.articles_view" - name="articles" - /> - - <route - path="archives/:view_name" - /> - -When you do this, the :term:`view name` will be computed dynamically if -the route matches. In the above example, if the ``view_name`` turns -out to be ``articles``, the articles view will eventually be called. - Catching the Root URL --------------------- @@ -379,7 +388,7 @@ declaration: <route path="" name="root" - view_name="root_view" + view=".views.root_view" /> Cleaning Up After a Request @@ -425,6 +434,8 @@ Then in the ``configure.zcml`` of your package, inject the following: This will cause the DBSession to be removed whenever the WSGI environment is destroyed (usually at the end of every request). +.. _using_security_with_urldispatch: + Using :mod:`repoze.bfg` Security With URL Dispatch -------------------------------------------------- @@ -471,9 +482,3 @@ the ``Article`` class' constructor, too. :term:`Routes` manual for a general overview of what the ``condition`` argument to ``.connect`` does. -Further Documentation and Examples ----------------------------------- - -The API documentation in :ref:`urldispatch_module` documents an older -(now-deprecated) version of Routes support in :mod:`repoze.bfg`. - diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index f9a0a17e5..dbbfd78a5 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -29,7 +29,7 @@ class IDELETERequest(IRequest): class IHEADRequest(IRequest): """ Request type interface attached to HEAD requests""" - + class IResponseFactory(Interface): """ A utility which generates a response factory """ def __call__(): diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml index aca4566bc..c297209f4 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml @@ -8,17 +8,13 @@ <route path="" name="home" + view=".views.my_view" factory=".models.get_root_model" /> - <view - for=".models.Model" - view=".views.my_view" - /> - - <view - view=".views.static_view" - name="static" - /> + <route path="static/*subpath" + name="static" + view=".views.static_view" + /> </configure> diff --git a/repoze/bfg/tests/routesapp/configure.zcml b/repoze/bfg/tests/routesapp/configure.zcml index 17653615a..01062b6d4 100644 --- a/repoze/bfg/tests/routesapp/configure.zcml +++ b/repoze/bfg/tests/routesapp/configure.zcml @@ -4,14 +4,9 @@ <route path=":id" - provides=".models.IFixture" - view_name="default"/> - - <view - name="default" - view=".views.fixture_view" - for=".models.IFixture" - permission="repoze.view" - /> + name="default" + view=".views.fixture_view" + permission="repoze.view" + /> </configure> diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index e08e98ca7..e1640bfdf 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -1,80 +1,5 @@ import unittest -class RoutesMapperTests(unittest.TestCase): - def setUp(self): - from zope.deprecation import __show__ - __show__.off() - - def tearDown(self): - from zope.deprecation import __show__ - __show__.on() - - def _getEnviron(self, **kw): - environ = {'SERVER_NAME':'localhost', - 'wsgi.url_scheme':'http'} - environ.update(kw) - return environ - - def _getTargetClass(self): - from repoze.bfg.urldispatch import RoutesMapper - return RoutesMapper - - def _makeOne(self, get_root): - klass = self._getTargetClass() - return klass(get_root) - - def test_routes_mapper_no_route_matches(self): - marker = () - get_root = make_get_root(marker) - mapper = self._makeOne(get_root) - environ = self._getEnviron(PATH_INFO='/') - result = mapper(environ) - self.assertEqual(result, marker) - self.assertEqual(mapper.mapper.environ, environ) - - def test_routes_mapper_route_matches(self): - marker = () - get_root = make_get_root(marker) - mapper = self._makeOne(get_root) - mapper.connect('archives/:action/:article', controller='foo') - environ = self._getEnviron(PATH_INFO='/archives/action1/article1') - result = mapper(environ) - from repoze.bfg.interfaces import IRoutesContext - self.failUnless(IRoutesContext.providedBy(result)) - self.assertEqual(result.controller, 'foo') - self.assertEqual(result.action, 'action1') - self.assertEqual(result.article, 'article1') - - def test_routes_mapper_custom_context_factory(self): - marker = () - get_root = make_get_root(marker) - mapper = self._makeOne(get_root) - class Dummy(object): - def __init__(self, **kw): - self.__dict__.update(kw) - mapper.connect('archives/:action/:article', controller='foo', - context_factory=Dummy) - environ = self._getEnviron(PATH_INFO='/archives/action1/article1') - result = mapper(environ) - self.assertEqual(result.controller, 'foo') - self.assertEqual(result.action, 'action1') - self.assertEqual(result.article, 'article1') - from repoze.bfg.interfaces import IRoutesContext - self.failUnless(IRoutesContext.providedBy(result)) - self.failUnless(isinstance(result, Dummy)) - self.failIf(hasattr(result, 'context_factory')) - - def test_url_for(self): - marker = () - get_root = make_get_root(marker) - mapper = self._makeOne(get_root) - mapper.connect('archives/:action/:article', controller='foo') - environ = self._getEnviron(PATH_INFO='/archives/action1/article1') - result = mapper(environ) - from routes import url_for - result = url_for(controller='foo', action='action2', article='article2') - self.assertEqual(result, '/archives/action2/article2') - class RoutesRootFactoryTests(unittest.TestCase): def _getEnviron(self, **kw): environ = {'SERVER_NAME':'localhost', @@ -103,18 +28,19 @@ class RoutesRootFactoryTests(unittest.TestCase): marker = () get_root = make_get_root(marker) mapper = self._makeOne(get_root) - mapper.connect('archives/:action/:article', view_name='foo') + mapper.connect('foo', 'archives/:action/:article', foo='foo') environ = self._getEnviron(PATH_INFO='/archives/action1/article1') result = mapper(environ) from repoze.bfg.interfaces import IRoutesContext self.failUnless(IRoutesContext.providedBy(result)) - self.assertEqual(result.view_name, 'foo') + self.assertEqual(result.foo, 'foo') self.assertEqual(result.action, 'action1') self.assertEqual(result.article, 'article1') routing_args = environ['wsgiorg.routing_args'][1] - self.assertEqual(routing_args['view_name'], 'foo') + self.assertEqual(routing_args['foo'], 'foo') self.assertEqual(routing_args['action'], 'action1') self.assertEqual(routing_args['article'], 'article1') + self.assertEqual(environ['bfg.route'].name, 'foo') def test_unnamed_root_route_matches(self): mapper = self._makeOne(None) @@ -123,18 +49,28 @@ class RoutesRootFactoryTests(unittest.TestCase): result = mapper(environ) from repoze.bfg.interfaces import IRoutesContext self.failUnless(IRoutesContext.providedBy(result)) + self.assertEqual(environ['bfg.route'].name, None) + + def test_named_root_route_matches(self): + mapper = self._makeOne(None) + mapper.connect('root', '') + environ = self._getEnviron(PATH_INFO='/') + result = mapper(environ) + from repoze.bfg.interfaces import IRoutesContext + self.failUnless(IRoutesContext.providedBy(result)) + self.assertEqual(environ['bfg.route'].name, 'root') def test_unicode_in_route_default(self): marker = () get_root = make_get_root(marker) mapper = self._makeOne(get_root) - class DummyRoute: + class DummyRoute2: routepath = ':id' _factory = None _provides = () la = unicode('\xc3\xb1a', 'utf-8') - mapper.routematch = lambda *arg: ({la:'id'}, DummyRoute) - mapper.connect(':la') + mapper.routematch = lambda *arg: ({la:'id'}, DummyRoute2) + mapper.connect('whatever', ':la') environ = self._getEnviron(PATH_INFO='/foo') result = mapper(environ) from repoze.bfg.interfaces import IRoutesContext @@ -146,7 +82,7 @@ class RoutesRootFactoryTests(unittest.TestCase): def test_no_fallback_get_root(self): marker = () mapper = self._makeOne(None) - mapper.connect('wont/:be/:found', view_name='foo') + mapper.connect('wont', 'wont/:be/:found') environ = self._getEnviron(PATH_INFO='/archives/action1/article1') result = mapper(environ) from repoze.bfg.urldispatch import RoutesContextNotFound @@ -163,11 +99,10 @@ class RoutesRootFactoryTests(unittest.TestCase): implements(IDummy) def __init__(self, **kw): self.__dict__.update(kw) - mapper.connect('archives/:action/:article', view_name='foo', + mapper.connect('article', 'archives/:action/:article', _factory=Dummy) environ = self._getEnviron(PATH_INFO='/archives/action1/article1') result = mapper(environ) - self.assertEqual(result.view_name, 'foo') self.assertEqual(result.action, 'action1') self.assertEqual(result.article, 'article1') from repoze.bfg.interfaces import IRoutesContext @@ -176,40 +111,23 @@ class RoutesRootFactoryTests(unittest.TestCase): self.failUnless(IDummy.providedBy(result)) self.failIf(hasattr(result, '_factory')) - def test_custom_provides(self): - marker = () - get_root = make_get_root(marker) - mapper = self._makeOne(get_root) - from zope.interface import Interface - class IDummy(Interface): - pass - mapper.connect('archives/:action/:article', view_name='foo', - _provides = [IDummy]) - environ = self._getEnviron(PATH_INFO='/archives/action1/article1') - result = mapper(environ) - self.assertEqual(result.view_name, 'foo') - self.assertEqual(result.action, 'action1') - self.assertEqual(result.article, 'article1') - from repoze.bfg.interfaces import IRoutesContext - self.failUnless(IRoutesContext.providedBy(result)) - self.failUnless(IDummy.providedBy(result)) - self.failIf(hasattr(result, '_provides')) - def test_has_routes(self): mapper = self._makeOne(None) self.assertEqual(mapper.has_routes(), False) - mapper.connect('archives/:action/:article', view_name='foo') + mapper.connect('whatever', 'archives/:action/:article') self.assertEqual(mapper.has_routes(), True) def test_url_for(self): marker = () get_root = make_get_root(marker) mapper = self._makeOne(get_root) - mapper.connect('archives/:action/:article', view_name='foo') + mapper.connect('whatever', 'archives/:action/:article') environ = self._getEnviron(PATH_INFO='/archives/action1/article1') + route = DummyRoute('yo') + environ['bfg.route'] = route result = mapper(environ) from routes import url_for - result = url_for(view_name='foo', action='action2', article='article2') + result = url_for(action='action2', article='article2') self.assertEqual(result, '/archives/action2/article2') class TestRoutesContextNotFound(unittest.TestCase): @@ -248,75 +166,29 @@ class RoutesModelTraverserTests(unittest.TestCase): from repoze.bfg.interfaces import ITraverser verifyObject(ITraverser, self._makeOne(None)) - def test_call_with_only_controller_bwcompat(self): - model = DummyContext() - model.controller = 'controller' - traverser = self._makeOne(model) - result = traverser({}) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'controller') - self.assertEqual(result[2], []) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) - - def test_call_with_only_view_name_bwcompat(self): - model = DummyContext() - model.view_name = 'view_name' - traverser = self._makeOne(model) - result = traverser({}) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'view_name') - self.assertEqual(result[2], []) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) - - def test_call_with_subpath_bwcompat(self): - model = DummyContext() - model.view_name = 'view_name' - model.subpath = '/a/b/c' - traverser = self._makeOne(model) - result = traverser({}) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'view_name') - self.assertEqual(result[2], ['a', 'b', 'c']) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) - - def test_call_with_no_view_name_or_controller_bwcompat(self): + def test_it_nothingfancy(self): model = DummyContext() traverser = self._makeOne(model) - result = traverser({}) - self.assertEqual(result[0], model) - self.assertEqual(result[1], '') - self.assertEqual(result[2], []) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) - - def test_call_with_only_view_name(self): - model = DummyContext() - traverser = self._makeOne(model) - routing_args = ((), {'view_name':'view_name'}) - environ = {'wsgiorg.routing_args': routing_args} + routing_args = ((), {}) + route = DummyRoute('yo') + environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route} result = traverser(environ) self.assertEqual(result[0], model) - self.assertEqual(result[1], 'view_name') + self.assertEqual(result[1], 'yo') self.assertEqual(result[2], []) self.assertEqual(result[3], None) self.assertEqual(result[4], model) self.assertEqual(result[5], None) - def test_call_with_view_name_and_subpath(self): + def test_call_with_subpath(self): model = DummyContext() traverser = self._makeOne(model) - routing_args = ((), {'view_name':'view_name', 'subpath':'/a/b/c'}) - environ = {'wsgiorg.routing_args': routing_args} + routing_args = ((), {'subpath':'/a/b/c'}) + route = DummyRoute('yo') + environ = {'wsgiorg.routing_args':routing_args, 'bfg.route': route} result = traverser(environ) self.assertEqual(result[0], model) - self.assertEqual(result[1], 'view_name') + self.assertEqual(result[1], 'yo') self.assertEqual(result[2], ['a', 'b','c']) self.assertEqual(result[3], None) self.assertEqual(result[4], model) @@ -325,12 +197,13 @@ class RoutesModelTraverserTests(unittest.TestCase): def test_with_path_info(self): model = DummyContext() traverser = self._makeOne(model) - routing_args = ((), {'view_name':'view_name', 'path_info':'foo/bar'}) - environ = {'wsgiorg.routing_args': routing_args, + routing_args = ((), {'path_info':'foo/bar'}) + route = DummyRoute('yo') + environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route, 'PATH_INFO':'/a/b/foo/bar', 'SCRIPT_NAME':''} result = traverser(environ) self.assertEqual(result[0], model) - self.assertEqual(result[1], 'view_name') + self.assertEqual(result[1], 'yo') self.assertEqual(result[2], []) self.assertEqual(result[3], None) self.assertEqual(result[4], model) @@ -341,8 +214,9 @@ class RoutesModelTraverserTests(unittest.TestCase): def test_with_path_info_PATH_INFO_w_extra_slash(self): model = DummyContext() traverser = self._makeOne(model) - routing_args = ((), {'view_name':'view_name', 'path_info':'foo/bar'}) - environ = {'wsgiorg.routing_args': routing_args, + routing_args = ((), {'path_info':'foo/bar'}) + route = DummyRoute('yo') + environ = {'wsgiorg.routing_args': routing_args, 'bfg.route':route, 'PATH_INFO':'/a/b//foo/bar', 'SCRIPT_NAME':''} result = traverser(environ) self.assertEqual(environ['PATH_INFO'], '/foo/bar') @@ -400,3 +274,7 @@ class DummyContext(object): class DummyRequest(object): """ """ +class DummyRoute(object): + def __init__(self, name): + self.name = name + diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 0d7f1b631..1853483ba 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -269,15 +269,15 @@ class TestConnectRouteFunction(unittest.TestCase): directive = DummyRouteDirective() self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) + self.assertEqual(mapper.connections[0][0], ('name', 'path')) self.assertEqual(mapper.connections[0][1], {'requirements': {}}) def test_name_and_path(self): mapper = self._registerRoutesMapper() - directive = DummyRouteDirective(name='abc') + directive = DummyRouteDirective(name='abc', path='thepath') self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('abc', 'a/b/c',)) + self.assertEqual(mapper.connections[0][0], ('abc', 'thepath',)) self.assertEqual(mapper.connections[0][1], {'requirements': {}}) def test_all_directives(self): @@ -290,28 +290,27 @@ class TestConnectRouteFunction(unittest.TestCase): parent_member_name='p', parent_collection_name='c', condition_method='GET', condition_subdomain=True, condition_function=foo, subdomains=['a'], - factory=foo, provides=[IDummy], view_name='def') + name='thename', path='thepath', + factory=foo, view='view', permission='permission') self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) + self.assertEqual(mapper.connections[0][0], ('thename', 'thepath')) pr = {'member_name':'p', 'collection_name':'c'} c = {'method':'GET', 'sub_domain':['a'], 'function':foo} - self.assertEqual(mapper.connections[0][1], - {'requirements': {}, - '_minimize':True, - '_explicit':True, - '_encoding':'utf-8', - '_static':True, - '_filter':foo, - '_absolute':True, - '_member_name':'m', - '_collection_name':'c', - '_parent_resource':pr, - 'conditions':c, - '_factory':foo, - '_provides':[IDummy], - 'view_name':'def', - }) + D = mapper.connections[0][1] + + self.assertEqual(D['requirements'], {}) + self.assertEqual(D['_minimize'],True) + self.assertEqual(D['_explicit'],True) + self.assertEqual(D['_encoding'],'utf-8') + self.assertEqual(D['_static'],True) + self.assertEqual(D['_filter'],foo) + self.assertEqual(D['_absolute'],True) + self.assertEqual(D['_member_name'], 'm') + self.assertEqual(D['_collection_name'], 'c') + self.assertEqual(D['_parent_resource'], pr) + self.assertEqual(D['conditions'], c) + self.assertEqual(D['_factory'], foo) def test_condition_subdomain_true(self): mapper = self._registerRoutesMapper() @@ -319,7 +318,6 @@ class TestConnectRouteFunction(unittest.TestCase): condition_subdomain=True) self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) self.assertEqual(mapper.connections[0][1], {'requirements': {}, '_static':True, @@ -335,7 +333,6 @@ class TestConnectRouteFunction(unittest.TestCase): condition_function=foo) self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) self.assertEqual(mapper.connections[0][1], {'requirements': {}, '_static':True, @@ -349,7 +346,6 @@ class TestConnectRouteFunction(unittest.TestCase): condition_method='GET') self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) self.assertEqual(mapper.connections[0][1], {'requirements': {}, '_static':True, @@ -359,11 +355,12 @@ class TestConnectRouteFunction(unittest.TestCase): def test_subdomains(self): mapper = self._registerRoutesMapper() - directive = DummyRouteDirective(static=True, explicit=True, + directive = DummyRouteDirective(name='name', + static=True, explicit=True, subdomains=['a', 'b']) self._callFUT(directive) self.assertEqual(len(mapper.connections), 1) - self.assertEqual(mapper.connections[0][0], ('a/b/c',)) + self.assertEqual(mapper.connections[0][0], ('name', 'path')) self.assertEqual(mapper.connections[0][1], {'requirements': {}, '_static':True, @@ -371,7 +368,7 @@ class TestConnectRouteFunction(unittest.TestCase): 'conditions':{'sub_domain':['a', 'b']} }) -class TestRouteGroupingContextDecorator(unittest.TestCase): +class TestRoute(unittest.TestCase): def setUp(self): cleanUp() @@ -382,33 +379,79 @@ class TestRouteGroupingContextDecorator(unittest.TestCase): from repoze.bfg.zcml import Route return Route - def _makeOne(self, context, path, **kw): - return self._getTargetClass()(context, path, **kw) + def _makeOne(self, context, path, name, view, **kw): + return self._getTargetClass()(context, path, name, view, **kw) def test_defaults(self): context = DummyContext() - route = self._makeOne(context, 'abc') + view = Dummy() + route = self._makeOne(context, 'path', 'name', view) + self.assertEqual(route.path, 'path') + self.assertEqual(route.name, 'name') + self.assertEqual(route.view, view) self.assertEqual(route.requirements, {}) - self.assertEqual(route.parent_member_name, None) - self.assertEqual(route.parent_collection_name, None) def test_parent_collection_name_missing(self): context = DummyContext() - self.assertRaises(ValueError, self._makeOne, context, 'abc', + view = Dummy() + from zope.configuration.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, self._makeOne, context, + 'path', 'name', view, parent_member_name='a') def test_parent_collection_name_present(self): context = DummyContext() - route = self._makeOne(context, 'abc', + view = Dummy() + route = self._makeOne(context, 'path', 'name', view, parent_member_name='a', parent_collection_name='p') self.assertEqual(route.parent_member_name, 'a') self.assertEqual(route.parent_collection_name, 'p') - def test_explicit_view_name(self): + def test_after(self): + from repoze.bfg.zcml import handler + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IRoutesContext + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + context = DummyContext() - route = self._makeOne(context, 'abc', view_name='def') - self.assertEqual(route.view_name, 'def') + view = Dummy() + route = self._makeOne(context, 'path', 'name', view) + route.after() + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + view_callable = view_action['callable'] + view_discriminator = view_action['discriminator'] + view_args = view_action['args'] + self.assertEqual(view_callable, handler) + self.assertEqual(len(view_discriminator), 6) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], IRoutesContext) + self.assertEqual(view_discriminator[2],'name') + self.assertEqual(view_discriminator[3], IRequest) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], True) + self.assertEqual(view_args, ('registerAdapter', view, + (IRoutesContext, IRequest), IView, + 'name', None)) + + 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(len(route_discriminator), 7) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'path') + self.assertEqual(route_discriminator[2],'{}') + self.assertEqual(route_discriminator[3], None) + self.assertEqual(route_discriminator[4], None) + self.assertEqual(route_discriminator[5], None) + self.assertEqual(route_discriminator[6], None) + self.assertEqual(route_args, (route,)) class TestZCMLPickling(unittest.TestCase): i = 0 @@ -760,6 +803,11 @@ class Dummy: pass class DummyRouteDirective: + path = 'path' + name = 'name' + view = None + factory = None + permission = None encoding = None static = False minimize = False @@ -775,11 +823,6 @@ class DummyRouteDirective: condition_subdomain = None condition_function = None subdomains = None - path = 'a/b/c' - name = None - view_name = '' - factory = None - provides = () def __init__(self, **kw): if not 'requirements' in kw: kw['requirements'] = {} @@ -796,4 +839,3 @@ from zope.interface import Interface class IDummy(Interface): pass - diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index ead8876de..2b6717891 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -31,85 +31,6 @@ deprecated('RoutesContext', "DefaultRoutesContext')", ) -class RoutesMapper(object): - """ The ``RoutesMapper`` is a wrapper for the ``get_root`` - callable passed in to the repoze.bfg ``Router`` at initialization - time. When it is instantiated, it wraps the get_root of an - application in such a way that the `Routes - <http://routes.groovie.org/index.html>`_ engine has the 'first - crack' at resolving the current request URL to a repoze.bfg view. - Any view that claims it is 'for' the interface - ``repoze.bfg.interfaces.IRoutesContext`` will be called if its - *name* matches the Routes 'controller' name for the match. It - will be passed a context object that has attributes that match the - Routes match arguments dictionary keys. If no Routes route - matches the current request, the 'fallback' get_root is called. - - .. warning:: This class is deprecated. As of :mod:`repoze.bfg` - 0.6.3, you should use the ``<route.. >`` ZCML directive instead - of manually creating a RoutesMapper. See :ref:`urldispatch_chapter` - for more information. - """ - def __init__(self, get_root): - self.get_root = get_root - self.mapper = Mapper(controller_scan=None, directory=None, - explicit=True, always_scan=False) - self.mapper.explicit = True - self._regs_created = False - - def __call__(self, environ): - if not self._regs_created: - self.mapper.create_regs([]) - self._regs_created = True - path = environ.get('PATH_INFO', '/') - self.mapper.environ = environ - args = self.mapper.match(path) - if isinstance(args, dict): # might be an empty dict - context_factory = args.get('context_factory', _marker) - if context_factory is _marker: - context_factory = DefaultRoutesContext - else: - args = args.copy() - del args['context_factory'] - config = request_config() - config.mapper = self.mapper - config.mapper_dict = args - config.host = environ.get('HTTP_HOST', environ['SERVER_NAME']) - config.protocol = environ['wsgi.url_scheme'] - config.redirect = None - context = context_factory(**args) - alsoProvides(context, IRoutesContext) - return context - - # fall back to original get_root - return self.get_root(environ) - - def connect(self, *arg, **kw): - """ Add a route to the Routes mapper associated with this - request. This method accepts the same arguments as a Routes - *Mapper* object. One differences exists: if the - ``context_factory`` is passed in with a value as a keyword - argument, this callable will be called when a model object - representing the ``context`` for the request needs to be - constructed. It will be called with the (all-keyword) - arguments supplied by the Routes mapper's ``match`` method for - this route, and should return an instance of a class. If - ``context_factory`` is not supplied in this way for a route, a - default context factory (the ``DefaultRoutesContext`` class) - will be used. The interface - ``repoze.bfg.interfaces.IRoutesContext`` will always be tacked - on to the context instance in addition to whatever interfaces - the context instance already supplies. - """ - - self.mapper.connect(*arg, **kw) - -deprecated('RoutesMapper', - 'Usage of the ``RoutesMapper`` class is deprecated. As of ' - 'repoze.bfg 0.6.3, you should use the ``<route.. >`` ZCML ' - 'directive instead of manually creating a RoutesMapper.', - ) - class RoutesContextNotFound(object): implements(IContextNotFound) def __init__(self, msg): @@ -172,7 +93,7 @@ class RoutesRootFactory(Mapper): args = args.copy() routepath = route.routepath factory = route._factory - if not factory: + if factory is None: factory = DefaultRoutesContext config = request_config() config.mapper = self @@ -189,9 +110,7 @@ class RoutesRootFactory(Mapper): kw[k] = v context = factory(**kw) environ['wsgiorg.routing_args'] = ((), kw) - provides = route._provides - for iface in provides: - alsoProvides(context, iface) + environ['bfg.route'] = route alsoProvides(context, IRoutesContext) return context @@ -207,29 +126,12 @@ class RoutesModelTraverser(object): self.context = context def __call__(self, environ): - # the traverser *wants* to get routing args from the environ - # as of 0.6.5; the rest of this stuff is for backwards - # compatibility - try: - # 0.6.5 + - match = environ['wsgiorg.routing_args'][1] - except KeyError: - # <= 0.6.4 - match = self.context.__dict__ - try: - view_name = match['view_name'] - except KeyError: - # b/w compat < 0.6.3 - try: - view_name = match['controller'] - except KeyError: - view_name = '' - try: - subpath = match['subpath'] + route = environ['bfg.route'] + match = environ['wsgiorg.routing_args'][1] + + subpath = match.get('subpath', []) + if subpath: subpath = filter(None, subpath.split('/')) - except KeyError: - # b/w compat < 0.6.5 - subpath = [] if 'path_info' in match: # this is stolen from routes.middleware; if the route map @@ -245,7 +147,7 @@ class RoutesModelTraverser(object): if environ['SCRIPT_NAME'].endswith('/'): environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1] - return self.context, view_name, subpath, None, self.context, None + return self.context, route.name, subpath, None, self.context, None class RoutesContextURL(object): """ The IContextURL adapter used to generate URLs for a context diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 61b6e3738..16e6d7012 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -24,8 +24,10 @@ from zope.schema import TextLine from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IRoutesMapper +from repoze.bfg.interfaces import IRoutesContext from repoze.bfg.interfaces import IViewPermission from repoze.bfg.interfaces import IView + from repoze.bfg.path import package_path from repoze.bfg.security import ViewPermissionFactory @@ -271,12 +273,11 @@ def route_requirement(context, attr, expr): class IRouteDirective(Interface): """ The interface for the ``route`` ZCML directive """ + name = TextLine(title=u'name', required=True) path = TextLine(title=u'path', required=True) - name = TextLine(title=u'name', required=False) - view_name = TextLine(title=u'view_name', required=False) + view = GlobalObject(title=u'view', required=True) + permission = TextLine(title=u'permission', required=False) factory = GlobalObject(title=u'context factory', required=False) - provides = Tokens(title=u'context interfaces', required=False, - value_type=GlobalObject()) minimize = Bool(title=u'minimize', required=False) encoding = TextLine(title=u'encoding', required=False) static = Bool(title=u'static', required=False) @@ -299,13 +300,8 @@ def connect_route(directive): mapper = queryUtility(IRoutesMapper) if mapper is None: return - args = [] - if directive.name: - args.append(directive.name) - args.append(directive.path) + args = [directive.name, directive.path] kw = dict(requirements=directive.requirements) - if directive.view_name: - kw['view_name'] = directive.view_name if directive.minimize: kw['_minimize'] = True if directive.explicit: @@ -341,57 +337,55 @@ def connect_route(directive): if directive.factory: kw['_factory'] = directive.factory - if directive.provides: - kw['_provides'] = directive.provides return mapper.connect(*args, **kw) class Route(zope.configuration.config.GroupingContextDecorator): """ Handle ``route`` ZCML directives """ - - implements(zope.configuration.config.IConfigurationContext,IRouteDirective) - - def __init__(self, context, path, name=None, view_name='', factory=None, - provides=(), minimize=True, encoding=None, static=False, - filter=None, absolute=False, member_name=None, - collection_name=None, condition_method=None, - condition_subdomain=None, condition_function=None, - parent_member_name=None, parent_collection_name=None, - subdomains=None, explicit=False): + view = None + permission = None + factory = None + minimize = True + encoding = None + static = False + filter = None + absolute = False + member_name = None + collection_name = None + condition_method = None + condition_subdomain = None + condition_function = None + parent_member_name = None + parent_collection_name = None + subdomains = None + explicit = False + + implements(zope.configuration.config.IConfigurationContext, + IRouteDirective) + + def __init__(self, context, path, name, view, **kw): + self.validate(**kw) + self.requirements = {} # mutated by subdirectives self.context = context self.path = path self.name = name - self.view_name = view_name - self.factory = factory - self.provides = provides - self.minimize = minimize - self.encoding = encoding - self.static = static - self.filter = filter - self.absolute = absolute - self.member_name = member_name - self.collection_name = collection_name - self.condition_method= condition_method - self.condition_subdomain = condition_subdomain - self.condition_function = condition_function - self.explicit = explicit - self.subdomains = subdomains - if parent_member_name is not None: - if parent_collection_name is not None: - self.parent_member_name = parent_member_name - self.parent_collection_name = parent_collection_name - else: - raise ValueError( + self.view = view + self.__dict__.update(**kw) + + def validate(self, **kw): + parent_member_name = kw.get('parent_member_name') + parent_collection_name = kw.get('parent_collection_name') + if parent_member_name or parent_collection_name: + if not (parent_member_name and parent_collection_name): + raise ConfigurationError( 'parent_member_name and parent_collection_name must be ' 'specified together') - else: - self.parent_member_name = None - self.parent_collection_name = None - # added by subdirectives - self.requirements = {} def after(self): + view(self.context, self.permission, IRoutesContext, self.view, + self.name, None) + self.context.action( discriminator = ('route', self.path, repr(self.requirements), self.condition_method, self.condition_subdomain, @@ -399,3 +393,4 @@ class Route(zope.configuration.config.GroupingContextDecorator): callable = connect_route, args = (self,), ) + |
