diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-06-18 06:35:21 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-06-18 06:35:21 +0000 |
| commit | f8dbdee6167b3b4ab1ee4b2138a3e04e47a7c9df (patch) | |
| tree | 2c03802fe9b3ab56d8e8bd067e437d3f29b92006 | |
| parent | 95a9cfc326f0fcb3bdfce1efe5c25748a7f8f077 (diff) | |
| download | pyramid-f8dbdee6167b3b4ab1ee4b2138a3e04e47a7c9df.tar.gz pyramid-f8dbdee6167b3b4ab1ee4b2138a3e04e47a7c9df.tar.bz2 pyramid-f8dbdee6167b3b4ab1ee4b2138a3e04e47a7c9df.zip | |
- The matchdict related to the matching of a Routes route is available
on the request as the ``matchdict`` attribute:
``request.matchdict``. If no route matched, this attribute will be
None.
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/authorization.rst | 2 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/basiclayout.rst | 24 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/definingviews.rst | 63 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml | 10 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/authorization/tutorial/views.py | 9 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml | 2 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml | 2 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml | 2 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/src/views/tutorial/views.py | 9 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_traversal.py | 11 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 12 |
12 files changed, 96 insertions, 55 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 4ce3369a4..2851f61ad 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Next release Features -------- +- The matchdict related to the matching of a Routes route is available + on the request as the ``matchdict`` attribute: + ``request.matchdict``. If no route matched, this attribute will be + None. + - Make 404 responses slightly cheaper by showing ``environ["PATH_INFO"]`` on the notfound result page rather than the fullly computed URL. diff --git a/docs/tutorials/bfgwiki2/authorization.rst b/docs/tutorials/bfgwiki2/authorization.rst index 402e42f8d..1e1fce92c 100644 --- a/docs/tutorials/bfgwiki2/authorization.rst +++ b/docs/tutorials/bfgwiki2/authorization.rst @@ -1,3 +1,5 @@ +.. _wiki2_adding_authorization: + ==================== Adding Authorization ==================== diff --git a/docs/tutorials/bfgwiki2/basiclayout.rst b/docs/tutorials/bfgwiki2/basiclayout.rst index 98e8bb183..303622c7e 100644 --- a/docs/tutorials/bfgwiki2/basiclayout.rst +++ b/docs/tutorials/bfgwiki2/basiclayout.rst @@ -32,16 +32,24 @@ XML namespace. Our sample ZCML file looks like the following: SQLAlchemy connection after a request is finished. #. *Lines 9-12*. Register a ``<route>`` that will be used when the - URL is ``/``. Since this ``<view>`` has a blank ``name`` - attribute, it is the "default" view. ``.views.my_view`` refers to a + URL is ``/``. Since this ``<route>`` has an empty ``path`` + attribute, it is the "default" route. The attribute named ``view`` + with the value ``.views.my_view`` is the dotted name to a *function* we write (generated by the ``bfg_routesalchemy`` template) that is given a ``context`` and a ``request`` and returns - a response. - -#. *Lines 14-17*. Register a ``<route>`` with a path that starts with - ``/static``, capturing the rest of the URL as ``subpath``. This is - a view that will serve up static resources for us, in this case, at - ``http://localhost:6543/static/`` and below. + a response. You will use mostly ``<route>`` statements in a + :term:`URL dispatch` based application to map URLs to code. + +#. *Lines 14-17*. Register a ``<view>`` with a path that starts with + ``/static``. This points at a bit of code (``.views.static_view``) + that will serve up static resources for us, in this case, at + ``http://localhost:6543/static/`` and below. ``<view>`` + declarations also map code to URLs like ``route`` statements, + except they match URLs based on :term:`traversal`. With this view + declaration, we're saying that any URL that starts with ``/static`` + should go to the static view; any remainder of its path (e.g. the + ``/foo`` in ``/static/foo``) will be used to compose a path to a + static file resource (CSS and such). Content Models with ``models.py`` --------------------------------- diff --git a/docs/tutorials/bfgwiki2/definingviews.rst b/docs/tutorials/bfgwiki2/definingviews.rst index 4d90db11f..25f52291c 100644 --- a/docs/tutorials/bfgwiki2/definingviews.rst +++ b/docs/tutorials/bfgwiki2/definingviews.rst @@ -7,13 +7,20 @@ parameters: :term:`context`, and :term:`request`. A view is assumed to return a :term:`response` object. A invocation of a view that matches a URL via :term:`url dispatch` -passes as the context object an object which has attributes matching -the elements placed into the URL by the ``path`` of a ``route`` -statement. For instance, if a route statement in ``configure.zcml`` -had the path ``:one/:two``, and the URL at -``http://example.com/foo/bar`` was invoked, matching this path, the -context object passed to the view would have a ``one`` attribute withe -the value ``foo`` and a ``two`` attribute with the value ``bar``. +passes as the context object an object generated by a ``root +factory``. In this application, the context will always be generated +by the *default* root factory. Since we're not using traversal in +this application, this means the context will appear useless, at least +until we get to :ref:`wiki2_adding_authorization`. + +The request passed to every view that is called as the result of a +route match has an attribute named ``matchdict`` that contains the +elements placed into the URL by the ``path`` of a ``route`` statement. +For instance, if a route statement in ``configure.zcml`` had the path +``:one/:two``, and the URL at ``http://example.com/foo/bar`` was +invoked, matching this path, the matchdict dictionary attached to the +request passed to the view would have a ``one`` key withe the value +``foo`` and a ``two`` key with the value ``bar``. Declaring Dependencies in Our ``setup.py`` File =============================================== @@ -111,14 +118,14 @@ The ``add_page`` function will be invoked when a user clicks on a *WikiWord* which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. It also acts as a handler for the form that is generated -when we want to add a page object. The ``context`` of the -``add_page`` view will always have the attributes we need to -construct URLs and find model objects. +when we want to add a page object. The ``matchdict`` attribute of the +request passed to the ``add_page`` view will have the values we need +to construct URLs and find model objects. -The context object will have a ``pagename`` attribute that matches the -name of the page we'd like to add. If our add view is invoked via, +The matchdict will have a ``pagename`` key that matches the name of +the page we'd like to add. If our add view is invoked via, e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename`` -attribute of the context will be ``SomeName``. +value in the matchdict will be ``SomeName``. If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is False), the view @@ -133,7 +140,7 @@ response. If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is True), we scrape the page body from the form data, create a Page object using the name -in ``pagename`` context attribute and obtain the page body from the +in the matchdict ``pagename``, and obtain the page body from the request, and save it into the database using ``session.add``. We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page. @@ -143,9 +150,10 @@ The ``edit_page`` view function The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. It renders an edit form but -it also acts as the handler for the form it renders. The ``context`` -of the ``edit_page`` view will *always* have a ``pagename`` attribute -matching the name of the page the user wants to edit. +it also acts as the handler for the form it renders. The +``matchdict`` attribute of the request passed to the ``add_page`` view +will have a ``pagename`` key matching the name of the page the user +wants to edit. If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is False), the view @@ -155,9 +163,9 @@ and a save_url which will be used as the action of the generated form. If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is True), the view grabs the ``body`` element of the request parameter and sets it as the -``data`` attribute of the page context. It then redirects to the -default view of the context (the page), which will always be the -``view_page`` view. +``data`` key in the matchdict. It then redirects to the default view +of the context (the page), which will always be the ``view_page`` +view. Viewing the Result of Our Edits to ``views.py`` =============================================== @@ -234,14 +242,15 @@ number and type of static resources can be placed in this directory Mapping Views to URLs in ``configure.zcml`` =========================================== -The ``configure.zcml`` file contains ``route`` declarations which -serve to map URLs (via :term:`url dispatch`) to view functions. -First, we’ll get rid of the existing ``route`` created by the -template using the name ``home``. It’s only an example and isn’t -relevant to our application. We'll leave the static ``route`` -declaration as it is, since we are going to use it for the CSS. +The ``configure.zcml`` file contains ``route`` declarations (and a +lone ``view`` declaration) which serve to map URLs (via :term:`url +dispatch`) to view functions. First, we’ll get rid of the existing +``route`` created by the template using the name ``home``. It’s only +an example and isn’t relevant to our application. We'll leave the +static ``view`` declaration as it is, since we are going to use it to +serve CSS. -We then need to add four ``view`` declarations to ``configure.zcml``. +We then need to add four ``route`` declarations to ``configure.zcml``. Note that the *ordering* of these declarations is very important. ``route`` declarations are matched in the order they're found in the ``configure.zcml`` file. diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml index ff0125f83..fd8c8484c 100644 --- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml @@ -6,6 +6,11 @@ <subscriber for="repoze.bfg.interfaces.INewRequest" handler=".run.handle_teardown"/> + <view + name="static" + view=".views.static_view" + /> + <route path="login" name="login" @@ -18,11 +23,6 @@ view=".login.logout" /> - <route path="static/*subpath" - name="static" - view=".views.static_view" - /> - <route path="" name="view_wiki" diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/views.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/views.py index b38a9489e..4bcaae185 100644 --- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/views.py +++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/views.py @@ -22,8 +22,9 @@ def view_wiki(context, request): return HTTPFound(location = url_for('view_page', pagename='FrontPage')) def view_page(context, request): + pagename = request.matchdict['pagename'] session = DBSession() - page = session.query(Page).filter_by(name=context.pagename).one() + page = session.query(Page).filter_by(name=pagename).one() def check(match): word = match.group(1) @@ -37,7 +38,7 @@ def view_page(context, request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) - edit_url = url_for('edit_page', pagename=context.pagename) + edit_url = url_for('edit_page', pagename=pagename) logged_in = authenticated_userid(request) return render_template_to_response('templates/view.pt', request = request, @@ -47,7 +48,7 @@ def view_page(context, request): edit_url = edit_url) def add_page(context, request): - name = context.pagename + name = request.matchdict['pagename'] if 'form.submitted' in request.params: session = DBSession() body = request.params['body'] @@ -64,7 +65,7 @@ def add_page(context, request): save_url = save_url) def edit_page(context, request): - name = context.pagename + name = request.matchdict['pagename'] session = DBSession() page = session.query(Page).filter_by(name=name).one() if 'form.submitted' in request.params: diff --git a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml index 4858c47fd..4d1a16612 100644 --- a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml @@ -11,7 +11,7 @@ view=".views.my_view" /> - <route path="static/*subpath" + <view name="static" view=".views.static_view" /> diff --git a/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml index 4858c47fd..4d1a16612 100644 --- a/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml @@ -11,7 +11,7 @@ view=".views.my_view" /> - <route path="static/*subpath" + <view name="static" view=".views.static_view" /> diff --git a/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml index 70c3d4d9a..9d936c6d3 100644 --- a/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml @@ -6,7 +6,7 @@ <subscriber for="repoze.bfg.interfaces.INewRequest" handler=".run.handle_teardown"/> - <route path="static/*subpath" + <view name="static" view=".views.static_view" /> diff --git a/docs/tutorials/bfgwiki2/src/views/tutorial/views.py b/docs/tutorials/bfgwiki2/src/views/tutorial/views.py index c4e91dda1..f81fbf2af 100644 --- a/docs/tutorials/bfgwiki2/src/views/tutorial/views.py +++ b/docs/tutorials/bfgwiki2/src/views/tutorial/views.py @@ -21,8 +21,9 @@ def view_wiki(context, request): return HTTPFound(location = url_for('view_page', pagename='FrontPage')) def view_page(context, request): + matchdict = request.matchdict session = DBSession() - page = session.query(Page).filter_by(name=context.pagename).one() + page = session.query(Page).filter_by(name=matchdict['pagename']).one() def check(match): word = match.group(1) @@ -36,7 +37,7 @@ def view_page(context, request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) - edit_url = url_for('edit_page', pagename=context.pagename) + edit_url = url_for('edit_page', pagename=matchdict['pagename']) return render_template_to_response('templates/view.pt', request = request, page = page, @@ -44,7 +45,7 @@ def view_page(context, request): edit_url = edit_url) def add_page(context, request): - name = context.pagename + name = request.matchdict['pagename'] if 'form.submitted' in request.params: session = DBSession() body = request.params['body'] @@ -59,7 +60,7 @@ def add_page(context, request): save_url = save_url) def edit_page(context, request): - name = context.pagename + name = request.matchdict['pagename'] session = DBSession() page = session.query(Page).filter_by(name=name).one() if 'form.submitted' in request.params: diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index 00b3cc99f..a36cbb1dc 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -90,6 +90,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_pathel_with_no_getitem(self): policy = self._makeOne(None) @@ -101,6 +102,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_withconn_getitem_emptypath_nosubpath(self): root = DummyContext() @@ -113,6 +115,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_withconn_getitem_withpath_nosubpath(self): foo = DummyContext() @@ -126,6 +129,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], [u'foo']) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_withconn_getitem_withpath_withsubpath(self): foo = DummyContext() @@ -139,6 +143,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], [u'foo']) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_with_explicit_viewname(self): foo = DummyContext() @@ -152,6 +157,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], None) def test_call_with_vh_root(self): environ = self._getEnviron(PATH_INFO='/baz', @@ -172,6 +178,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], [u'foo', u'bar', u'baz']) self.assertEqual(result['virtual_root'], bar) self.assertEqual(result['virtual_root_path'], [u'foo', u'bar']) + self.assertEqual(result['matchdict'], None) def test_non_utf8_path_segment_unicode_path_segments_fails(self): foo = DummyContext() @@ -201,6 +208,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], model) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], {}) def test_withroute_with_subpath(self): model = DummyContext() @@ -213,6 +221,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], model) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], {'subpath':'/a/b/c'}) def test_withroute_with_path_info(self): model = DummyContext() @@ -228,6 +237,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], []) self.assertEqual(environ['PATH_INFO'], '/foo/bar') self.assertEqual(environ['SCRIPT_NAME'], '/a/b') + self.assertEqual(result['matchdict'], {'path_info':'foo/bar'}) def test_withroute_with_path_info_PATH_INFO_w_extra_slash(self): model = DummyContext() @@ -249,6 +259,7 @@ class ModelGraphTraverserTests(unittest.TestCase): self.assertEqual(result['traversed'], []) self.assertEqual(result['virtual_root'], model) self.assertEqual(result['virtual_root_path'], []) + self.assertEqual(result['matchdict'], {'traverse':'foo/bar'}) class FindInterfaceTests(unittest.TestCase): def _callFUT(self, context, iface): diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index 40f65901f..63fd71e74 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -493,6 +493,7 @@ class ModelGraphTraverser(object): self.root = root def __call__(self, environ, _marker=_marker): + matchdict = None if 'bfg.routes.matchdict' in environ: # this request matched a Routes route matchdict = environ['bfg.routes.matchdict'] @@ -541,19 +542,22 @@ class ModelGraphTraverser(object): if segment[:2] =='@@': return dict(context=ob, view_name=segment[2:], subpath=path[i:], traversed=traversed, virtual_root=vroot, - virtual_root_path=vroot_path, root=self.root) + virtual_root_path=vroot_path, root=self.root, + matchdict=matchdict) try: getitem = ob.__getitem__ except AttributeError: return dict(context=ob, view_name=segment, subpath=path[i:], traversed=traversed, virtual_root=vroot, - virtual_root_path=vroot_path, root=self.root) + virtual_root_path=vroot_path, root=self.root, + matchdict=matchdict) try: next = getitem(segment) except KeyError: return dict(context=ob, view_name=segment, subpath=path[i:], traversed=traversed, virtual_root=vroot, - virtual_root_path=vroot_path, root=self.root) + virtual_root_path=vroot_path, root=self.root, + matchdict=matchdict) if vroot_idx == i-1: vroot = ob traversed.append(segment) @@ -563,7 +567,7 @@ class ModelGraphTraverser(object): return dict(context=ob, view_name=u'', subpath=subpath, traversed=traversed, virtual_root=vroot, virtual_root_path=vroot_path, - root=self.root) + root=self.root, matchdict=matchdict) class TraversalContextURL(object): """ The IContextURL adapter used to generate URLs for a context |
