summaryrefslogtreecommitdiff
path: root/docs/narr/urldispatch.rst
blob: 304152052f58d291a0e32cb132e415398d48ba4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
.. _urldispatch_chapter:

URL Dispatch
============

It is common for :mod:`repoze.bfg` developers to rely on
:term:`traversal` to map URLs to code.  However, :mod:`repoze.bfg` can
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.

.. 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.

It often makes a lot of sense to use :term:`URL dispatch` instead of
:term:`traversal` in an application that has no natural hierarchy.
For instance, if all the data in your application lives in a
relational database, and that relational database has no
self-referencing tables that form a natural hierarchy, URL dispatch is
easier to use than traversal, and is often a more natural fit for
creating an application that maniplates "flat" data.

Concept and Usage
-----------------

The URL dispatch features of :mod:`repoze.bfg` allow you to either
augment or replace :term:`traversal`, allowing URL dispatch to have
the "first crack" (and potentially the *only* crack) at resolving a
given URL to :term:`context` and :term:`view name`.  

To allow for URL dispatch to be used, the :mod:`repoze.bfg` framework
allows you to inject ``route`` ZCML directives into your application's
``configure.zcml`` file.

.. note:: Each ZCML ``route`` statement equates to a call to the
          :term:`Routes` ``Mapper`` object's ``connect`` method.  See
          `Setting up routes
          <http://routes.groovie.org/manual.html#setting-up-routes>`_
          for examples of using a Routes ``Mapper`` object outside of
          :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" (aka ``get_root``) in a special ``RoutesRootFactory``
instance.  This 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 ``get_root`` 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.

.. note:: See :ref:`modelspy_project_section` for an example of a
          simple ``get_root`` callable that will use traversal.

Example 1
---------

Below is an example of some route statements you might add to your
``configure.zcml``: 

.. code-block:: xml
   :linenos:

   <route
    path="ideas/:idea"
    view_name="ideas"/>

   <route
    path="users/:user"
    view_name="users"/>

   <route
    path="tags/:tag"
    view_name="tags"/>

The above configuration will allow :mod:`repoze.bfg` to service URLs
in these forms:

.. code-block:: bash
   :linenos:

   /ideas/<ideaname>
   /users/<username>
   /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.

The context object passed to a view found as the result of URL
dispatch will by default be an instance of the
``repoze.bfg.urldispatch.DefaultRoutesContext`` object.  You can
override this behavior by passing in a ``factory`` argument to the
ZCML directive for a particular route.  The ``factory`` should be a
callable that accepts arbitrary keyword arguments and returns an
instance of a class that will be the context used by the view.

An example of using a route with a factory:

.. code-block:: xml
   :linenos:

   <route
    path="ideas/:idea"
    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.

.. 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.

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.

If no route matches in the above configuration, :mod:`repoze.bfg` will
call the "fallback" ``get_root`` callable provided to it during
``make_app`.  If the "fallback" ``get_root`` is None, a ``NotFound``
error will be raised when no route matches.

.. note:: See :ref:`using_model_interfaces` for more information about
          how views are found when interfaces are attached to a
          context.  You can also map classes to views; interfaces are
          not used then.

Example 2
---------

An example of configuring a ``view`` declaration in ``configure.zcml``
that maps a context found via :term:`Routes` URL dispatch to a view
function is as follows:

.. code-block:: xml
   :linenos:

   <view
       for=".interfaces.ISomeContext"
       view=".views.articles_view"
       name="articles"
       />

   <route
      path="archives/:article"
      view_name="articles"
      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:

.. code-block:: python
   :linenos:

   class Article(object):
       def __init__(self, **kw):
           self.__dict__.update(kw)

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
called with its :term:`context` as a instance of the ``Article``
class.  The ``Article`` instance will have attributes matching the
keys and values in the Routes routing dictionary associated with the
request.

In this case in particular, when a user visits
``/archives/something``, the context will be an instance of the
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.

Using :mod:`repoze.bfg` Security With URL Dispatch
--------------------------------------------------

:mod:`repoze.bfg` provides its own security framework which consults a
:term:`security policy` before allowing any application code to be
called.  This framework operates in terms of ACLs (Access Control
Lists, see :ref:`security_chapter` for more information about the
:mod:`repoze.bfg` security subsystem).  A common thing to want to do
is to attach an ``__acl__`` to the context object dynamically for
declarative security purposes.  You can use the ``factory``
argument that points at a context factory which attaches a custom
``__acl__`` to an object at its creation time.

Such a ``factory`` might look like so:

.. code-block:: python
   :linenos:

   class Article(object):
       def __init__(self, **kw):
           self.__dict__.update(kw)

   def article_factory(**kw):
       model = Article(**kw)
       article = kw.get('article', None)
       if article == '1':
           model.__acl__ = [ (Allow, 'editor', 'view') ]
       return model

If the route ``archives/:article`` is matched, and the article number
is ``1``, :mod:`repoze.bfg` will generate an ``Article``
:term:`context` with an ACL on it that allows the ``editor`` principal
the ``view`` permission.  Obviously you can do more generic things
that inspect the routes match dict to see if the ``article`` argument
matches a particular string; our sample ``article_factory`` function
is not very ambitious.  Its job could have just as well been done in
the ``Article`` class' constructor, too.

.. note:: See :ref:`security_chapter` for more information about
   :mod:`repoze.bfg` security and ACLs.

.. note:: See `Conditions
   <http://routes.groovie.org/manual.html#conditions>`_ in the
   :term:`Routes` manual for a general overview of what the
   ``condition`` argument to ``.connect`` does.

Further Documentation and Examples
----------------------------------

URL-dispatch related API documentation is available in
:ref:`urldispatch_module` .