diff options
| -rw-r--r-- | TODO.txt | 4 | ||||
| -rw-r--r-- | docs/glossary.rst | 17 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | docs/latexindex.rst | 1 | ||||
| -rw-r--r-- | docs/narr/configuration.rst | 4 | ||||
| -rw-r--r-- | docs/narr/firstapp.rst | 349 | ||||
| -rw-r--r-- | docs/narr/router.rst | 2 | ||||
| -rw-r--r-- | docs/narr/security.rst | 29 | ||||
| -rw-r--r-- | docs/narr/static.rst | 225 | ||||
| -rw-r--r-- | docs/narr/traversal.rst | 167 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 649 | ||||
| -rw-r--r-- | docs/narr/urlmapping.rst | 92 | ||||
| -rw-r--r-- | docs/narr/views.rst | 1648 | ||||
| -rw-r--r-- | docs/narr/webob.rst | 87 | ||||
| -rw-r--r-- | docs/whatsnew-1.1.rst | 10 | ||||
| -rw-r--r-- | docs/zcml.rst | 19 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 6 |
17 files changed, 1774 insertions, 1536 deletions
@@ -4,10 +4,6 @@ - Review: - [ ] Traversal - - [ ] URL dispatch - [ ] Combining Traversal and URL Dispatch [ ] Views diff --git a/docs/glossary.rst b/docs/glossary.rst index ef1ad8144..e2a941571 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -636,3 +636,20 @@ Glossary "Python"; :term:`Jython`, Google's App Engine, and `PyPy <http://codespeak.net/pypy/dist/pypy/doc/>`_ are examples of non-C based Python implementations. + + View Lookup + The act of finding and invoking the "best" :term:`view callable` + given a :term:`request`, a :term:`context`, and a :term:`view + name`. + + Context Finding + The act of locating a :term:`context` and a :term:`view name` + given a :term:`request`. :term:`Traversal` and :term:`URL + dispatch` are the context finding subsystems used by + :mod:`repoze.bfg`. + + Triad + The three bits of information used by :term:`view lookup` to find + "the best" view callable for a given circumstance: a + :term:`context` type, a :term:`view name` and a :term:`request`. + diff --git a/docs/index.rst b/docs/index.rst index 28b2667a1..dfdfbfe18 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,6 +45,7 @@ Narrative documentation in chapter form explaining how to use narr/startup narr/urlmapping narr/views + narr/static narr/webob narr/templates narr/models diff --git a/docs/latexindex.rst b/docs/latexindex.rst index e6d271724..a350a5257 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -32,6 +32,7 @@ Narrative Documentation narr/project narr/urlmapping narr/views + narr/static narr/webob narr/templates narr/models diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 0b0301c79..e864af9aa 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -1,8 +1,8 @@ -.. _configuration_narr: - .. index:: single: application configuration +.. _configuration_narr: + Application Configuration ========================= diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 1df082003..c3adc936c 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -1,104 +1,27 @@ -.. _configuration_narr: +.. _firstapp_chapter: Creating Your First :mod:`repoze.bfg` Application ================================================= We will walk through the creation of a tiny :mod:`repoze.bfg` -application in this chapter and explain in more detail how the -application works. - -But before we dive into the code, a short introduction to -:term:`traversal` is required. - -.. index:: - pair: traversal; introduction - -.. _traversal_intro: - -An Introduction to Traversal ----------------------------- - -In order for a web application to perform any useful action, it needs -some way of finding and invoking code based on parameters present in -the :term:`request`. :term:`traversal` is a mechanism that plays a -part in finding code when a request enters the system. - -:term:`traversal` is the act of finding a :term:`context` and a -:term:`view name` by walking over a graph of objects starting from a -:term:`root` object, using the :term:`request` object as a source of -path information. - -:term:`view` code is the code in an application that responds to a -request. Traversal doesn't actually locate :term:`view` code by -itself: it only locates a :term:`context` and a :term:`view name`. -But the combination of the :term:`context` object and the :term:`view -name` found via traversal is used by a separate :mod:`repoze.bfg` -subsystem (the "view lookup" subsystem) to find a :term:`view -callable` later in the same request. A view callable is a specific -bit of code that receives a :term:`request` and which returns a -:term:`response`. - -.. note:: - - Another distinct mode known as :term:`URL dispatch` can alternately - be used to find a view callable based on a URL. However, the - application we're going to write uses only :term:`traversal`. - -The ``PATH_INFO`` portion of a URL is the data following the hostname -and port number, but before any query string elements or fragments, -for example the ``/a/b/c`` portion of the URL -``http://example.com/a/b/c?foo=1``. - -Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of -path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is -treated as the sequence ``['a', 'b', 'c']``. Traversal pops the first -element (``a``) from the segment sequence and attempts to use it as a -lookup key into an *object graph* supplied by our application. If -that succeeeds, the :term:`context` temporarily becomes the object -found via that lookup. Then the next segment (``b``) is popped from -the sequence, and the object graph is queried for that segment; if -that lookup succeeds, the :term:`context` becomes that object. This -process continues until the path segment sequence is exhausted or any -lookup for a name in the sequence fails. - -As we previously mentioned, the results of a :term:`traversal` include -a :term:`context` and a :term:`view name`. The :term:`view name` is -the *first* URL path segment in the set of ``PATH_INFO`` segments -"left over" in the path segment list popped by :term:`traversal`. It -will be the empty string (``''``) if no segments remain. The -circumstance where the :term:`view name` is the empty string -represents that the :term:`default view` for a :term:`context` should -be invoked. - -If the :term:`view name` is *not* the empty string, it means that -traversal "ran out" of nodes in the *object graph* before it finished -exhausting all the path segments implied by the URL path segments. In -this case, because the :term:`view name` is non-empty, a *non-default* -view callable will be invoked. - -This description of traversal is not comprehensive: it's tailored -towards understand the sample application we're about to create; we'll -cover traversal in more detail in the :term:`traversal_chapter`. +application in this chapter. After we're done with creating it, we'll +explain in more detail how the application works. .. note:: - A detailed analogy of how :mod:`repoze.bfg` :term:`traversal` works - is available within the chapter section entitled - :ref:`traversal_behavior`. If you're a "theory-first" person, you - might choose to read this to augment your understanding of - traversal while diving into the code that follows, but it's not - necessary if you're willing to "go with the flow". - -.. index:: - single: helloworld + If you're a "theory-first" kind of person, you might choose to read + :ref:`urlmapping_chapter` and :ref:`views_chapter` to augment your + understanding before diving into the code that follows, but it's + not necessary if -- like many programmers -- you're willing to "go + with the flow". .. _helloworld_imperative: Hello World, Goodbye World (Imperative) --------------------------------------- -Here's one of the simplest :mod:`repoze.bfg` applications, configured -imperatively: +Here's one of the very simplest :mod:`repoze.bfg` applications, +configured imperatively: .. code-block:: python :linenos: @@ -124,49 +47,12 @@ imperatively: When this code is inserted into a Python script named ``helloworld.py`` and executed by a Python interpreter which has the -:mod:`repoze.bfg` software installed, an HTTP server is started on -port 8080. When port 8080 is visited by a user agent on the root URL -(``/``), the server will simply serve up the text "Hello world!" with -the HTTP response values ``200 OK`` as a response code and a -``Content-Type`` header value of ``text/plain``. But for reasons -we'll better understand shortly, when visited by a user agent on the -URL ``/goodbye``, the server will serve up "Goodbye world!" +:mod:`repoze.bfg` software installed, an HTTP server is started on TCP +port 8080. When port 8080 is visited by a browser on the root URL +(``/``), the server will simply serve up the text "Hello world!" When +visited by a browser on the URL ``/goodbye``, the server will serve up +the text "Goodbye world!" -Our application's :term:`root` object is the *default* root object -used when one isn't otherwise specified in application configuration. -The default root object has no children. - -.. note:: - - In a "real" traversal-based :mod:`repoze.bfg` application, we'd - pass a ``root_factory`` to the ``Configurator`` object's - constructor, which would provide our application with a custom root - object instead of using the :mod:`repoze.bfg` default root object. - Supplying a custom ``root_factory`` is how you provide a custom - *object graph* to :mod:`repoze.bfg`. However, because our - application is so simple, we don't need a custom root object here. - -In a more complex :mod:`repoze.bfg` application there will be many -:term:`context` objects to which URLs might resolve. However, in this -toy application, effectively there is only ever one context: the -:term:`root` object. This is because the object graph of our hello -world application is very simple: there's exactly one object in our -graph; the default root object. - -We have only a single :term:`default view` registered (the -registration for the ``hello_world`` view callable). Due to this set -of circumstances, you can consider the sole possible URL that will -resolve to a :term:`default view` in this application the root URL -``'/'``. It is the only URL that will resolve to the :term:`view -name` of ``''`` (the empty string). - -We have only a single view registered for the :term:`view name` -``goodbye`` (the registration for the ``goodbye_world`` view -callable). Due to this set of circumstances, you can consider the -sole possible URL that will resolve to the ``goodbye_world`` in this -application the URL ``'/goodbye'`` because it is the only URL that -will result in the :term:`view name` of ``goodbye`` after traversal. - Now that we have a rudimentary understanding of what the application does, let's examine it piece-by-piece. @@ -199,8 +85,8 @@ The script also imports the ``Configurator`` class from the class provides methods which help configure various parts of :mod:`repoze.bfg` for a given application deployment. -View Declaration -~~~~~~~~~~~~~~~~ +View Callable Declarations +~~~~~~~~~~~~~~~~~~~~~~~~~~ The above script, beneath its set of imports, defines two functions: one named ``hello_world`` and one named ``goodbye_world``. @@ -214,30 +100,32 @@ one named ``hello_world`` and one named ``goodbye_world``. def goodbye_world(request): return Response('Goodbye world!') -Each function accepts a single argument (``request``) and returns an -instance of the :class:`webob.Response` class. In the ``hello_world`` -function, the string ``'Hello world!'`` is passed to the ``Response`` -constructor as the *body* of the response. In the ``goodbye_world`` -function, the string ``'Goodbye world!'`` is passed. +These functions don't really do anything very interesting. Both +functions accept a single argument (``request``). The ``hello_world`` +function does nothing but return a response instance with the body +``Hello world!``. The ``goodbye_world`` function returns a response +instance with the body ``Goodbye world!``. Each of these functions is known as a :term:`view callable`. View -callables in a "real" :mod:`repoze.bfg` application are often -functions which accept a :term:`request` and return a -:term:`response`. A view callable can be represented via another type -of object, like a class or an instance, but for our purposes here, a -function serves us well. - -A view callable is called with a :term:`request` object, which is a -representation of an HTTP request sent by a remote user agent. A view -callable is required to return a :term:`response` object because a -response object has all the information necessary to formulate an -actual HTTP response; this object is then converted to text and sent -back to the requesting user agent. - -The ``hello_world`` view callable defined by the script does nothing -but return a response with the body ``Hello world!``; the -``goodbye_world`` view callable returns a response with the body -``Goodbye world!``. +callables in a :mod:`repoze.bfg` application accept a single argument, +``request`` and are expected to return a :term:`response` object. A +view callable doesn't need to be a function; it can be represented via +another type of object, like a class or an instance, but for our +purposes here, a function serves us well. + +A view callable is always called with a :term:`request` object. A +request object is a representation of an HTTP request sent to +:mod:`repoze.bfg` via the active :term:`WSGI` server. + +A view callable is required to return a :term:`response` object +because a response object has all the information necessary to +formulate an actual HTTP response; this object is then converted to +text and sent back to the requesting browser. To return a response, +each view callable creates an instance of the :class:`webob.Response` +class. In the ``hello_world`` function, the string ``'Hello world!'`` +is passed to the ``Response`` constructor as the *body* of the +response. In the ``goodbye_world`` function, the string ``'Goodbye +world!'`` is passed. .. index:: pair: imperative; configuration @@ -291,19 +179,9 @@ only be run during a direct script execution. The ``config = Configurator()`` line above creates an instance of the :class:`repoze.bfg.configuration.Configurator` class. The resulting ``config`` object represents an API which the script uses to configure -this particular :mod:`repoze.bfg` application. - -.. note:: - - An instance of the :class:`repoze.bfg.configuration.Configurator` - class is a *wrapper* object which mutates an :term:`application - registry` as its methods are called. An application registry - represents the configuration state of a :mod:`repoze.bfg` - application. The ``Configurator`` is not itself an - :term:`application registry`, it is a mechanism used to configure - an application registry. The underlying application registry - object being configured by a ``Configurator`` is available as its - ``registry`` attribute. +this particular :mod:`repoze.bfg` application. Methods called on the +Configurator will cause registrations to be made in a +:term:`application registry` associated with the application. Beginning Configuration ~~~~~~~~~~~~~~~~~~~~~~~ @@ -344,64 +222,37 @@ Each of these lines calls the :meth:`repoze.bfg.configuration.Configurator.add_view` method. The ``add_view`` method of a configurator registers a :term:`view configuration` within the :term:`application registry`. A :term:`view -configuration` represents a :term:`view callable` which must be -invoked when a set of circumstances related to the :term:`request` is -true. This "set of circumstances" is provided as one or more keyword -arguments to the ``add_view`` method, otherwise known as -:term:`predicate` arguments. +configuration` represents a set of circumstances related to the +:term:`request` that will cause a specific :term:`view callable` to be +invoked. This "set of circumstances" is provided as one or more +keyword arguments to the ``add_view`` method. Each of these keyword +arguments is known as a view configuration :term:`predicate`. The line ``config.add_view(hello_world)`` registers the ``hello_world`` function as a view callable. The ``add_view`` method of a Configurator must be called with a view callable object as its -first argument, so the first argument passed is ``hello_world`` -function we'd like to use as a view callable. However, this line -calls ``add_view`` with a single default :term:`predicate` argument, -the ``name`` predicate with a value of the empty string (``''``), -meaning that we'd like :mod:`repoze.bfg` to invoke the ``hello_world`` -view callable for any request for the :term:`default view` of an -object. - -Our ``hello_world`` :term:`view callable` returns a Response instance -with a body of ``Hello world!`` in the configuration implied by this -script. It is configured as a :term:`default view`. Therefore, a -user agent contacting a server running this application will receive -the greeting ``Hello world!`` when any :term:`default view` is -invoked. - -.. sidebar:: View Dispatch and Ordering - - When :term:`traversal` is used, :mod:`repoze.bfg` chooses the most - specific view callable based *only* on view :term:`predicate` - applicability. This is unlike :term:`URL dispatch`, another - dispatch mode of :mod:`repoze.bfg` (and similar schemes used by - other frameworks, like :term:`Pylons` and :term:`Django`) which - first uses an ordered routing lookup to resolve the request to a - view callable by running it through a relatively-ordered series of - URL path matches. We're not really concerned about the finer - details of :term:`URL dispatch` right now. It's just useful to use - for comparative purposes: the ordering of calls to - :meth:`repoze.bfg.configuration.Configurator.add_view` is never - very important. We can register ``goodbye_world`` first and - ``hello_world`` second; :mod:`repoze.bfg` will still give us the - most specific callable when a request is dispatched to it. +first argument, so the first argument passed is the ``hello_world`` +function. This line calls ``add_view`` with a *default* value for the +:term:`predicate` argument, named ``name``. The ``name`` predicate +defaults to a value equalling the empty string (``''``). This means +that we're instructing :mod:`repoze.bfg` to invoke the ``hello_world`` +view callable when the :term:`view name` is the empty string. We'll +learn in later chapters what a :term:`view name` is, and under which +circumstances a request will have a view name that is the empty +string; in this particular application, it means that the +``hello_world`` view callable will be invoked when the root URL ``/`` +is visted by a browser. The line ``config.add_view(goodbye_world, name='goodbye')`` registers the ``goodbye_world`` function as a view callable. The line calls ``add_view`` with the view callable as the first required positional argument, and a :term:`predicate` keyword argument ``name`` with the -value ``'goodbye'``. This :term:`view configuration` implies that a -request with a :term:`view name` of ``goodbye`` should cause the -``goodbye_world`` view callable to be invoked. For the purposes of -this discussion, the :term:`view name` can be considered the first -non-empty path segment in the URL: in particular, this view -configuration will match when the URL is ``/goodbye``. - -Our ``goodbye_world`` :term:`view callable` returns a Response -instance with a body of ``Goodbye world!`` in the configuration -implied by this script. It is configured as with a :term:`view name` -predicate of ``goodbye``. Therefore, a user agent contacting a server -running this application will receive the greeting ``Goodbye world!`` -when the path info part of the request is ``/goodbye``. +value ``'goodbye'``. The ``name`` argument supplied in this +:term:`view configuration` implies that only a request that has a +:term:`view name` of ``goodbye`` should cause the ``goodbye_world`` +view callable to be invoked. In this particular application, this +means that the ``goodbye_world`` view callable will be invoked when +the URL ``/goodbye`` is visted by a browser. Each invocation of the ``add_view`` method implies a :term:`view configuration` registration. Each :term:`predicate` provided as a @@ -414,24 +265,13 @@ request, however, the view callable with the *most specific* view configuration (the view configuration that matches the most specific set of predicates) is always invoked. -Earlier we explained that the server would return ``Hello world!`` if -you visited the *root* (``/``) URL. However, actually, because the -view configuration registration for the ``hello_world`` view callable -has no :term:`predicate` arguments, the ``hello_world`` view callable -is applicable for the :term:`default view` of any :term:`context` -resulting from a request. This isn't all that interesting in this -application, because we always only have *one* potential context (the -root object): it is the only object in the graph. - -We've also registered a view configuration for another circumstance: -the ``goodbye_world`` view callable has a ``name`` predicate of -``goodbye``, meaning that it will match for requests that have the -:term:`view name` ``goodbye`` unlike the ``hello_world`` view -configuration registration, which will only match the default view -(view name ``''``) of a request. Because :mod:`repoze.bfg` chooses -the best view configuration for any request, the ``goodbye_world`` -view callable will be used when the URL contains path information that -ends with ``/goodbye``. +In this application, :mod:`repoze.bfg` chooses the most specific view +callable based only on view :term:`predicate` applicability. The +ordering of calls to +:meth:`repoze.bfg.configuration.Configurator.add_view` is never very +important. We can register ``goodbye_world`` first and +``hello_world`` second; :mod:`repoze.bfg` will still give us the most +specific callable when a request is dispatched to it. Ending Configuration ~~~~~~~~~~~~~~~~~~~~ @@ -484,12 +324,9 @@ It has a reference to the :term:`application registry` which resulted from method calls to the configurator used to configure it. The router consults the registry to obey the policy choices made by a single application. These policy choices were informed by method -calls to the ``Configurator`` made earlier; in our case, the only -policy choices made were implied by two calls to the ``add_view`` -method, telling our application that it should effectively serve up -the ``hello_world`` view callable to any user agent when it visits the -root URL, and the ``goodbye_world`` view callable to any user agent -when it visits the URL with the path info ``/goodbye``. +calls to the :term:`Configurator` made earlier; in our case, the only +policy choices made were implied by two calls to its ``add_view`` +method. WSGI Application Serving ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -519,9 +356,8 @@ Conclusion Our hello world application is one of the simplest possible :mod:`repoze.bfg` applications, configured "imperatively". We can see -a good deal of what's going on "under the hood" when we configure a -:mod:`repoze.bfg` application imperatively. However, another mode of -configuration exists named *declarative* configuration. +that it's configured imperatively because the full power of Python is +available to us as we perform configuration tasks. .. index:: pair: helloworld; declarative @@ -532,11 +368,12 @@ configuration exists named *declarative* configuration. Hello World, Goodbye World (Declarative) ---------------------------------------- -:mod:`repoze.bfg` can be configured for the same "hello world" -application "declaratively", if so desired, as described in -:ref:`declarative_configuration`. +Another almost entirely equivalent mode of application configuration +exists named *declarative* configuration. :mod:`repoze.bfg` can be +configured for the same "hello world" application "declaratively", if +so desired. -Create a file named ``helloworld.py``: +To do so, first, create a file named ``helloworld.py``: .. code-block:: python :linenos: @@ -559,8 +396,8 @@ Create a file named ``helloworld.py``: app = config.make_wsgi_app() serve(app, host='0.0.0.0') -Create a file named ``configure.zcml`` in the same directory as the -previously created ``helloworld.py``: +Then create a file named ``configure.zcml`` in the same directory as +the previously created ``helloworld.py``: .. code-block:: xml :linenos: @@ -764,7 +601,7 @@ configuration` it creates. Since the relative ordering of calls to :meth:`repoze.bfg.configuration.Configurator.add_view` doesn't matter (see the sidebar entitled *View Dispatch and Ordering* within -:ref:`adding_configuration), the relative order of ``<view>`` tags in +:ref:`adding_configuration`), the relative order of ``<view>`` tags in ZCML doesn't matter either. The following ZCML orderings are completely equivalent: @@ -797,18 +634,16 @@ completely equivalent: /> We've now configured a :mod:`repoze.bfg` helloworld application -declaratively. +declaratively. More information about this mode of configuration is +available in :ref:`declarative_configuration` and within +:ref:`zcml_directives`. References ---------- -For more information about the API of a ``Configurator`` object, see -:class:`repoze.bfg.configuration.Configurator` . The equivalent ZCML -declaration tags are introduced in narrative documentation chapters as -necessary. - -For more information about :term:`traversal`, see -:ref:`traversal_chapter`. +For more information about the API of a :term:`Configurator` object, +see :class:`repoze.bfg.configuration.Configurator` . The equivalent +ZCML declaration tags are introduced in :ref:`zcml_directives`. For more information about :term:`view configuration`, see :ref:`views_chapter`. diff --git a/docs/narr/router.rst b/docs/narr/router.rst index c9ffe2089..05546f28e 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -102,7 +102,7 @@ processing? This is a very high-level overview that leaves out various details. For more detail about subsystems invoked by the BFG router such as traversal, URL dispatch, views, and event processing, see -:ref:`url_mapping_chapter`, :ref:`traversal_chapter`, +:ref:`urlmapping_chapter`, :ref:`traversal_chapter`, :ref:`urldispatch_chapter`, :ref:`views_chapter`, and :ref:`events_chapter`. diff --git a/docs/narr/security.rst b/docs/narr/security.rst index cb10ff0b5..ad91e9e29 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -11,23 +11,32 @@ Here's how it works at a high level: - A :term:`request` is generated when a user visits our application. -- Based on the request, a :term:`context` is located. Exactly how a - context is located depends whether you are using :term:`traversal` - or :term:`URL dispatch`, but in either case, one is found. See - :ref:`url_mapping_chapter` for more information. +- Based on the request, a :term:`context` is located through + :term:`context finding`. A context is located differently depending + on whether the application uses :term:`traversal` or :term:`URL + dispatch`, but a context is ultimately found in either case. See + :ref:`urlmapping_chapter` for more information about context + finding. -- A :term:`view callable` is located using the the context as well as - other attributes of the request. +- A :term:`view callable` is located by :term:`view lookup` using the + the context as well as other attributes of the request. + +- If an :term:`authentication policy` is in effect, it is passed the + request; it returns some number of :term:`principal` identifiers. - If an :term:`authorization policy` is in effect and the :term:`view configuration` associated with the view callable that was found has a :term:`permission` associated with it, the authorization policy is - passed the context: it will either allow or deny access. + passed the :term:`context`, some number of :term:`principal` + identifiers returned by the authentication policy, and the + :term:`permission` associated with the view; it will allow or deny + access. -- If access is allowed, the view callable is invoked. +- If the authorization policy allows access, the view callable is + invoked. -- If access is denied, view callable is not invoked; instead the - :term:`forbidden view` is invoked. +- If the authorization policy denies access, the view callable is not + invoked; instead the :term:`forbidden view` is invoked. Authorization is enabled by modifying your application to include a :term:`authentication policy` and :term:`authorization policy`. diff --git a/docs/narr/static.rst b/docs/narr/static.rst new file mode 100644 index 000000000..18423fec4 --- /dev/null +++ b/docs/narr/static.rst @@ -0,0 +1,225 @@ +Static Resources +================ + +:mod:`repoze.bfg` makes it possible to serve up "static" (non-dynamic) +resources from a directory on a filesystem. This chapter describes +how to configure :mod:`repoze.bfg` to do so. + +.. index:: + triple: view; zcml; static resource + single: add_static_view + +.. _static_resources_section: + +Serving Static Resources Using a ZCML Directive +----------------------------------------------- + +Use of the ``static`` ZCML directive or the +:meth:`repoze.bfg.configuration.configurator.add_static_view` method +is the preferred way to serve static resources (such as JavaScript and +CSS files) within a :mod:`repoze.bfg` application. These mechanisms +makes static files available at a name relative to the application +root URL, e.g. ``/static``. + +Use of the ``add_static_view`` imperative configuration method is +completely equivalent to using ZCML for the same purpose. + +Here's an example of a ``static`` ZCML directive that will serve files +up ``/static`` URL from the ``/var/www/static`` directory of the +computer which runs the :mod:`repoze.bfg` application. + +.. code-block:: xml + :linenos: + + <static + name="static" + path="/var/www/static" + /> + +Here's an example of a ``static`` directive that will serve files up +``/static`` URL from the ``a/b/c/static`` directory of the Python +package named ``some_package``. + +.. code-block:: xml + :linenos: + + <static + name="static" + path="some_package:a/b/c/static" + /> + +Here's an example of a ``static`` directive that will serve files up +under the ``/static`` URL from the ``static`` directory of the Python +package in which the ``configure.zcml`` file lives. + +.. code-block:: xml + :linenos: + + <static + name="static" + path="static" + /> + +When you place your static files on filesystem in the directory +represented as the ``path`` of the directive you, you should be able +to view the static files in this directory via a browser at URLs +prefixed with the directive's ``name``. For instance if the +``static`` directive's ``name`` is ``static`` and the static +directive's ``path`` is ``/path/to/static``, +``http://localhost:6543/static/foo.js`` may return the file +``/path/to/static/dir/foo.js``. The static directory may contain +subdirectories recursively, and any subdirectories may hold files; +these will be resolved by the static view as you would expect. + +See :ref:`static_directive` for detailed information. + +.. note:: The :ref:`static_directive` ZCML directive is new in + :mod:`repoze.bfg` 1.1. + +.. note:: The + :meth:`repoze.bfg.configuration.Configurator.add_static_view` + method offers an imperative equivalent to the ``static`` ZCML + directive. + +.. index:: + triple: generating; static resource; urls + +.. _generating_static_resource_urls: + +Generating Static Resource URLs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a ref::`static_directive` ZCML directive or a call to the +``add_static_view`` method of a +:class:`repoze.bfg.configuration.Configurator` is used to register a +static resource directory, a special helper API named +:func:`repoze.bfg.static_url` can be used to generate the appropriate +URL for a package resource that lives in one of the directories named +by the static registration ``path`` attribute. + +For example, let's assume you create a set of ``static`` declarations +in ZCML like so: + +.. code-block:: xml + :linenos: + + <static + name="static1" + path="resources/1" + /> + + <static + name="static2" + path="resources/2" + /> + +These declarations create URL-accessible directories which have URLs +which begin, respectively, with ``/static1`` and ``/static2``. The +resources in the ``resources/1`` directory are consulted when a user +visits a URL which begins with ``/static1``, and the resources in the +``resources/2`` directory are consulted when a user visits a URL which +begins with ``/static2``. + +You needn't generate the URLs to static resources "by hand" in such a +configuration. Instead, use the :func:`repoze.bfg.url.static_url` API +to generate them for you. For example, let's imagine that the +following code lives in a module that shares the same directory as the +above ZCML file: + +.. code-block:: python + :linenos: + + from repoze.bfg.url import static_url + from repoze.bfg.chameleon_zpt import render_template_to_response + + def my_view(request): + css_url = static_url('resources/1/foo.css', request) + js_url = static_url('resources/2/foo.js', request) + return render_template_to_response('templates/my_template.pt', + css_url = css_url, + js_url = js_url) + +If the request "application URL" of the running system is +``http://example.com``, the ``css_url`` generated above would be: +``http://example.com/static1/foo.css``. The ``js_url`` generated +above would be ``'http://example.com/static2/foo.js``. + +One benefit of using the :func:`repoze.bfg.url.static_url` function +rather than constructing static URLs "by hand" is that if you need to +change the ``name`` of a static URL declaration in ZCML, the generated +URLs will continue to resolve properly after the rename. + +.. note:: The :func:`repoze.bfg.url.static_url` API is new in + :mod:`repoze.bfg` 1.1. + +.. index:: + pair: view; static resource + +Advanced: Serving Static Resources Using a View Callable +-------------------------------------------------------- + +For more flexibility, static resources can be served by a :term:`view +callable` which you register manually. For example, you may want +static resources to only be available when the :term:`context` of the +view is of a particular type, or when the request is of a particular +type. + +The :class:`repoze.bfg.view.static` helper class is used to perform +this task. This class creates an object that is capable acting as a +:mod:`repoze.bfg` view callable which serves static resources from a +directory. For instance, to serve files within a directory located on +your filesystem at ``/path/to/static/dir`` mounted at the URL path +``/static`` in your application, create an instance of the +:class:`repoze.bfg.view.static` class inside a ``static.py`` file in +your application root as below. + +.. ignore-next-block +.. code-block:: python + :linenos: + + from repoze.bfg.view import static + static_view = static('/path/to/static/dir') + +.. note:: the argument to :class:`repoze.bfg.view.static` can also be + a relative pathname, e.g. ``my/static`` (meaning relative to the + Python package of the module in which the view is being defined). + It can also be a :term:`resource specification` + (e.g. ``anotherpackage:some/subdirectory``) or it can be a + "here-relative" path (e.g. ``some/subdirectory``). If the path is + "here-relative", it is relative to the package of the module in + which the static view is defined. + +Subsequently, you may wire this view up to be accessible as +``/static`` using either the +:mod:`repoze.bfg.configuration.Configurator.add_view` method or the +``<view>`` ZCML directive in your application's ``configure.zcml`` +against either the class or interface that represents your root +object. For example (ZCML): + +.. code-block:: xml + :linenos: + + <view + context=".models.Root" + view=".static.static_view" + name="static" + /> + +In this case, ``.models.Root`` refers to the class of which your +:mod:`repoze.bfg` application's root object is an instance. + +.. note:: You can also give a ``context`` of ``*`` if you want the + name ``static`` to be accessible as the static view against any + model. This will also allow ``/static/foo.js`` to work, but it + will allow for ``/anything/static/foo.js`` too, as long as + ``anything`` itself is resolvable. + +.. note:: To ensure that model objects contained in the root don't + "shadow" your static view (model objects take precedence during + traversal), or to ensure that your root object's ``__getitem__`` is + never called when a static resource is requested, you can refer to + your static resources as registered above in URLs as, + e.g. ``/@@static/foo.js``. This is completely equivalent to + ``/static/foo.js``. See :ref:`traversal_chapter` for information + about "goggles" (``@@``). + diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index acf2b7493..812861412 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -3,15 +3,53 @@ Traversal ========= -When :term:`traversal` is used within a :mod:`repoze.bfg` application, -the :mod:`repoze.bfg` :term:`router` parses the URL associated with -the request. It splits the URL into individual path segments. Based -on these path segments, :mod:`repoze.bfg` traverses an *object graph* -in order to find a :term:`context`. It then attempts to find a -:term:`view` based on the *type* of the context (specified by its -Python class or any :term:`interface` attached to it). If -:mod:`repoze.bfg` finds a :term:`view` for the context, it calls it -and returns a response to the user. +:term:`traversal` is a :term:`context finding` mechanism that is used +by :mod:`repoze.bfg`. :term:`traversal` is the act of finding a +:term:`context` and a :term:`view name` by walking over an *object +graph*, starting from a :term:`root` object, using a :term:`request` +object as a source of path information. + +In this chapter, we'll provide a high-level overview of traversal, +explain the concept of an *object graph*, + +.. index:: + pair: traversal; high-level overview + +A High-Level Overview of Traversal Mechanics +-------------------------------------------- + +:term:`Traversal` is dependent on information in a :term:`request` +object. The :term:`request` object contains URL path information in +the ``PATH_INFO`` portion of the :term:`WSGI` environment. The +``PATH_INFO`` portion of the WSGI environment is the URL data in a +request following the hostname and port number, but before any query +string elements or fragments, for example the ``/a/b/c`` portion of +the URL ``http://example.com/a/b/c?foo=1``. + +Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of +path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is +treated as the sequence ``['a', 'b', 'c']``. Traversal pops the first +element (``a``) from the path segment sequence and attempts to use it +as a lookup key into an object graph supplied by an application. If +that succeeeds, the :term:`context` temporarily becomes the object +found via that lookup. Then the next segment (``b``) is popped from +the sequence, and the object graph is queried for that segment; if +that lookup succeeds, the :term:`context` becomes that object. This +process continues until the path segment sequence is exhausted or any +lookup for a name in the sequence fails. In either case, a +:term:`context` is found. + +The results of a :term:`traversal` also include a :term:`view name`. +The :term:`view name` is the *first* URL path segment in the set of +``PATH_INFO`` segments "left over" in the path segment list popped by +the traversal process. + +The combination of the :term:`context` object and the :term:`view +name` found via traversal is used later in the same request by a +separate :mod:`repoze.bfg` subsystem -- the "view lookup" subsystem -- +to find a :term:`view callable` later within the same request. How +:mod:`repoze.bfg` performs view lookup is explained within the +:ref:`views_chapter` chapter. .. index:: single: object graph @@ -55,6 +93,52 @@ usually a *mapping* object (such as a Python dictionary). emulates the WSGI environment, so code expecting the argument to be a dictionary will continue to work. +.. sidebar:: Emulating the Default Root Factory + + For purposes of understanding the default root factory better, + we'll note that you can emulate the default root factory by using + this code as an explicit root factory in your application setup: + + .. code-block:: python + :linenos: + + class Root(object): + def __init__(self, request): + pass + + config = Configurator(root_factory=Root) + + The default root factory is just a really stupid object that has no + behavior or state. + +Using :term:`traversal` against an application that uses the object +graph supplied by the default root object is not very interesting, +because the default root object has no children. In a more complex +:mod:`repoze.bfg` application, a root factory would be supplied which +would return an object that had children capable of being traversed, +and therefore there might be many :term:`context` objects to which +URLs might resolve, depending on the URL path. However, in this toy +application, there's exactly one object in our object graph; the +default root object. Therefore, there can only ever be one context: +the :term:`root` object itself. + +We have only a single :term:`default view` registered (the +registration for the ``hello_world`` view callable). Due to this set +of circumstances, you can consider the sole possible URL that will +resolve to a :term:`default view` in this application the root URL +``'/'``. It is the only URL that will resolve to the :term:`view +name` of ``''`` (the empty string) when the default object graph is +traversed. + +We have only a single view registered for the :term:`view name` +``goodbye`` (the registration for the ``goodbye_world`` view +callable). Due to this set of circumstances, you can consider the +sole possible URL that will resolve to the ``goodbye_world`` in this +application the URL ``'/goodbye'`` because it is the only URL that +will result in the :term:`view name` of ``goodbye`` when the default +object graph is traversed. + + Items contained within the object graph are sometimes analogous to the concept of :term:`model` objects used by many other frameworks (and :mod:`repoze.bfg` APIs often refers to them as "models", as well). @@ -373,66 +457,8 @@ There are two special cases: graph unambiguously. .. index:: - pair: traversal; side-effects - -.. _traversal_related_side_effects: - -Traversal-Related Side Effects ------------------------------- - -The :term:`subpath` will always be available to a view as a the -``subpath`` attribute of the :term:`request` object. It will be a -sequence containing zero or more elements (which will be Unicode -objects). - -The :term:`view name` will always be available to a view as the -``view_name`` attribute of the :term:`request` object. It will be a -single string (possibly the empty string if we're rendering a default -view). - -The :term:`root` will always be available to a view as the ``root`` -attribute of the :term:`request` object. It will be the model object -at which traversal started (the root). - -The :term:`context` will always be available to a view as the -``context`` attribute of the :term:`request` object. It will be the -context object implied by the current request. - -The "traversal path" will always be available to a view as the -``traversed`` attribute of the :term:`request` object. It will be a -sequence representing the ordered set of names that were used to -traverse to the :term:`context`, not including the view name or -subpath. If there is a virtual root associated with request, the -virtual root path is included within the traversal path. - -The :term:`virtual root` will always be available to a view as the -``virtual_root`` attribute of the :term:`request` object. It will be -the virtual root object implied by the current request. See -:ref:`vhosting_chapter` for more information about virtual roots. - -The :term:`virtual root` *path* will always be available to a view as -the ``virtual_root_path`` attribute of the :term:`request` object. It -will be a sequence representing the ordered set of names that were -used to traverse to the virtual root object. See -:ref:`vhosting_chapter` for more information about virtual roots. - -.. index:: pair: debugging; not found errors -.. _debug_notfound_section: - -:exc:`NotFound` Errors ----------------------- - -It's useful to be able to debug :exc:`NotFound` error responses when -they occur unexpectedly due to an application registry -misconfiguration. To debug these errors, use the -``BFG_DEBUG_NOTFOUND`` environment variable or the ``debug_notfound`` -configuration file setting. Details of why a view was not found will -be printed to ``stderr``, and the browser representation of the error -will include the same information. See :ref:`environment_chapter` for -more information about how and where to set these values. - .. index:: pair: traversal; unicode @@ -449,3 +475,10 @@ in ``PATH_INFO`` is not decodeable using the UTF-8 decoding, a TypeError is raised. A segment will be fully URL-unquoted and UTF8-decoded before it is passed it to the ``__getitem__`` of any model object during traversal. + +References +---------- + +For a contextual example of how :term:`traversal` can be used to +create a :mod:`repoze.bfg` application, see the +:ref:`bfg_wiki_tutorial`. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 699d017c4..44515cd54 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -6,61 +6,77 @@ 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`. The presence of -``<route>`` statements in a :term:`ZCML` file used by your application -or the presence of calls to the +The URL dispatch feature of :mod:`repoze.bfg` allows you to either +augment or replace :term:`traversal` as a :term:`context finding` +mechanism, allowing URL pattern matching to have the "first crack" at +resolving a given URL to :term:`context` and :term:`view name`. + +Using URL dispatch exclusively allows you to avoid thinking about your +application in terms of "contexts" and "view names" entirely. Many +applications don't need :mod:`repoze.bfg` features -- such as +declarative security via an :term:`authorization policy` -- that +benefit from having any visible separation between :term:`context +finding` and :term:`view lookup`. To this end, URL dispatch provides +a handy syntax that allows you to effectively map URLs *directly* to +:term:`view` code in such a way that you needn't think about your +application in terms "context finding" at all. This makes developing +a :mod:`repoze.bfg` application seem more like developing an +application in a system that is "context-free", such as :term:`Pylons` +or :term:`Django`. + +Whether or not you care about "context", it often makes a lot of sense +to use :term:`URL dispatch` instead of :term:`traversal` in an +application that has no natural data 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 manipulates "flat" +data. + +The presence of :ref:`route_directive` statements in a :term:`ZCML` +file used by your application or the presence of calls to the :meth:`repoze.bfg.configuration.Configurator.add_route` method in imperative configuration within your application is a sign that you're -using URL dispatch. Using the ``add_route`` configurator method or -``<route>`` statements in ZCML allows you to declaratively map URLs to -code. The syntax of the pattern matching language used by -:mod:`repoze.bfg` is close to that of :term:`Routes`. - -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 manipulates "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. - -The :mod:`repoze.bfg` :term:`Router` checks an incoming request -against a *routes map* to find a :term:`context` and a :term:`view -callable` before :term:`traversal` has a chance to find these things -first. If a route matches, a :term:`context` is generated and -:mod:`repoze.bfg` will call the :term:`view callable` found due to the -context and the request. If no route matches, :mod:`repoze.bfg` will -fail over to calling the :term:`root factory` callable passed to the -:term:`Configurator` for the application (usually a traversal -function). - -A root factory is not required for purely URL-dispatch-based apps: if -the root factory callable is passed as ``None`` to the -:term:`Configurator`, :mod:`repoze.bfg` will return a :exc:`NotFound` -error to the user's browser when no routes match. - -.. note:: See :ref:`modelspy_project_section` for an example of a - simple root factory callable that will use traversal. +using :term:`URL dispatch`. + +High-Level Operational Overview +------------------------------- + +If route configuration is present in an application, the +:mod:`repoze.bfg` :term:`Router` checks every incoming request against +an ordered set of URL matching patterns present in a *route map*. + +If any route patern matches the information in the :term:`request` +provided to :mod:`repoze.bfg`, a route-specific :term:`context` and +:term:`view name` will be generated. In this circumstance, +:mod:`repoze.bfg` will shortcut :term:`traversal`, and will invoke +:term:`view lookup` using the context and view name generated by URL +dispatch. If the route named a :term:`view callable` in its +configuration, that view callable will be invoked when view lookup is +performed. + +However, if no route pattern matches the information in the +:term:`request` provided to :mod:`repoze.bfg`, it will fail over to +using :term:`traversal` to perform context finding and view lookup. + +Route Configuration +------------------- + +:term:`route configuration` is the act of adding a new :term:`route` +to an application. A route has a *path*, representing a pattern meant +to match against the ``PATH_INFO`` portion of a URL, and a *name*, +which is used by developers within a :mod:`repoze.bfg` application to +uniquely identify a particular route when generating a URL. It also +optionally has a ``factory`` and a set of :term:`view` parameters. + +A route configuration may be added to the system via :term:`imperative +configuration` or via :term:`ZCML`. Both are completely equivalent. .. index:: single: add_route -Configuring a Route via The ``add_route`` Configurator Method -------------------------------------------------------------- +Configuring a Route Imperatively via The ``add_route`` Configurator Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`repoze.bfg.configuration.Configurator.add_route` method adds a single :term:`route configuration` to the :term:`application @@ -69,13 +85,17 @@ registry`. Here's an example: .. ignore-next-block .. code-block:: python - config.add_route('myroute', '/prefix/:one/:two') + # "config" below is presumed to be an instance of the + # repoze.bfg.configuration.Configurator class; "myview" is assumed + # to be a "view callable" function + from views import myview + config.add_route(name='myroute', path='/prefix/:one/:two', view=myview) .. index:: single: ZCML directive; route Configuring a Route via ZCML ----------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of using the imperative method of adding a route, you can use :term:`ZCML` for the same purpose. For example: @@ -86,82 +106,113 @@ Instead of using the imperative method of adding a route, you can use <route name="myroute" path="/prefix/:one/:two" + view=".views.myview" /> -See :ref:`route_directive` for full ``route`` ZCML directive -documentation. +.. note:: -.. note:: The documentation that follows in this chapter assumes that - :term:`ZCML` will be used to perform route configuration. + Values prefixed with a period (``.``) within the values of ZCML + attributes such as the ``view`` attribute of a ``route`` 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 + ``.views.myview`` with the absolute ``hello.views.myview`` 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. -.. index:: - pair: route; ordering - -Route Ordering --------------- -ZCMl ``<route>`` declaration ordering and the ordering of calls to -:mod:`repoze.bfg.configuration.Configurator.add_route` is very -important, because routes are evaluated in a specific order. The -order that routes are evaluated is the order in which they are added -to the application at startup time. For ZCML, the order that routes -are evaluated is the order in which they appear in the ZCML relative -to each other. +See :ref:`route_directive` for full ``route`` ZCML directive +documentation. -This is unlike traversal, which depends on emergent behavior rather -than an ordered list of declarations. +Route Configuration That Names a View Callable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. index:: - pair: route; factory - single: route factory +When a route configuration declaration names a ``view`` attribute, the +attribute will be a value that references a :term:`view callable`. A +view callable, as described in :ref:`views_chapter`, is +developer-supplied code that "does stuff" as the result of a request. +For more information about how to create view callables, see +:ref:`views_chapter`. -Route Factories ---------------- - -A "route" declaration can mention a "factory". When a factory is -attached to a route, it is used to generate a root (it's a :term:`root -factory`) instead of the *default* root factory. This object will be -used as the :term:`context` of the view callable the route represents. +Here's an example route configuration that references a view callable: .. code-block:: xml + :linenos: <route - path="/abc" - name="abc" - view=".views.theview" - factory=".models.root_factory" + name="myroute" + path="/prefix/:one/:two" + view="mypackage.views.myview" /> -In this way, each route can use a different factory, making it -possible to supply a different :term:`context` object to the view -related to each route. - -.. index:: - pair: URL dispatch; matchdict - -The Matchdict -------------- - -The main purpose of a route is to match (nor not match) the -``PATH_INFO`` present in the WSGI environment provided during a -request against a URL path pattern. When this URL path pattern is -matched, a dictionary is placed on the request named ``matchdict`` -with the values that match patterns in the ``path`` element. If the -URL pattern does not match, no matchdict is generated. +When a route configuration names a ``view`` attribute, the :term:`view +callable` named as that ``view`` attribute will always be found and +invoked when the associated route path pattern matches during a +request. + +The purpose of making it possible to specify a view callable within a +route configuration is to avoid the need for developers to deeply +understand the details of :term:`context finding` and :term:`view +lookup`. When a route names a view callable, and a request enters the +system which matches the path of the route, the result is simple: the +view callable associated with the route is invoked with the request +that caused the invocation. + +For most usage, you needn't understand more than this; how it works is +an implementation detail. In the interest of completeness, however, +we'll explain how it *does* work in the following section. You can +skip it if you're uninterested. + +Route View Callable Registration and Lookup Details ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +When a ``view`` attribute is attached to a route configuration, +:mod:`repoze.bfg` ensures that a :term:`view configuration` is +registered that will always be found when the route path pattern is +matched during a request. To do so: + +- A special route-specific :term:`interface` is created at startup time + for each route configuration declaration. + +- When a route configuration declaration mentions a ``view`` + attribute, a :term:`view configuration` is registered at startup + time. This view configuration uses the route-specific interface as + a :term:`request` type. + +- At runtime, when a request causes any route to match, the + :term:`request` object is decorated with the route-specific + interface. + +- The fact that the request is decorated with a route-specific + interface causes the view lookup machinery to always use the view + callable registered using that interface by the route configuration + to service requests that match the route path pattern. + +In this way, we supply a system that still consumes the :term:`context +finding` and :term:`view lookup` services provided by +:mod:`repoze.bfg`, but which does not require that a developer +understand either of them if he doesn't want to. It also means that +we can allow a developer to combine :term:`URL dispatch` and +:term:`traversal` in exceptional cases (see :ref:`hybrid_chapter`). .. index:: pair: URL dispatch; path pattern syntax .. _route_path_pattern_syntax: -Path Pattern Syntax --------------------- +Route Path Pattern Syntax +~~~~~~~~~~~~~~~~~~~~~~~~~ -The path pattern syntax is simple. +The syntax of the pattern matching language used by :mod:`repoze.bfg` +URL dispatch in the *path* argument is straightforward; it is close to +that of the :term:`Routes` system used by :term:`Pylons`. -The path may start with a slash character. If the path does not start -with a slash character, an implicit slash will be prepended to it at -matching time. For example, the following paths are equivalent: +The *path* used in route configuration may start with a slash +character. If the path does not start with a slash character, an +implicit slash will be prepended to it at matching time. For example, +the following paths are equivalent: .. code-block:: text @@ -264,81 +315,173 @@ Will generate the following matchdict: .. index:: triple: ZCML directive; route; examples -``<route>`` Statement Examples ------------------------------- -Let's check out some examples of how ``<route>`` statements might be -commonly declared. +.. index:: + pair: route; ordering + +Route Declaration Ordering +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because route configuration declarations are evaluated in a specific +order when a request enters the system, route configuration +declaration ordering is very important. -Example 1 -~~~~~~~~~ +The order that routes declarations are evaluated is the order in which +they are added to the application at startup time. This is unlike +:term:`traversal`, which depends on the emergent behavior which +happens as a result of traversing a graph. + +For routes configured via ZCML, the order that routes are evaluated is +the order in which they appear in the ZCML relative to each other. +For routes added via the +:mod:`repoze.bfg.configuration.Configurator.add_route` method, the +order that routes are evaluated is the order in which they are added +to the configuration imperatively. -The simplest route declaration: +.. index:: + pair: route; factory + +Route Factories +~~~~~~~~~~~~~~~ + +A "route" configuration declaration can mention a "factory". When a +factory is attached to a route, the :term:`root factory` passed at +startup time to the :term:`Configurator` is ignored; instead the +factory associated with the route is used to generate a :term:`root` +object. This object will usually be used as the :term:`context` of +the view callable ultimately found via :term:`view lookup`. .. code-block:: xml - :linenos: <route - name="idea" - path="hello.html" - view="mypackage.views.hello_view" + path="/abc" + name="abc" + view=".views.theview" + factory=".models.root_factory" /> -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. +In this way, each route can use a different factory, making it +possible to supply a different :term:`context` object to the view +related to each particular route. -The ``mypackage.views`` module referred to above might look like so: +Supplying a different context for each route is useful when you're +trying to use a :mod:`repoze.bfg` :term:`authorization policy` to +provide declarative "context-sensitive" security checks; each context +can maintain a separate :term:`ACL` (as in +:ref:`using_security_with_urldispatch`). It is also useful when you +wish to combine URL dispatch with :term:`traversal` (as in +:ref:`hybrid_chapter`). -.. code-block:: python - :linenos: +Route Matching +-------------- - from webob import Response +The main purpose of route configuration is to match (nor not match) +the ``PATH_INFO`` present in the WSGI environment provided during a +request against a URL path pattern. - def hello_view(request): - return Response('Hello!') +The way that :mod:`repoze.bfg` does this is very simple. When a +request enters the system, for each route configuration declaration +present in the system, :mod:`repoze.bfg` checks the ``PATH_INFO`` +against the pattern declared. -.. note: the ``context`` attribute of the ``request`` object passed to - the above view will be an instance of the - :class:`repoze.bfg.urldispatch.DefaultRoutesContext` class. This - is the type of object created for a context when there is no - "factory" specified in the ``route`` declaration. It is a mapping - object, a lot like a dictionary. +If any route matches, the route matching process stops. The +:term:`request` is decorated with a special :term:`interface` which +describes it as a "route request", the :term:`context` and :term:`view +name` are generated, and the context, the view name, and the resulting +request are handed off to :term:`view lookup`. This process is +otherwise known as :term:`context finding`. During view lookup, if +any ``view`` argument was provided within the matched route +configuration, this view is called. -When using :term:`url dispatch` exclusively in an application (as -opposed to using both url dispatch and :term:`traversal`), the -:term:`context` of the view isn't always terribly interesting, -particularly if you never use a ``factory`` attribute on your route -definitions. However, if you do use a ``factory`` attribute on your -route definitions, you may be very interested in the :term:`context` -of the view. :mod:`repoze.bfg` supports view callables defined with -two arguments: ``context`` and ``request``. For example, the below -view statement is completely equivalent to the above view statement: +If no route matches after all route patterns are exhausted, +:mod:`repoze.bfg` falls back to :term:`traversal` to do :term:`context +finding` and :term:`view lookup`. + +.. index:: + pair: URL dispatch; matchdict + +The Matchdict +~~~~~~~~~~~~~ + +When the URL path pattern associated with a particular route +configuration is matched by a request, a dictionary named +``matchdict`` is added as an attribute of the :term:`request` object. +Thus, ``request.matchdict`` will contain the values that match +replacement patterns in the ``path`` element. The keys in a matchdict +will be strings. The values will be Unicode objects. + +.. note:: + + If no route URL pattern matches, no ``matchdict`` is attached to + the request. + +Routing Examples +---------------- + +Let's check out some examples of how route configuration statements +might be commonly declared, and what will happen if a they are matched +by the information present in a request. The examples that follow +assume that :term:`ZCML` will be used to perform route configuration, +although you can use :term:`imperative configuration` equivalently if +you like. + +.. _urldispatch_example1: + +Example 1 +~~~~~~~~~ + +The simplest route declaration which configures a route match to +*directly* result in a particular view callable being invoked: + +.. code-block:: xml + :linenos: + + <route + name="idea" + path="site/:id" + view="mypackage.views.site_view" + /> + +When a route configuration with a ``view`` attribute is added to the +system, and an incoming request matches the *path* of the route +configuration, the :term:`view callable` named as the ``view`` +attribute of the route configuration will be invoked. + +In the case of the above example, when the URL of a request matches +``/site/:id``, the view callable at the Python dotted path name +``mypackage.views.site_view`` will be called with the request. In +other words, we've associated a view callable directly with a route +path. + +When the ``/site/:id`` route path pattern matches during a request, +the ``site_view`` view callable is invoked with that request as its +sole argument. When this route matches, a ``matchdict`` will be +generated and attached to the request as ``request.matchdict``. If +the specific URL matched is ``/site/1``, the ``matchdict`` will be a +dictionary with a single key, ``id``; the value will be the string +``'1'``, ex.: ``{'id':'1'}``. + +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!') + def site_view(request): + return Response(request.matchdict['id']) -The ``context`` passed to this view will be an instance returned by -the default root factory or an instance returned by the ``factory`` -argument to your route definition. +The view has access to the matchdict directly via the request, and can +access variables within it that match keys present as a result of the +route path pattern. -Even if you use the request-only argument format in view callables, -you can still get to the ``context`` of the view (if necessary) by -accessing ``request.context``. - -See :ref:`request_and_context_view_definitions` for more information. +See :ref:`views_chapter` for more information about views. Example 2 ~~~~~~~~~ -Below is an example of some more complicated route statements you -might add to your ``configure.zcml``: +Below is an example of a more complicated set of route statements you +might add to your application: .. code-block:: xml :linenos: @@ -366,25 +509,46 @@ in these forms: .. code-block:: text - /ideas/<ideaname> - /users/<username> - /tags/<tagname> - -When a URL matches the pattern ``/ideas/<ideaname>``, the view -registered with the name ``idea`` will be called. This will be the -view available at the dotted Python pathname -``mypackage.views.idea_view``. + /ideas/:idea + /users/:user + /tags/:tag + +- When a URL matches the pattern ``/ideas/:idea``, the view available + at the dotted Python pathname ``mypackage.views.idea_view`` will be + called. For the specific URL ``/ideas/1``, the ``matchdict`` + generated and attached to the :term:`request` will consist of + ``{'idea':'1'}``. + +- When a URL matches the pattern ``/users/:user``, the view available + at the dotted Python pathname ``mypackage.views.user_view`` will be + called. For the specific URL ``/users/1``, the ``matchdict`` + generated and attached to the :term:`request` will consist of + ``{'user':'1'}``. + +- When a URL matches the pattern ``/tags/:tag``, the view available + at the dotted Python pathname ``mypackage.views.tag_view`` will be + called. For the specific URL ``/tags/1``, the ``matchdict`` + generated and attached to the :term:`request` will consist of + ``{'tag':'1'}``. + +In this example we've again associated each of our routes with a +:term:`view callable` directly. In all cases, the request, which will +have a ``matchdict`` attribute detailing the information found in the +URL by the process will be passed to the view callable. Example 3 ~~~~~~~~~ -The context object passed to a view found as the result of URL -dispatch will by default be an instance of the object returned by the -default :term:`root factory`. 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 a -:term:`request` and returns an instance of a class that will be the -context used by the view. +The context object passed in to a view found as the result of URL +dispatch will, by default, be an instance of the object returned by +the :term:`root factory` configured at startup time (the +``root_factory`` argument to the :term:`Configurator` used to +configure the application). + +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 a :term:`request` 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: @@ -400,94 +564,67 @@ An example of using a route with a factory: The above route will manufacture an ``Idea`` model as a :term:`context`, assuming that ``mypackage.models.Idea`` resolves to a -class that accepts a request in its ``__init__``. - -.. note:: Values prefixed with a period (``.``) for the ``factory`` - and ``view`` attributes of a ``route`` (such as ``.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. - -If no route matches in the above configuration, :mod:`repoze.bfg` will -call the "fallback" :term:`root factory` callable provided to the -:term:`Configurator` constructor. If the "fallback" root factory is -None, a :exc:`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. +class that accepts a request in its ``__init__``. For example: + +.. code-block:: python + :linenos: + + class Idea(object): + def __init__(self, request): + pass + +In a more complicated application, this root factory might be a class +representing a :term:`SQLAlchemy` model. Example 4 ~~~~~~~~~ -An example of configuring a ``view`` declaration in ``configure.zcml`` -that maps a context found via URL dispatch to a view function is as -follows: +It is possible to create a route declaration without a ``view`` +attribute, but associate the route with a :term:`view callable` using +a ``view`` declaration. .. code-block:: xml :linenos: <route - name="article" - path="archives/:article" - view=".views.article_view" - factory=".models.Article" - /> - -The ``.models`` module referred to above might look like so: - -.. code-block:: python - :linenos: - - class Article(object): - def __init__(self, request): - self.__dict__.update(request.matchdict) + name="idea" + path="site/:id" + /> - def is_root(self): - return self.article == 'root' + <view + view="mypackage.views.site_view" + route_name="idea" + /> -The ``.views`` module referred to above might look like so: +This set of configuration parameters creates a configuration +completely equivalent to this example provided in +:ref:`urldispatch_example1`: -.. code-block:: python +.. code-block:: xml :linenos: - from webob import Response + <route + name="idea" + path="site/:id" + view="mypackage.views.site_view" + /> + +In fact the spelling in :ref:`urldispatch_example1` is just syntactic +sugar for the more verbose spelling where the route declaration and +the view declaration are spelled separately. - def article_view(context, request): - if context.is_root(): - return Response('Root article') - else: - return Response('Article with name %s' % 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 -called with its :term:`context` as a instance of the ``Article`` -class. The ``Article`` instance will have keys and values matching -the keys and values in the 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``. +More uses for this style of associating views with routes are explored +in :ref:`hybrid_chapter`. .. index:: - pair: URL dispatch; catching root URL + pair: URL dispatch; matching the root URL -Catching the Root URL +Matching the Root URL --------------------- -It's not entirely obvious how to use a route to catch the root URL -("/"). To do so, give the empty string as a path in a ZCML ``route`` -declaration: +It's not entirely obvious how to use a route path pattern to match the +root URL ("/"). To do so, give the empty string as a path in a ZCML +``route`` declaration: .. code-block:: xml :linenos: @@ -684,3 +821,43 @@ not very ambitious. .. note:: See :ref:`security_chapter` for more information about :mod:`repoze.bfg` security and ACLs. +Context-and-Request View Callables +---------------------------------- + +When using :term:`url dispatch` exclusively in an application (as +opposed to using both url dispatch *and* :term:`traversal` in the same +application), the :term:`context` of the view isn't always terribly +interesting, particularly if you never use a ``factory`` attribute on +your route definitions. + +However, if you do use a ``factory`` attribute on your route +definitions, you may be very interested in the :term:`context` of the +view. :mod:`repoze.bfg` supports view callables defined with two +arguments: ``context`` and ``request``. For example, the below view +statement is completely equivalent to the above view statement: + +.. code-block:: python + :linenos: + + from webob import Response + + def hello_view(context, request): + return Response('Hello!') + +The ``context`` passed to this view will be an instance returned by +the default root factory or an instance returned by the ``factory`` +argument to your route definition. + +Even if you use the request-only argument format in view callables, +you can still get to the ``context`` of the view (if necessary) by +accessing ``request.context``. + +See :ref:`request_and_context_view_definitions` for more information. + +References +---------- + +For a contextual example of how :term:`URL dispatch` can be used to +create a :mod:`repoze.bfg` application, see the +:ref:`bfg_sql_wiki_tutorial`. + diff --git a/docs/narr/urlmapping.rst b/docs/narr/urlmapping.rst index 6f111dcfd..b7ba7021b 100644 --- a/docs/narr/urlmapping.rst +++ b/docs/narr/urlmapping.rst @@ -2,25 +2,71 @@ triple: differences; URL dispatch; traversal pair: mapping; URLs -.. _url_mapping_chapter: +.. _urlmapping_chapter: Mapping URLs to Code -------------------- -:mod:`repoze.bfg` supports two methods by which a URL can be mapped to -code: :term:`URL dispatch` and :term:`traversal`. +In order for a web application to perform any useful action, it needs +some way of finding and invoking code written by the application +developer based on parameters present in the :term:`request`. + +:mod:`repoze.bfg` uses two separate but cooperating subsystems to +ultimately find and invoke code written by the application developer: +:term:`context finding` and :term:`view lookup` . + +- A :mod:`repoze.bfg` "context finding" subsystem is given a + :term:`request`; it is responsible for finding a :term:`context` + object and a :term:`view name` based on information present in the + request. + +- The :mod:`repoze.bfg` view lookup subsystem is provided with a + :term:`request`, a :term:`context` and a :term:`view name`, and is + responsible for finding and invoking a :term:`view callable`. A + view callable is a specific bit of code that receives the + :term:`request` and which returns a :term:`response`, written and + registered by the application developer. + +These two subsystems are are used by :mod:`repoze.bfg` serially: a +:term:`context finding` subsystem does its job, then the result of +context finding is passed to the :term:`view lookup` subsystem. The +view lookup system finds a :term:`view callable` written by an +application developer, and invokes it. A view callable returns a +:term:`response`. The response is returned to the requesting user. + +.. sidebar:: What Good is A Context Finding Subsystem? + + Many other web frameworks such as :term:`Pylons` or :term:`Django` + actually collapse the two steps of context finding and view lookup + into a single step. In such systems, a URL maps *directly* to a + view callable. These systems possess no analogue to a + context finding subsystem: they are "context-free". This makes + them simpler to understand than systems which use "context". + However, using an explicit context finding step provides extra + flexibility. For example, it makes it possible to protect your + application with declarative context-sensitive instance-level + :term:`authorization`, which is not well supported in frameworks + that do not provide a notion of a context. See the + :ref:`security_chapter` for more information. + +There are two separate context finding subsystems in +:mod:`repoze.bfg`: :term:`traversal` and :term:`URL dispatch`. The +subsystems are documented within this chapter. They can be used +separately or they can be combined. + +There is only one view lookup subsystem present in :mod:`repoze.bfg`. +It is not documented in this chapter. Instead, it is documented +within :ref:`views_chapter`. -.. note:: +.. toctree:: + :maxdepth: 2 - The :mod:`repoze.bfg` support for :term:`URL dispatch` was inspired - by the :term:`Routes` system used by :term:`Pylons`. - :mod:`repoze.bfg` support for :term:`traversal` was inspired by - :term:`Zope`. + traversal + urldispatch + hybrid -:term:`URL dispatch` is convenient and straightforward: an incoming -URL is checked against a list of potential matches in a predefined -order. When a match is found, it means that a particular :term:`view -callable` will be invoked. +Should I Use Traversal or URL Dispatch for Context Finding? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :term:`URL dispatch` can easily handle URLs such as ``http://example.com/members/Chris``, where it's assumed that each @@ -78,22 +124,16 @@ can be traversed, it also becomes easy to provide "instance-level security": you just attach a security declaration to each instance in the graph. This is not nearly as easy to do when using URL dispatch. -In essence, the choice to use graph traversal vs. URL dispatch is -largely religious. Graph traversal dispatch probably just doesn't -make any sense when you possess completely "square" data stored in a -relational database because it requires the construction and -maintenance of a graph and requires that the developer think about -mapping URLs to code in terms of traversing that graph. However, when -you have a hierarchical data store, using traversal can provide -significant advantages over using URL-based dispatch. +In essence, the choice to use traversal vs. URL dispatch is largely +religious. Traversal dispatch probably just doesn't make any sense +when you possess completely "square" data stored in a relational +database because it requires the construction and maintenance of a +graph and requires that the developer think about mapping URLs to code +in terms of traversing that graph. However, when you have a +hierarchical data store, using traversal can provide significant +advantages over using URL-based dispatch. Since :mod:`repoze.bfg` provides support for both approaches, you can use either as you see fit; you can even combine them together if necessary. -.. toctree:: - :maxdepth: 2 - - traversal - urldispatch - hybrid diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 16a580221..f51e913ba 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -3,63 +3,56 @@ Views ===== -Views do the "heavy lifting" within almost every :mod:`repoze.bfg` -application. The primary job of any :mod:`repoze.bfg` application is -is to find and call a :term:`view callable` when a :term:`request` -reaches it. A :term:`view callable` is a callable which is invoked -when a request enters your application. +The primary job of any :mod:`repoze.bfg` application is is to find and +call a :term:`view callable` when a :term:`request` reaches the +application. A :term:`view callable` is invoked when a request enters +your application: it "does something", then returns a response. All +view callables are written by you, the application developer. + +The :ref:`urlmapping_chapter` describes how a :term:`context` and a +:term:`view name` are computed using information from the +:term:`request` via the process of :term:`context finding`. But +neither the context nor the view name found very useful unless those +elements can eventually be mapped to a :term:`view callable`. -A :term:`view callable` is mapped to one or more URLs by virtue of -:term:`view configuration`. View configuration is performed in one of -three ways: +.. note:: -- by using the :meth:`repoze.bfg.configuration.Configurator.add_view` - method. + A :term:`view callable` is oten referred to in conversational + shorthand as a :term:`view`; in this documentation we need to be + more precise, however, due to the difference between view + *configuration* and the code that implements a view *callable*. -- by adding a ``<view>`` declaration to :term:`ZCML` used by your - application (see :ref:`view_directive`). +The job of actually locating and invoking the "best" :term:`view +callable` is the job of the :term:`view lookup` subsystem. The view +lookup subsystem compares information found via +:term:`context finding` against :term:`view configuration` statements +made by the developer to choose "the best" view callable for a +specific circumstance. -- by running a :term:`scan` against application source code which has - a :class:`repoze.bfg.view.bfg_view` decorator attached to a Python - object. +Provided within this chapter is documentation of the process of +creating view callables, documentation about performing view +configuration, and a detailed explanation of view lookup. -Each of these mechanisms is completely equivalent to the other. +View Callables +-------------- -A view might also be mapped to a URL by virtue of :term:`route -configuration`. Route configuration is performed in one of the -following two ways: +No matter how a view callable is eventually found, all view callables +used by :mod:`repoze.bfg` must be constructed in the same way, and +must return the same kind of return value. -- by using the :meth:`repoze.bfg.configuration.Configurator.add_route` - method - -- by adding a ``<route>`` declaration to :term:`ZCML` used by - your application. - -See :ref:`urldispatch_chapter` for more information on mapping URLs to -views using routes. - -However a view callable is configured to be called, most view -callables accept a single argument named ``request``. This argument -represents a :term:`WebOb` :term:`Request` object representing the -current HTTP request. +Most view callables accept a single argument named ``request``. This +argument represents a :term:`WebOb` :term:`Request` object as +represented to :mod:`repoze.bfg` by the upstream :term:`WSGI` server. A view callable may always return a :term:`WebOb` :term:`Response` object directly. It may optionally return another arbitrary -non-Response value. If a view callable returns a non-Response result, +non-Response value: if a view callable returns a non-Response result, the result must be converted into a response by the :term:`renderer` associated with the :term:`view configuration` for the view. -.. note:: - - A :term:`view callable` is referred to in conversational shorthand - as a :term:`view`; in this documentation we need to be more - precise, however, due to the difference between view - *configuration* and the code that implements a view *callable*. - -.. note:: - - See :ref:`traversal_intro` for an example of how a view might be - found as the result of a request. +View callables can be functions, instances, or classes. View +callables can optionally be defined with an alternate calling +convention. .. index:: pair: view; calling convention @@ -69,7 +62,7 @@ associated with the :term:`view configuration` for the view. .. _function_as_view: Defining a View Callable as a Function --------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The easiest way to define a view callable is to create a function that accepts a single argument named ``request`` and which returns a @@ -91,8 +84,8 @@ callable implemented as a function: .. _class_as_view: -Defining a View Callable as a Class ------------------------------------ +Defining a View Callable as a Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: This feature is new as of :mod:`repoze.bfg` 0.8.1. @@ -139,7 +132,7 @@ represent the method expected to return a response, you can use an .. _request_and_context_view_definitions: Request-And-Context View Callable Definitions ---------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Usually, view callables are defined to accept only a single argument: ``request``. However, view callables may alternately be defined as @@ -210,7 +203,7 @@ has access to the context via ``request.context``. .. _the_response: View Callable Responses ------------------------ +~~~~~~~~~~~~~~~~~~~~~~~ A view callable may always return an object that implements the :term:`WebOb` :term:`Response` interface. The easiest way to return @@ -241,13 +234,35 @@ view by changing the ``renderer`` attribute in the view's configuration. See :ref:`views_which_use_a_renderer`. .. index:: + pair: view; http redirect + +Using a View Callable to Do A HTTP Redirect +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can issue an HTTP redirect from within a view by returning a +particular kind of response. + +.. code-block:: python + :linenos: + + from webob.exc import HTTPFound + + def myview(request): + return HTTPFound(location='http://example.com') + +All exception types from the :mod:`webob.exc` module implement the +Webob :term:`Response` interface; any can be returned as the response +from a view. See :term:`WebOb` for the documentation for this module; +it includes other response types for ``Unauthorized``, etc. + +.. index:: single: renderer pair: view; renderer .. _views_which_use_a_renderer: Writing View Callables Which Use a Renderer -------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: This feature is new as of :mod:`repoze.bfg` 1.1 @@ -306,31 +321,607 @@ Additional renderers can be added to the system as necessary via a ZCML directive (see :ref:`adding_and_overriding_renderers`). .. index:: + pair: renderers; built-in + +.. _built_in_renderers: + +Built-In Renderers +~~~~~~~~~~~~~~~~~~ + +Several built-in "renderers" exist in :mod:`repoze.bfg`. These +renderers can be used in the ``renderer`` attribute of view +configurations. + +.. index:: + pair: renderer; string + +``string``: String Renderer ++++++++++++++++++++++++++++ + +The ``string`` renderer is a renderer which renders a view callable +result to a string. If a view callable returns a non-Response object, +and the ``string`` renderer is associated in that view's +configuration, the result will be to run the object through the Python +``str`` function to generate a string. Note that if a Unicode object +is returned, it is not ``str()`` -ified. + +Here's an example of a view that returns a dictionary. If the +``string`` renderer is specified in the configuration for this view, +the view will render the returned dictionary to the ``str()`` +representation of the dictionary: + +.. code-block:: python + :linenos: + + from webob import Response + from repoze.bfg.view import bfg_view + + @bfg_view(renderer='string') + def hello_world(request): + return {'content':'Hello!'} + +The body of the response returned by such a view will be a string +representing the ``str()`` serialization of the return value: + +.. code-block: python + :linenos: + + {'content': 'Hello!'} + +Views which use the string renderer can vary non-body response +attributes by attaching properties to the request. See +:ref:`response_request_attrs`. + +.. index:: + pair: renderer; JSON + +``json``: JSON Renderer ++++++++++++++++++++++++ + +The ``json`` renderer is a renderer which renders view callable +results to :term:`JSON`. If a view callable returns a non-Response +object it is called. It passes the return value through the +``simplejson.dumps`` function, and wraps the result in a response +object. + +Here's an example of a view that returns a dictionary. If the +``json`` renderer is specified in the configuration for this view, the +view will render the returned dictionary to a JSON serialization: + +.. code-block:: python + :linenos: + + from webob import Response + from repoze.bfg.view import bfg_view + + @bfg_view(renderer='json') + def hello_world(request): + return {'content':'Hello!'} + +The body of the response returned by such a view will be a string +representing the JSON serialization of the return value: + +.. code-block: python + :linenos: + + '{"content": "Hello!"}' + +The return value needn't be a dictionary, but the return value must +contain values renderable by :func:`json.dumps`. + +You can configure a view to use the JSON renderer in ZCML by naming +``json`` as the ``renderer`` attribute of a view configuration, e.g.: + +.. code-block:: xml + :linenos: + + <view + context=".models.Hello" + view=".views.hello_world" + name="hello" + renderer="json" + /> + +Views which use the JSON renderer can vary non-body response +attributes by attaching properties to the request. See +:ref:`response_request_attrs`. + +.. index:: + pair: renderer; chameleon + +.. _chameleon_template_renderers: + +``*.pt`` or ``*.txt``: Chameleon Template Renderers ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Two built-in renderers exist for :term:`Chameleon` templates. + +If the ``renderer`` attribute of a view configuration is an absolute +path, a relative path or :term:`resource specification` which has a +final path element with a filename extension of ``.pt``, the Chameleon +ZPT renderer is used. See :ref:`chameleon_zpt_templates` for more +information about ZPT templates. + +If the ``renderer`` attribute of a view configuration is an absolute +path, a source-file relative path, or a :term:`resource specification` +which has a final path element with a filename extension of ``.txt``, +the :term:`Chameleon` text renderer is used. See +:ref:`chameleon_zpt_templates` for more information about Chameleon +text templates. + +The behavior of these renderers is the same, except for the engine +used to render the template. + +When a ``renderer`` attribute that names a Chameleon template path +(e.g. ``templates/foo.pt`` or ``templates/foo.txt``) is used, the view +must return a Response object or a Python *dictionary*. If the view +callable with an associated template returns a Python dictionary, the +named template will be passed the dictionary as its keyword arguments, +and the template renderer implementation will return the resulting +rendered template in a response to the user. If the view callable +returns anything but a dictionary, an error will be raised. + +Before passing keywords to the template, the keywords derived from the +dictionary returned by the view are augmented. The callable object +-- whatever object was used to define the ``view`` -- will be +automatically inserted into the set of keyword arguments passed to the +template as the ``view`` keyword. If the view callable was a class, +the ``view`` keyword will be an instance of that class. Also inserted +into the keywords passed to the template are ``renderer_name`` (the +name of the renderer, which may be a full path or a package-relative +name, typically the full string used in the ``renderer`` attribute of +the directive), ``context`` (the context of the view used to render +the template), and ``request`` (the request passed to the view used to +render the template). + +Here's an example view configuration which uses a Chameleon ZPT +renderer: + +.. code-block:: xml + :linenos: + + <view + context=".models.Hello" + view=".views.hello_world" + name="hello" + renderer="templates/foo.pt" + /> + +Here's an example view configuration which uses a Chameleon text +renderer: + +.. code-block:: xml + :linenos: + + <view + context=".models.Hello" + view=".views.hello_world" + name="hello" + renderer="templates/foo.txt" + /> + +Views with use a Chameleon renderer can vary response attributes by +attaching properties to the request. See +:ref:`response_request_attrs`. + +.. index:: + pair: renderer; response attributes + pair: renderer; changing headers + triple: headers; changing; renderer + +.. _response_request_attrs: + +Varying Attributes of Rendered Responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before a response that is constructed as the result of the use of a +:term:`renderer` is returned to :mod:`repoze.bfg`, several attributes +of the request are examined which have the potential to influence +response behavior. + +View callables that don't directly return a response should set these +values on the ``request`` object via ``setattr`` within the view +callable to influence automatically constructed response attributes. + +``response_content_type`` + + Defines the content-type of the resulting response, + e.g. ``text/xml``. + +``response_headerlist`` + + A sequence of tuples describing cookie values that should be set in + the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header', + 'foo')]``. + +``response_status`` + + A WSGI-style status code (e.g. ``200 OK``) describing the status of + the response. + +``response_charset`` + + The character set (e.g. ``UTF-8``) of the response. + +``response_cache_for`` + + A value in seconds which will influence ``Cache-Control`` and + ``Expires`` headers in the returned response. The same can also be + achieved by returning various values in the ``response_headerlist``, + this is purely a convenience. + +.. index:: + pair: renderers; adding + +.. _adding_and_overriding_renderers: + +Adding and Overriding Renderers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Additional configuration declarations can be made which override an +existing :term:`renderer` or which add a new renderer. Adding or +overriding a renderer is accomplished via :term:`ZCML` or via +imperative configuration. + +For example, to add a renderer which renders views which have a +``renderer`` attribute that is a path that ends in ``.jinja2``: + +.. topic:: Via ZCML + + .. code-block:: xml + :linenos: + + <renderer + name=".jinja2" + factory="my.package.MyJinja2Renderer"/> + + The ``factory`` attribute is a :term:`dotted Python name` that must + point to an implementation of a :term:`renderer`. + + The ``name`` attribute is the renderer name. + +.. topic:: Via Imperative Configuration + + .. code-block:: python + :linenos: + + from my.package import MyJinja2Renderer + config.add_renderer('.jinja2', MyJinja2Renderer) + + The first argument is the renderer name. + + The second argument is a reference to an to an implementation of a + :term:`renderer`. + +A renderer implementation is usually a class which has the following +interface: + +.. code-block:: python + :linenos: + + class RendererFactory: + def __init__(self, name): + """ Constructor: ``name`` may be a path """ + + def __call__(self, value, system): """ Call a the renderer + implementation with the value and the system value passed + in as arguments and return the result (a string or unicode + object). The value is the return value of a view. The + system value is a dictionary containing available system + values (e.g. ``view``, ``context``, and ``request``). """ + +There are essentially two different kinds of ``renderer`` +registrations: registrations that use a dot (``.``) in their ``name`` +argument and ones which do not. + +Renderer registrations that have a ``name`` attribute which starts +with a dot are meant to be *wildcard* registrations. When a ``view`` +configuration is encountered which has a ``name`` attribute that +contains a dot, at startup time, the path is split on its final dot, +and the second element of the split (the filename extension, +typically) is used to look up a renderer for the configured view. The +renderer's factory is still passed the entire ``name`` attribute value +(not just the extension). + +Renderer registrations that have ``name`` attribute which *does not* +start with a dot are meant to be absolute registrations. When a +``view`` configuration is encountered which has a ``name`` argument +that does not contain a dot, the full value of the ``name`` attribute +is used to look up the renderer for the configured view. + +Here's an example of a renderer registration in ZCML: + +.. code-block:: xml + :linenos: + + <renderer + name="amf" + factory="my.package.MyAMFRenderer"/> + +Adding the above ZCML to your application will allow you to use the +``my.package.MyAMFRenderer`` renderer implementation in ``view`` +configurations by referring to it as ``amf`` in the ``renderer`` +attribute: + +.. code-block:: python + :linenos: + + from repoze.bfg.view import bfg_view + + @bfg_view(renderer='amf') + def myview(request): + return {'Hello':'world'} + +By default, when a template extension is unrecognized, an error is +thrown at rendering time. You can associate more than one filename +extension with the same renderer implementation as necessary if you +need to use a different file extension for the same kinds of +templates. For example, to associate the ``.zpt`` extension with the +Chameleon page template renderer factory, use: + +.. code-block:: xml + :linenos: + + <renderer + name=".zpt" + factory="repoze.bfg.chameleon_zpt.renderer_factory"/> + +To override the default mapping in which files with a ``.pt`` +extension are rendered via a Chameleon ZPT page template renderer, use +a variation on the following in your application's ZCML: + +.. code-block:: xml + :linenos: + + <renderer + name=".pt" + factory="my.package.pt_renderer"/> + +To override the default mapping in which files with a ``.txt`` +extension are rendered via a Chameleon text template renderer, use a +variation on the following in your application's ZCML: + +.. code-block:: xml + :linenos: + + <renderer + name=".txt" + factory="my.package.text_renderer"/> + +To associate a *default* renderer with *all* view configurations (even +ones which do not possess a ``renderer`` attribute), use a variation +on the following (ie. omit the ``name`` attribute to the renderer +tag): + +.. code-block:: xml + :linenos: + + <renderer + factory="repoze.bfg.renderers.json_renderer_factory"/> + +See also :ref:`renderer_directive`. + +.. index:: + triple: exceptions; special; view + +Using Special Exceptions In View Callables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Usually when a Python exception is raised within a view callable, +:mod:`repoze.bfg` allows the exception to propagate all the way out to +the :term:`WSGI` server which invoked the application. + +However, for convenience, two special exceptions exist which are +always handled by :mod:`repoze.bfg` itself. These are +:exc:`repoze.bfg.exceptions.NotFound` and +:exc:`repoze.bfg.exceptions.Forbidden`. Both is an exception class +which accepts a single positional constructor argument: a ``message``. + +If :exc:`repoze.bfg.exceptions.NotFound` is raised within view code, +the result of the :term:`Not Found View` will be returned to the user +agent which performed the request. + +If :exc:`repoze.bfg.exceptions.Forbidden` is raised within view code, +the result of the :term:`Forbidden View` will be returned to the user +agent which performed the request. + +In all cases, the message provided to the exception constructor is +made available to the view which :mod:`repoze.bfg` invokes as +``request.environ['repoze.bfg.message']``. + +.. index:: + triple: view; forms; unicode + +Handling Form Submissions in View Callables (Unicode and Character Set Issues) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Most web applications need to accept form submissions from web +browsers and various other clients. In :mod:`repoze.bfg`, form +submission handling logic is always part of a :term:`view`. For a +general overview of how to handle form submission data using the +:term:`WebOb` API, see `"Query and POST variables" within the WebOb +documentation +<http://pythonpaste.org/webob/reference.html#query-post-variables>`_. +:mod:`repoze.bfg` defers to WebOb for its request and response +implementations, and handling form submission data is a property of +the request implementation. Understanding WebOb's request API is the +key to understanding how to process form submission data. + +There are some defaults that you need to be aware of when trying to +handle form submission data in a :mod:`repoze.bfg` view. Because +having high-order (non-ASCII) characters in data contained within form +submissions is exceedingly common, and because the UTF-8 encoding is +the most common encoding used on the web for non-ASCII character data, +and because working and storing Unicode values is much saner than +working with and storing bytestrings, :mod:`repoze.bfg` configures the +:term:`WebOb` request machinery to attempt to decode form submission +values into Unicode from the UTF-8 character set implicitly. This +implicit decoding happens when view code obtains form field values via +the :term:`WebOb` ``request.params``, ``request.GET``, or +``request.POST`` APIs. + +For example, let's assume that the following form page is served up to +a browser client, and its ``action`` points at some :mod:`repoze.bfg` +view code: + +.. code-block:: xml + :linenos: + + <html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + </head> + <form method="POST" action="myview"> + <div> + <input type="text" name="firstname"/> + </div> + <div> + <input type="text" name="lastname"/> + </div> + <input type="submit" value="Submit"/> + </form> + </html> + +The ``myview`` view code in the :mod:`repoze.bfg` application *must* +expect that the values returned by ``request.params`` will be of type +``unicode``, as opposed to type ``str``. The following will work to +accept a form post from the above form: + +.. code-block:: python + :linenos: + + def myview(request): + firstname = request.params['firstname'] + lastname = request.params['lastname'] + +But the following ``myview`` view code *may not* work, as it tries to +decode already-decoded (``unicode``) values obtained from +``request.params``: + +.. code-block:: python + :linenos: + + def myview(request): + # the .decode('utf-8') will break below if there are any high-order + # characters in the firstname or lastname + firstname = request.params['firstname'].decode('utf-8') + lastname = request.params['lastname'].decode('utf-8') + +For implicit decoding to work reliably, you must ensure that every +form you render that posts to a :mod:`repoze.bfg` view is rendered via +a response that has a ``;charset=UTF-8`` in its ``Content-Type`` +header; or, as in the form above, with a ``meta http-equiv`` tag that +implies that the charset is UTF-8 within the HTML ``head`` of the page +containing the form. This must be done explicitly because all known +browser clients assume that they should encode form data in the +character set implied by ``Content-Type`` value of the response +containing the form when subsequently submitting that form; there is +no other generally accepted way to tell browser clients which charset +to use to encode form data. If you do not specify an encoding +explicitly, the browser client will choose to encode form data in its +default character set before submitting it. The browser client may +have a non-UTF-8 default encoding. If such a request is handled by +your view code, when the form submission data is encoded in a non-UTF8 +charset, eventually the WebOb request code accessed within your view +will throw an error when it can't decode some high-order character +encoded in another character set within form data e.g. when +``request.params['somename']`` is accessed. + +If you are using the :class:`webob.Response` class to generate a +response, or if you use the ``render_template_*`` templating APIs, the +UTF-8 charset is set automatically as the default via the +``Content-Type`` header. If you return a ``Content-Type`` header +without an explicit charset, a WebOb request will add a +``;charset=utf-8`` trailer to the ``Content-Type`` header value for +you for response content types that are textual (e.g. ``text/html``, +``application/xml``, etc) as it is rendered. If you are using your +own response object, you will need to ensure you do this yourself. + +To avoid implicit form submission value decoding, so that the values +returned from ``request.params``, ``request.GET`` and ``request.POST`` +are returned as bytestrings rather than Unicode, add the following to +your application's ``configure.zcml``:: + + <subscriber for="repoze.bfg.interfaces.INewRequest" + handler="repoze.bfg.request.make_request_ascii"/> + +You can then control form post data decoding "by hand" as necessary. +For example, when this subscriber is active, the second example above +will work unconditionally as long as you ensure that your forms are +rendered in a request that has a ``;charset=utf-8`` stanza on its +``Content-Type`` header. + +.. note:: The behavior that form values are decoded from UTF-8 to + Unicode implicitly was introduced in :mod:`repoze.bfg` 0.7.0. + Previous versions of :mod:`repoze.bfg` performed no implicit + decoding of form values (the default was to treat values as + bytestrings). + +.. note:: Only the *values* of request params obtained via + ``request.params``, ``request.GET`` or ``request.POST`` are decoded + to Unicode objects implicitly in :mod:`repoze.bfg`'s default + configuration. The keys are still strings. + + + +.. index:: single: view configuration pair: view; configuration .. _view_configuration: -View Configuration: Mapping View Callables to URLs --------------------------------------------------- +View Configuration +------------------ + +A developer makes a :term:`view callable` available for use within a +:mod:`repoze.bfg` application via :term:`view configuration`. A view +configuration associates a view callable with a set of statements +about the set of circumstances which must be true for the view +callable to be invoked. + +A view configuration statement is made about information present in +the :term:`context` and in the :term:`request`, as well as the +:term:`view name`. These three pieces of information are known, +collectively, as a :term:`triad`. + +View configuration is performed in one of three ways: + +- by adding a ``<view>`` declaration to :term:`ZCML` used by your + application (see :ref:`mapping_views_using_zcml_section` and + :ref:`view_directive`). -:term:`View configuration` may be performed in one of three ways: by -using the :meth:`repoze.bfg.configuration.Configurator.add_view` -method, by adding ``view`` declarations using :term:`ZCML` or by using -the :class:`repoze.bfg.view.bfg_view` decorator. Each method is -explained below. +- by running a :term:`scan` against application source code which has + a :class:`repoze.bfg.view.bfg_view` decorator attached to a Python + object (see :class:`repoze.bfg.view.bfg_view` and + :ref:`mapping_views_using_a_decorator_section`). + +- by using the :meth:`repoze.bfg.configuration.Configurator.add_view` + method (see :meth:`repoze.bfg.configuration.Configurator.add_view` + and :ref:`mapping_views_using_imperative_config_section`). + +Each of these mechanisms is completely equivalent to the other. + +A view might also be mapped to a URL by virtue of :term:`route +configuration`. Route configuration is performed in one of the +following two ways: + +- by using the :meth:`repoze.bfg.configuration.Configurator.add_route` + method. + +- by adding a ``<route>`` declaration to :term:`ZCML` used by + your application. .. index:: triple: zcml; view; configuration -.. _mapping_views_to_urls_using_zcml_section: +.. _mapping_views_using_zcml_section: View Configuration Via ZCML ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You may associate a view with a URL by adding ``view`` declarations -via :term:`ZCML` in a ``configure.zcml`` file. An example of a view -declaration in ZCML is as follows: +You may associate a view with a URL by adding :ref:`view_directive` +declarations via :term:`ZCML` in a ``configure.zcml`` file. An +example of a view declaration in ZCML is as follows: .. code-block:: xml :linenos: @@ -342,9 +933,12 @@ declaration in ZCML is as follows: /> The above maps the ``.views.hello_world`` view callable function to -:term:`context` objects which are instances (or subclasses) of the -Python class represented by ``.models.Hello`` when the *view name* is -``hello.html``. +the following set of :term:`context finding` results: + +- A :term:`context` object which is an instance (or subclass) of the + Python class represented by ``.models.Hello`` + +- A :term:`view name` equalling ``hello.html``. .. note:: Values prefixed with a period (``.``) for the ``context`` and ``view`` attributes of a ``view`` declaration (such as those @@ -370,12 +964,25 @@ You can also declare a *default view callable* for a model type: /> A *default view callable* simply has no ``name`` attribute. When a -:term:`context` is found and there is no *view name* associated with -the result of :term:`traversal`, the *default view callable* is the -view callable that is used. +:term:`context` is found and there is no :term:`view name` associated +with the result of :term:`context finding`, the *default view +callable* is the view callable that is used. + +A default view callable can alternately be defined by using the empty +string as its ``name`` attribute: + +.. code-block:: xml + :linenos: + + <view + context=".models.Hello" + view=".views.hello_world" + name="" + /> -You can also declare that a view callable is good for any model type -by using the special ``*`` character in the ``context`` attribute: +You may also declare that a view callable is good for any context type +by using the special ``*`` character as the value of the ``context`` +attribute: .. code-block:: xml :linenos: @@ -386,9 +993,9 @@ by using the special ``*`` character in the ``context`` attribute: name="hello.html" /> -This indicates that when :mod:`repoze.bfg` identifies that the *view -name* is ``hello.html`` against *any* :term:`context`, the -``.views.hello_world`` view callable will be called. +This indicates that when :mod:`repoze.bfg` identifies that the +:term:`view name` is ``hello.html`` and the context is of any type, +the ``.views.hello_world`` view callable will be invoked. A ZCML ``view`` declaration's ``view`` attribute can also name a class. In this case, the rules described in :ref:`class_as_view` @@ -399,7 +1006,7 @@ See :ref:`view_directive` for complete ZCML directive documentation. .. index:: triple: view; bfg_view; decorator -.. _mapping_views_to_urls_using_a_decorator_section: +.. _mapping_views_using_a_decorator_section: View Configuration Using the ``@bfg_view`` Decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -718,6 +1325,8 @@ could be spelled equivalently as the below: single: add_view triple: imperative; adding; view +.. _mapping_views_using_imperative_config_section: + View Configuration Using the ``add_view`` Method of a Configurator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -740,73 +1349,12 @@ example: config.add_view(hello_world, name='hello.html') .. index:: - pair: view; lookup ordering - -.. _view_lookup_ordering: - -View Lookup Ordering --------------------- - -Many attributes of view configuration can be thought of like -"narrowers" or "predicates". In general, the greater number of -attributes possessed by a view's configuration, the more specific the -circumstances need to be before the registered view callable will be -invoked. - -For any given request, a view with five predicates will always be -found and evaluated before a view with two, for example. All -predicates must match for the associated view to be called. - -This does not mean however, that :mod:`repoze.bfg` "stops looking" -when it finds a view registration with predicates that don't match. -If one set of view predicates does not match, the "next most specific" -view (if any) view is consulted for predicates, and so on, until a -view is found, or no view can be matched up with the request. The -first view with a set of predicates all of which match the request -environment will be invoked. - -If no view can be found which has predicates which allow it to be -matched up with the request, :mod:`repoze.bfg` will return an error to -the user's browser, representing a "not found" (404) page. See -:ref:`changing_the_notfound_view` for more information about changing -the default notfound view. - -There are a several exceptions to the the rule which says that view -configuration attributes represent "narrowings". Several attributes -of the ``view`` directive are *not* narrowing predicates. These are -``permission``, ``name``, ``renderer``, and ``attr``. - -The value of the ``permission`` attribute represents the permission -that must be possessed by the user to invoke any found view. When a -view is found that matches all predicates, but the invoking user does -not possess the permission implied by any associated ``permission`` in -the current context, processing stops, and an -:exc:`repoze.bfg.exception.Forbidden` error is raised, usually -resulting in the :term:`forbidden view` being shown to the invoking -user. No further view narrowing or view lookup is done. - -.. note:: - - See :ref:`changing_the_forbidden_view` for more information about - changing the default forbidden view. - -The value of the ``name`` attribute represents a direct match of the -view name returned via traversal. It is part of initial view lookup -rather than a predicate/narrower. - -The value of the ``renderer`` attribute represents the renderer used -to convert non-response return values from a view. - -The value of the ``attr`` attribute represents the attribute name -looked up on the view object to return a response. - -.. index:: pair: model; interfaces .. _using_model_interfaces: Using Model Interfaces In View Configuration --------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of registering your views with a ``context`` that names a Python model *class* as a context, you can optionally register a view @@ -893,400 +1441,19 @@ within view configuration, see :ref:`models_which_implement_interfaces`. .. index:: - pair: renderers; built-in - -.. _built_in_renderers: - -Built-In Renderers ------------------- - -Several built-in "renderers" exist in :mod:`repoze.bfg`. These -renderers can be used in the ``renderer`` attribute of view -configurations. - -.. index:: - pair: renderer; string - -``string``: String Renderer -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``string`` renderer is a renderer which renders a view callable -result to a string. If a view callable returns a non-Response object, -and the ``string`` renderer is associated in that view's -configuration, the result will be to run the object through the Python -``str`` function to generate a string. Note that if a Unicode object -is returned, it is not ``str()`` -ified. - -Here's an example of a view that returns a dictionary. If the -``string`` renderer is specified in the configuration for this view, -the view will render the returned dictionary to the ``str()`` -representation of the dictionary: - -.. code-block:: python - :linenos: - - from webob import Response - from repoze.bfg.view import bfg_view - - @bfg_view(renderer='string') - def hello_world(request): - return {'content':'Hello!'} - -The body of the response returned by such a view will be a string -representing the ``str()`` serialization of the return value: - -.. code-block: python - :linenos: - - {'content': 'Hello!'} - -Views which use the string renderer can vary non-body response -attributes by attaching properties to the request. See -:ref:`response_request_attrs`. - -.. index:: - pair: renderer; JSON - -``json``: JSON Renderer -~~~~~~~~~~~~~~~~~~~~~~~ - -The ``json`` renderer is a renderer which renders view callable -results to :term:`JSON`. If a view callable returns a non-Response -object it is called. It passes the return value through the -``simplejson.dumps`` function, and wraps the result in a response -object. - -Here's an example of a view that returns a dictionary. If the -``json`` renderer is specified in the configuration for this view, the -view will render the returned dictionary to a JSON serialization: - -.. code-block:: python - :linenos: - - from webob import Response - from repoze.bfg.view import bfg_view - - @bfg_view(renderer='json') - def hello_world(request): - return {'content':'Hello!'} - -The body of the response returned by such a view will be a string -representing the JSON serialization of the return value: - -.. code-block: python - :linenos: - - '{"content": "Hello!"}' - -The return value needn't be a dictionary, but the return value must -contain values renderable by :func:`json.dumps`. - -You can configure a view to use the JSON renderer in ZCML by naming -``json`` as the ``renderer`` attribute of a view configuration, e.g.: - -.. code-block:: xml - :linenos: - - <view - context=".models.Hello" - view=".views.hello_world" - name="hello" - renderer="json" - /> - -Views which use the JSON renderer can vary non-body response -attributes by attaching properties to the request. See -:ref:`response_request_attrs`. - -.. index:: - pair: renderer; chameleon - -.. _chameleon_template_renderers: - -``*.pt`` or ``*.txt``: Chameleon Template Renderers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Two built-in renderers exist for :term:`Chameleon` templates. - -If the ``renderer`` attribute of a view configuration is an absolute -path, a relative path or :term:`resource specification` which has a -final path element with a filename extension of ``.pt``, the Chameleon -ZPT renderer is used. See :ref:`chameleon_zpt_templates` for more -information about ZPT templates. - -If the ``renderer`` attribute of a view configuration is an absolute -path, a source-file relative path, or a :term:`resource specification` -which has a final path element with a filename extension of ``.txt``, -the :term:`Chameleon` text renderer is used. See -:ref:`chameleon_zpt_templates` for more information about Chameleon -text templates. - -The behavior of these renderers is the same, except for the engine -used to render the template. - -When a ``renderer`` attribute that names a Chameleon template path -(e.g. ``templates/foo.pt`` or ``templates/foo.txt``) is used, the view -must return a Response object or a Python *dictionary*. If the view -callable with an associated template returns a Python dictionary, the -named template will be passed the dictionary as its keyword arguments, -and the template renderer implementation will return the resulting -rendered template in a response to the user. If the view callable -returns anything but a dictionary, an error will be raised. - -Before passing keywords to the template, the keywords derived from the -dictionary returned by the view are augmented. The callable object --- whatever object was used to define the ``view`` -- will be -automatically inserted into the set of keyword arguments passed to the -template as the ``view`` keyword. If the view callable was a class, -the ``view`` keyword will be an instance of that class. Also inserted -into the keywords passed to the template are ``renderer_name`` (the -name of the renderer, which may be a full path or a package-relative -name, typically the full string used in the ``renderer`` attribute of -the directive), ``context`` (the context of the view used to render -the template), and ``request`` (the request passed to the view used to -render the template). - -Here's an example view configuration which uses a Chameleon ZPT -renderer: - -.. code-block:: xml - :linenos: - - <view - context=".models.Hello" - view=".views.hello_world" - name="hello" - renderer="templates/foo.pt" - /> - -Here's an example view configuration which uses a Chameleon text -renderer: - -.. code-block:: xml - :linenos: - - <view - context=".models.Hello" - view=".views.hello_world" - name="hello" - renderer="templates/foo.txt" - /> - -Views with use a Chameleon renderer can vary response attributes by -attaching properties to the request. See -:ref:`response_request_attrs`. - -.. index:: - pair: renderer; response attributes - pair: renderer; changing headers - triple: headers; changing; renderer - -.. _response_request_attrs: - -Varying Attributes of Rendered Responses ----------------------------------------- - -Before a response that is constructed as the result of the use of a -:term:`renderer` is returned to :mod:`repoze.bfg`, several attributes -of the request are examined which have the potential to influence -response behavior. - -View callables that don't directly return a response should set these -values on the ``request`` object via ``setattr`` within the view -callable to influence automatically constructed response attributes. - -``response_content_type`` - - Defines the content-type of the resulting response, - e.g. ``text/xml``. - -``response_headerlist`` - - A sequence of tuples describing cookie values that should be set in - the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header', - 'foo')]``. - -``response_status`` - - A WSGI-style status code (e.g. ``200 OK``) describing the status of - the response. - -``response_charset`` - - The character set (e.g. ``UTF-8``) of the response. - -``response_cache_for`` - - A value in seconds which will influence ``Cache-Control`` and - ``Expires`` headers in the returned response. The same can also be - achieved by returning various values in the ``response_headerlist``, - this is purely a convenience. - -.. index:: - pair: renderers; adding - -.. _adding_and_overriding_renderers: - -Adding and Overriding Renderers -------------------------------- - -Additional configuration declarations can be made which override an -existing :term:`renderer` or which add a new renderer. Adding or -overriding a renderer is accomplished via :term:`ZCML` or via -imperative configuration. - -For example, to add a renderer which renders views which have a -``renderer`` attribute that is a path that ends in ``.jinja2``: - -.. topic:: Via ZCML - - .. code-block:: xml - :linenos: - - <renderer - name=".jinja2" - factory="my.package.MyJinja2Renderer"/> - - The ``factory`` attribute is a :term:`dotted Python name` that must - point to an implementation of a :term:`renderer`. - - The ``name`` attribute is the renderer name. - -.. topic:: Via Imperative Configuration - - .. code-block:: python - :linenos: - - from my.package import MyJinja2Renderer - config.add_renderer('.jinja2', MyJinja2Renderer) - - The first argument is the renderer name. - - The second argument is a reference to an to an implementation of a - :term:`renderer`. - -A renderer implementation is usually a class which has the following -interface: - -.. code-block:: python - :linenos: - - class RendererFactory: - def __init__(self, name): - """ Constructor: ``name`` may be a path """ - - def __call__(self, value, system): """ Call a the renderer - implementation with the value and the system value passed - in as arguments and return the result (a string or unicode - object). The value is the return value of a view. The - system value is a dictionary containing available system - values (e.g. ``view``, ``context``, and ``request``). """ - -There are essentially two different kinds of ``renderer`` -registrations: registrations that use a dot (``.``) in their ``name`` -argument and ones which do not. - -Renderer registrations that have a ``name`` attribute which starts -with a dot are meant to be *wildcard* registrations. When a ``view`` -configuration is encountered which has a ``name`` attribute that -contains a dot, at startup time, the path is split on its final dot, -and the second element of the split (the filename extension, -typically) is used to look up a renderer for the configured view. The -renderer's factory is still passed the entire ``name`` attribute value -(not just the extension). - -Renderer registrations that have ``name`` attribute which *does not* -start with a dot are meant to be absolute registrations. When a -``view`` configuration is encountered which has a ``name`` argument -that does not contain a dot, the full value of the ``name`` attribute -is used to look up the renderer for the configured view. - -Here's an example of a renderer registration in ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name="amf" - factory="my.package.MyAMFRenderer"/> - -Adding the above ZCML to your application will allow you to use the -``my.package.MyAMFRenderer`` renderer implementation in ``view`` -configurations by referring to it as ``amf`` in the ``renderer`` -attribute: - -.. code-block:: python - :linenos: - - from repoze.bfg.view import bfg_view - - @bfg_view(renderer='amf') - def myview(request): - return {'Hello':'world'} - -By default, when a template extension is unrecognized, an error is -thrown at rendering time. You can associate more than one filename -extension with the same renderer implementation as necessary if you -need to use a different file extension for the same kinds of -templates. For example, to associate the ``.zpt`` extension with the -Chameleon page template renderer factory, use: - -.. code-block:: xml - :linenos: - - <renderer - name=".zpt" - factory="repoze.bfg.chameleon_zpt.renderer_factory"/> - -To override the default mapping in which files with a ``.pt`` -extension are rendered via a Chameleon ZPT page template renderer, use -a variation on the following in your application's ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name=".pt" - factory="my.package.pt_renderer"/> - -To override the default mapping in which files with a ``.txt`` -extension are rendered via a Chameleon text template renderer, use a -variation on the following in your application's ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name=".txt" - factory="my.package.text_renderer"/> - -To associate a *default* renderer with *all* view configurations (even -ones which do not possess a ``renderer`` attribute), use a variation -on the following (ie. omit the ``name`` attribute to the renderer -tag): - -.. code-block:: xml - :linenos: - - <renderer - factory="repoze.bfg.renderers.json_renderer_factory"/> - -See also :ref:`renderer_directive`. - -.. index:: pair: view; security .. _view_security_section: -View Security -------------- +Configuring View Security +~~~~~~~~~~~~~~~~~~~~~~~~~ If a :term:`authorization policy` is active, any :term:`permission` attached to a :term:`view configuration` found during view lookup will be consulted to ensure that the currently authenticated user possesses that permission against the context before the view function is actually called. Here's an example of specifying a permission in a -view declaration in ZCML: +view configuration declaration in ZCML: .. code-block:: xml :linenos: @@ -1311,402 +1478,83 @@ user does not possess the ``add`` permission relative to the current an authentication policy. .. index:: - pair: view; http redirect - -Using a View Callable to Do A HTTP Redirect -------------------------------------------- - -You can issue an HTTP redirect from within a view by returning a -slightly different response. - -.. code-block:: python - :linenos: - - from webob.exc import HTTPFound - - def myview(request): - return HTTPFound(location='http://example.com') - -All exception types from the :mod:`webob.exc` module implement the -Webob :term:`Response` interface; any can be returned as the response -from a view. See :term:`WebOb` for the documentation for this module; -it includes other response types for ``Unauthorized``, etc. - -.. index:: - triple: view; zcml; static resource - single: add_static_view - -.. _static_resources_section: - -Serving Static Resources Using a ZCML Directive ------------------------------------------------ - -Use of the ``static`` ZCML directive or the -:meth:`repoze.bfg.configuration.configurator.add_static_view` method -is the preferred way to serve static resources (such as JavaScript and -CSS files) within a :mod:`repoze.bfg` application. These mechanisms -makes static files available at a name relative to the application -root URL, e.g. ``/static``. - -Use of the ``add_static_view`` imperative configuration method is -completely equivalent to using ZCML for the same purpose. - -Here's an example of a ``static`` ZCML directive that will serve files -up ``/static`` URL from the ``/var/www/static`` directory of the -computer which runs the :mod:`repoze.bfg` application. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="/var/www/static" - /> - -Here's an example of a ``static`` directive that will serve files up -``/static`` URL from the ``a/b/c/static`` directory of the Python -package named ``some_package``. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="some_package:a/b/c/static" - /> - -Here's an example of a ``static`` directive that will serve files up -under the ``/static`` URL from the ``static`` directory of the Python -package in which the ``configure.zcml`` file lives. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="static" - /> - -When you place your static files on filesystem in the directory -represented as the ``path`` of the directive you, you should be able -to view the static files in this directory via a browser at URLs -prefixed with the directive's ``name``. For instance if the -``static`` directive's ``name`` is ``static`` and the static -directive's ``path`` is ``/path/to/static``, -``http://localhost:6543/static/foo.js`` may return the file -``/path/to/static/dir/foo.js``. The static directory may contain -subdirectories recursively, and any subdirectories may hold files; -these will be resolved by the static view as you would expect. - -See :ref:`static_directive` for detailed information. - -.. note:: The :ref:`static_directive` ZCML directive is new in - :mod:`repoze.bfg` 1.1. - -.. note:: The - :meth:`repoze.bfg.configuration.Configurator.add_static_view` - method offers an imperative equivalent to the ``static`` ZCML - directive. - -.. index:: - triple: generating; static resource; urls - -.. _generating_static_resource_urls: - -Generating Static Resource URLs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a ref::`static_directive` ZCML directive or a call to the -``add_static_view`` method of a -:class:`repoze.bfg.configuration.Configurator` is used to register a -static resource directory, a special helper API named -:func:`repoze.bfg.static_url` can be used to generate the appropriate -URL for a package resource that lives in one of the directories named -by the static registration ``path`` attribute. - -For example, let's assume you create a set of ``static`` declarations -in ZCML like so: - -.. code-block:: xml - :linenos: - - <static - name="static1" - path="resources/1" - /> - - <static - name="static2" - path="resources/2" - /> - -These declarations create URL-accessible directories which have URLs -which begin, respectively, with ``/static1`` and ``/static2``. The -resources in the ``resources/1`` directory are consulted when a user -visits a URL which begins with ``/static1``, and the resources in the -``resources/2`` directory are consulted when a user visits a URL which -begins with ``/static2``. - -You needn't generate the URLs to static resources "by hand" in such a -configuration. Instead, use the :func:`repoze.bfg.url.static_url` API -to generate them for you. For example, let's imagine that the -following code lives in a module that shares the same directory as the -above ZCML file: - -.. code-block:: python - :linenos: - - from repoze.bfg.url import static_url - from repoze.bfg.chameleon_zpt import render_template_to_response - - def my_view(request): - css_url = static_url('resources/1/foo.css', request) - js_url = static_url('resources/2/foo.js', request) - return render_template_to_response('templates/my_template.pt', - css_url = css_url, - js_url = js_url) - -If the request "application URL" of the running system is -``http://example.com``, the ``css_url`` generated above would be: -``http://example.com/static1/foo.css``. The ``js_url`` generated -above would be ``'http://example.com/static2/foo.js``. - -One benefit of using the :func:`repoze.bfg.url.static_url` function -rather than constructing static URLs "by hand" is that if you need to -change the ``name`` of a static URL declaration in ZCML, the generated -URLs will continue to resolve properly after the rename. - -.. note:: The :func:`repoze.bfg.url.static_url` API is new in - :mod:`repoze.bfg` 1.1. - -.. index:: - pair: view; static resource - -Serving Static Resources Using a View Callable ----------------------------------------------- + pair: view; lookup -For more flexibility, static resources can be served by a view which -you register manually. For example, you may want static resources to -only be available when the ``context`` of the view is of a particular -type, or when the request is of a particular type. +.. _view_lookup: -The :class:`repoze.bfg.view.static` helper class is used to perform -this task. This class creates a callable that is capable acting as a -:mod:`repoze.bfg` view which serves static resources from a directory. -For instance, to serve files within a directory located on your -filesystem at ``/path/to/static/dir`` mounted at the URL path -``/static`` in your application, create an instance of the -:class:`repoze.bfg.view.static` class inside a ``static.py`` file in -your application root as below. +View Lookup +----------- -.. ignore-next-block -.. code-block:: python - :linenos: - - from repoze.bfg.view import static - static_view = static('/path/to/static/dir') - -.. note:: the argument to :class:`repoze.bfg.view.static` can also be - a relative pathname, e.g. ``my/static`` (meaning relative to the - Python package of the module in which the view is being defined). - It can also be a :term:`resource specification` - (e.g. ``anotherpackage:some/subdirectory``) or it can be a - "here-relative" path (e.g. ``some/subdirectory``). If the path is - "here-relative", it is relative to the package of the module in - which the static view is defined. - -Subsequently, you may wire this view up to be accessible as -``/static`` using either the -:mod:`repoze.bfg.configuration.Configurator.add_view` method or the -``<view>`` ZCML directive in your application's ``configure.zcml`` -against either the class or interface that represents your root -object. For example (ZCML): - -.. code-block:: xml - :linenos: - - <view - context=".models.Root" - view=".static.static_view" - name="static" - /> - -In this case, ``.models.Root`` refers to the class of which your -:mod:`repoze.bfg` application's root object is an instance. - -.. note:: You can also give a ``context`` of ``*`` if you want the - name ``static`` to be accessible as the static view against any - model. This will also allow ``/static/foo.js`` to work, but it - will allow for ``/anything/static/foo.js`` too, as long as - ``anything`` itself is resolvable. - -.. note:: To ensure that model objects contained in the root don't - "shadow" your static view (model objects take precedence during - traversal), or to ensure that your root object's ``__getitem__`` is - never called when a static resource is requested, you can refer to - your static resources as registered above in URLs as, - e.g. ``/@@static/foo.js``. This is completely equivalent to - ``/static/foo.js``. See :ref:`traversal_chapter` for information - about "goggles" (``@@``). - -.. index:: - triple: exceptions; special; view - -Special Exceptions ------------------- - -Usually when a Python exception is raised within view code, -:mod:`repoze.bfg` allows the exception to propagate all the way out to -the :term:`WSGI` server which invoked the application. - -However, for convenience, two special exceptions exist which are -always handled by :mod:`repoze.bfg` itself. These are -:exc:`repoze.bfg.exceptions.NotFound` and -:exc:`repoze.bfg.exceptions.Forbidden`. Both is an exception class -which accepts a single positional constructor argument: a ``message``. - -If :exc:`repoze.bfg.exceptions.NotFound` is raised within view code, -the result of the :term:`Not Found View` will be returned to the user -agent which performed the request. - -If :exc:`repoze.bfg.exceptions.Forbidden` is raised within view code, -the result of the :term:`Forbidden View` will be returned to the user -agent which performed the request. +:term:`View lookup` is the :mod:`repoze.bfg` subsystem responsible for +finding an invoking a :term:`view callable`. The view lookup +subsystem is passed a :term:`context`, a :term:`view name`, and the +:term:`request` object. These three bits of information are referred +to within this chapter as a :term:`triad`. -In all cases, the message provided to the exception constructor is -made available to the view which :mod:`repoze.bfg` invokes as -``request.environ['repoze.bfg.message']``. - -.. index:: - triple: view; forms; unicode - -Using Views to Handle Form Submissions (Unicode and Character Set Issues) -------------------------------------------------------------------------- - -Most web applications need to accept form submissions from web -browsers and various other clients. In :mod:`repoze.bfg`, form -submission handling logic is always part of a :term:`view`. For a -general overview of how to handle form submission data using the -:term:`WebOb` API, see `"Query and POST variables" within the WebOb -documentation -<http://pythonpaste.org/webob/reference.html#query-post-variables>`_. -:mod:`repoze.bfg` defers to WebOb for its request and response -implementations, and handling form submission data is a property of -the request implementation. Understanding WebOb's request API is the -key to understanding how to process form submission data. - -There are some defaults that you need to be aware of when trying to -handle form submission data in a :mod:`repoze.bfg` view. Because -having high-order (non-ASCII) characters in data contained within form -submissions is exceedingly common, and because the UTF-8 encoding is -the most common encoding used on the web for non-ASCII character data, -and because working and storing Unicode values is much saner than -working with and storing bytestrings, :mod:`repoze.bfg` configures the -:term:`WebOb` request machinery to attempt to decode form submission -values into Unicode from the UTF-8 character set implicitly. This -implicit decoding happens when view code obtains form field values via -the :term:`WebOb` ``request.params``, ``request.GET``, or -``request.POST`` APIs. - -For example, let's assume that the following form page is served up to -a browser client, and its ``action`` points at some :mod:`repoze.bfg` -view code: - -.. code-block:: xml - :linenos: - - <html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> - </head> - <form method="POST" action="myview"> - <div> - <input type="text" name="firstname"/> - </div> - <div> - <input type="text" name="lastname"/> - </div> - <input type="submit" value="Submit"/> - </form> - </html> +Many attributes of view configuration can be thought of like +"narrowers" or "predicates". In general, the greater number of +attributes possessed by a view's configuration, the more specific the +circumstances need to be before the registered view callable will be +invoked. -The ``myview`` view code in the :mod:`repoze.bfg` application *must* -expect that the values returned by ``request.params`` will be of type -``unicode``, as opposed to type ``str``. The following will work to -accept a form post from the above form: +For any given request, a view with five predicates will always be +found and evaluated before a view with two, for example. All +predicates must match for the associated view to be called. -.. code-block:: python - :linenos: +This does not mean however, that :mod:`repoze.bfg` "stops looking" +when it finds a view registration with predicates that don't match. +If one set of view predicates does not match, the "next most specific" +view (if any) view is consulted for predicates, and so on, until a +view is found, or no view can be matched up with the request. The +first view with a set of predicates all of which match the request +environment will be invoked. - def myview(request): - firstname = request.params['firstname'] - lastname = request.params['lastname'] +If no view can be found which has predicates which allow it to be +matched up with the request, :mod:`repoze.bfg` will return an error to +the user's browser, representing a "not found" (404) page. See +:ref:`changing_the_notfound_view` for more information about changing +the default notfound view. -But the following ``myview`` view code *may not* work, as it tries to -decode already-decoded (``unicode``) values obtained from -``request.params``: +There are a several exceptions to the the rule which says that view +configuration attributes represent "narrowings". Several attributes +of the ``view`` directive are *not* narrowing predicates. These are +``permission``, ``name``, ``renderer``, and ``attr``. -.. code-block:: python - :linenos: +The value of the ``permission`` attribute represents the permission +that must be possessed by the user to invoke any found view. When a +view is found that matches all predicates, but the invoking user does +not possess the permission implied by any associated ``permission`` in +the current context, processing stops, and an +:exc:`repoze.bfg.exception.Forbidden` error is raised, usually +resulting in the :term:`forbidden view` being shown to the invoking +user. No further view narrowing or view lookup is done. - def myview(request): - # the .decode('utf-8') will break below if there are any high-order - # characters in the firstname or lastname - firstname = request.params['firstname'].decode('utf-8') - lastname = request.params['lastname'].decode('utf-8') +.. note:: -For implicit decoding to work reliably, you must ensure that every -form you render that posts to a :mod:`repoze.bfg` view is rendered via -a response that has a ``;charset=UTF-8`` in its ``Content-Type`` -header; or, as in the form above, with a ``meta http-equiv`` tag that -implies that the charset is UTF-8 within the HTML ``head`` of the page -containing the form. This must be done explicitly because all known -browser clients assume that they should encode form data in the -character set implied by ``Content-Type`` value of the response -containing the form when subsequently submitting that form; there is -no other generally accepted way to tell browser clients which charset -to use to encode form data. If you do not specify an encoding -explicitly, the browser client will choose to encode form data in its -default character set before submitting it. The browser client may -have a non-UTF-8 default encoding. If such a request is handled by -your view code, when the form submission data is encoded in a non-UTF8 -charset, eventually the WebOb request code accessed within your view -will throw an error when it can't decode some high-order character -encoded in another character set within form data e.g. when -``request.params['somename']`` is accessed. + See :ref:`changing_the_forbidden_view` for more information about + changing the default forbidden view. -If you are using the :class:`webob.Response` class to generate a -response, or if you use the ``render_template_*`` templating APIs, the -UTF-8 charset is set automatically as the default via the -``Content-Type`` header. If you return a ``Content-Type`` header -without an explicit charset, a WebOb request will add a -``;charset=utf-8`` trailer to the ``Content-Type`` header value for -you for response content types that are textual (e.g. ``text/html``, -``application/xml``, etc) as it is rendered. If you are using your -own response object, you will need to ensure you do this yourself. +The value of the ``name`` attribute represents a direct match of the +view name returned via traversal. It is part of initial view lookup +rather than a predicate/narrower. -To avoid implicit form submission value decoding, so that the values -returned from ``request.params``, ``request.GET`` and ``request.POST`` -are returned as bytestrings rather than Unicode, add the following to -your application's ``configure.zcml``:: +The value of the ``renderer`` attribute represents the renderer used +to convert non-response return values from a view. - <subscriber for="repoze.bfg.interfaces.INewRequest" - handler="repoze.bfg.request.make_request_ascii"/> +The value of the ``attr`` attribute represents the attribute name +looked up on the view object to return a response. -You can then control form post data decoding "by hand" as necessary. -For example, when this subscriber is active, the second example above -will work unconditionally as long as you ensure that your forms are -rendered in a request that has a ``;charset=utf-8`` stanza on its -``Content-Type`` header. +.. _debug_notfound_section: -.. note:: The behavior that form values are decoded from UTF-8 to - Unicode implicitly was introduced in :mod:`repoze.bfg` 0.7.0. - Previous versions of :mod:`repoze.bfg` performed no implicit - decoding of form values (the default was to treat values as - bytestrings). +:exc:`NotFound` Errors +~~~~~~~~~~~~~~~~~~~~~~ -.. note:: Only the *values* of request params obtained via - ``request.params``, ``request.GET`` or ``request.POST`` are decoded - to Unicode objects implicitly in :mod:`repoze.bfg`'s default - configuration. The keys are still strings. +It's useful to be able to debug :exc:`NotFound` error responses when +they occur unexpectedly due to an application registry +misconfiguration. To debug these errors, use the +``BFG_DEBUG_NOTFOUND`` environment variable or the ``debug_notfound`` +configuration file setting. Details of why a view was not found will +be printed to ``stderr``, and the browser representation of the error +will include the same information. See :ref:`environment_chapter` for +more information about how and where to set these values. diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 3df1bac32..906aadb5e 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -1,5 +1,6 @@ .. index:: pair: ian; bicking + single: WebOb .. _webob_chapter: @@ -44,6 +45,7 @@ creating requests. .. index:: single: request object + single: request attributes; standard Request ~~~~~~~ @@ -105,7 +107,70 @@ instance, ``req.if_modified_since`` returns a `datetime <http://pythonpaste.org/webob/class-webob.Request.html>`_. .. index:: - pair: request; URL + pair: request attributes; special + +Special Attributes Added to the Request by :mod:`repoze.bfg` +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +In addition to the standard :term:`WebOb` attributes, +:mod:`repoze.bfg` adds the following special attributes to every +request. + +``req.subpath`` + + The traversal :term:`subpath` will be available as the ``subpath`` + attribute of the :term:`request` object. It will be a sequence + containing zero or more elements (which will be Unicode objects). + See :ref:`traversal_chapter` for information about the subpath. + +``req.view_name`` + + The :term:`view name` will be available as the ``view_name`` + attribute of the :term:`request` object. It will be a single string + (possibly the empty string if we're rendering a default view). + See :ref:`traversal_chapter` for information about view names. + +``req.root`` + + The :term:`root` object will be available as the ``root`` attribute + of the :term:`request` object. It will be the model object at which + traversal started (the root). See :ref:`traversal_chapter` for + information about root objects. + +``req.context`` + + The :term:`context` will be available as the ``context`` attribute + of the :term:`request` object. It will be the context object + implied by the current request. See :ref:`traversal_chapter` for + information about context objects. + +``req.traversed`` + + The "traversal path" will be as the ``traversed`` attribute of the + :term:`request` object. It will be a sequence representing the + ordered set of names that were used to traverse to the + :term:`context`, not including the view name or subpath. If there + is a virtual root associated with request, the virtual root path is + included within the traversal path. See :ref:`traversal_chapter` + for more information. + +``req.virtual_root`` + + The :term:`virtual root` will be available as the ``virtual_root`` + attribute of the :term:`request` object. It will be the virtual + root object implied by the current request. See + :ref:`vhosting_chapter` for more information about virtual roots. + +``req.virtual_root_path`` + + The :term:`virtual root` *path* will be available as the + ``virtual_root_path`` attribute of the :term:`request` object. It + will be a sequence representing the ordered set of names that were + used to traverse to the virtual root object. See + :ref:`vhosting_chapter` for more information about virtual roots. + +.. index:: + pair: request; URLs URLs ++++ @@ -130,7 +195,6 @@ of the request. I'll show various values for an example URL Gives a URL, relative to the current URL. If ``to_application`` is True, then resolves it relative to ``req.application_url``. - .. index:: pair: request; methods @@ -279,7 +343,7 @@ default to anything, though if you subclass ``Response`` and set ``default_content_type`` you can override this behavior. .. index:: - pair: WebOb; exceptions + pair: response; exceptions Exceptions ++++++++++ @@ -311,12 +375,15 @@ You can use this like: .. code-block:: python :linenos: + from webob.exc import HTTPException + from webob.exc import HTTPNotFound + def aview(request): try: # ... stuff ... raise HTTPNotFound('No such resource').exception except HTTPException, e: - return e(environ, start_response) + return request.get_response(e) The exceptions are still WSGI applications, but you cannot set attributes like ``content_type``, ``charset``, etc. on these exception @@ -326,7 +393,7 @@ objects. pair: WebOb; multidict Multidict -+++++++++ +~~~~~~~~~ Several parts of WebOb use a "multidict"; this is a dictionary where a key can have multiple values. The quintessential example is a query @@ -348,13 +415,3 @@ key/value pairs will show up. Similarly ``request.GET.keys()`` returns ``['pref', 'pref']``. Multidict is a view on a list of tuples; all the keys are ordered, and all the values are ordered. -.. index:: - triple: response; attributes; special - -Special :mod:`repoze.bfg` Attributes Added to the Request -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:mod:`repoze.bfg` adds special attributes to a request as the result -of :term:`traversal`. See :ref:`traversal_related_side_effects` for a -list of attributes added to the request by :mod:`repoze.bfg` itself. - diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index c2573ae55..21dd2f471 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -344,11 +344,11 @@ composite response. The response of the outermost wrapper view will be returned to the user. The wrapper view will be found as any view is found: see -:ref:`view_lookup_ordering`. The "best" wrapper view will be found -based on the lookup ordering: "under the hood" this wrapper view is -looked up via ``repoze.bfg.view.render_view_to_response(context, -request, 'wrapper_viewname')``. The context and request of a wrapper -view is the same context and request of the inner view. +:ref:`view_lookup`. The "best" wrapper view will be found based on +the lookup ordering: "under the hood" this wrapper view is looked up +via ``repoze.bfg.view.render_view_to_response(context, request, +'wrapper_viewname')``. The context and request of a wrapper view is +the same context and request of the inner view. If the ``wrapper`` attribute is unspecified in a view configuration, no view wrapping is done. diff --git a/docs/zcml.rst b/docs/zcml.rst index 528b9098a..be82741aa 100644 --- a/docs/zcml.rst +++ b/docs/zcml.rst @@ -193,9 +193,8 @@ evaluation to true or false when view lookup is performed. *All* predicates named in a view configuration must evaluate to true in order for the view callable it names to be considered "invokable" -for a given request. See :ref:`view_lookup_ordering` for a -description of how a view configuration matches (or doesn't match) -during a request. +for a given request. See :ref:`view_lookup` for a description of how +a view configuration matches (or doesn't match) during a request. The possible attributes of the ``view`` ZCML directive are described below. They are divided into predicate and non-predicate categories. @@ -280,12 +279,12 @@ Non-Predicate Attributes "chain" views together to form a composite response. The response of the outermost wrapper view will be returned to the user. The wrapper view will be found as any view is found: see - :ref:`view_lookup_ordering`. The "best" wrapper view will be found - based on the lookup ordering: "under the hood" this wrapper view is - looked up via ``repoze.bfg.view.render_view_to_response(context, - request, 'wrapper_viewname')``. The context and request of a wrapper - view is the same context and request of the inner view. If this - attribute is unspecified, no view wrapping is done. + :ref:`view_lookup`. The "best" wrapper view will be found based on + the lookup ordering: "under the hood" this wrapper view is looked up + via ``repoze.bfg.view.render_view_to_response(context, request, + 'wrapper_viewname')``. The context and request of a wrapper view is + the same context and request of the inner view. If this attribute + is unspecified, no view wrapping is done. .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. @@ -944,7 +943,7 @@ the same job as the ``scan`` ZCML directive. See Also ~~~~~~~~ -See also :ref:`mapping_views_to_urls_using_a_decorator_section`. +See also :ref:`mapping_views_using_a_decorator_section`. .. _resource_directive: diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 5fd5b5b1b..f9c70e02d 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -521,9 +521,9 @@ class Configurator(object): views together to form a composite response. The response of the outermost wrapper view will be returned to the user. The wrapper view will be found as any view is found: see - :ref:`view_lookup_ordering`. The "best" wrapper view will - be found based on the lookup ordering: "under the hood" this - wrapper view is looked up via + :ref:`view_lookup`. The "best" wrapper view will be found + based on the lookup ordering: "under the hood" this wrapper + view is looked up via ``repoze.bfg.view.render_view_to_response(context, request, 'wrapper_viewname')``. The context and request of a wrapper view is the same context and request of the inner view. If |
