From 590fe7c48d15f6280955e9784a9f42c2e28bca69 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jan 2010 07:36:12 +0000 Subject: Massive overhaul to deal with the reality that we don't map URLs directly to code. --- docs/narr/configuration.rst | 4 +- docs/narr/firstapp.rst | 349 ++------ docs/narr/router.rst | 2 +- docs/narr/security.rst | 29 +- docs/narr/static.rst | 225 +++++ docs/narr/traversal.rst | 167 ++-- docs/narr/urldispatch.rst | 649 +++++++++----- docs/narr/urlmapping.rst | 92 +- docs/narr/views.rst | 2090 ++++++++++++++++++++----------------------- docs/narr/webob.rst | 87 +- 10 files changed, 1959 insertions(+), 1735 deletions(-) create mode 100644 docs/narr/static.rst (limited to 'docs/narr') 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 ```` tags in +:ref:`adding_configuration`), the relative order of ```` 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: + + + +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: + + + +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: + + + +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: + + + + + +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 +```` 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: + + + +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). @@ -372,67 +456,9 @@ There are two special cases: views that may have the same names as model instance names in the 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 -```` 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 -```` 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 -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 ```` 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: -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 -```` Statement Examples ------------------------------- -Let's check out some examples of how ```` 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: -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: + + + +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/ - /users/ - /tags/ - -When a URL matches the pattern ``/ideas/``, 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: - -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' + -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 + + +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: - -- by using the :meth:`repoze.bfg.configuration.Configurator.add_view` - method. - -- by adding a ```` declaration to :term:`ZCML` used by your - application (see :ref:`view_directive`). - -- by running a :term:`scan` against application source code which has - a :class:`repoze.bfg.view.bfg_view` decorator attached to a Python - object. +.. note:: -Each of these mechanisms is completely equivalent to the other. + 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*. -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: +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 using the :meth:`repoze.bfg.configuration.Configurator.add_route` - method +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. -- by adding a ```` declaration to :term:`ZCML` used by - your application. +View Callables +-------------- -See :ref:`urldispatch_chapter` for more information on mapping URLs to -views using routes. +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. -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 @@ -240,6 +233,28 @@ to construct a response. The associated renderer can be varied for a 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 @@ -247,7 +262,7 @@ configuration. See :ref:`views_which_use_a_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,96 @@ Additional renderers can be added to the system as necessary via a ZCML directive (see :ref:`adding_and_overriding_renderers`). .. index:: - single: view configuration - pair: view; configuration + pair: renderers; built-in -.. _view_configuration: +.. _built_in_renderers: -View Configuration: Mapping View Callables to URLs --------------------------------------------------- +Built-In Renderers +~~~~~~~~~~~~~~~~~~ -: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. +Several built-in "renderers" exist in :mod:`repoze.bfg`. These +renderers can be used in the ``renderer`` attribute of view +configurations. .. index:: - triple: zcml; view; configuration + pair: renderer; string -.. _mapping_views_to_urls_using_zcml_section: +``string``: String Renderer ++++++++++++++++++++++++++++ -View Configuration Via ZCML -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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 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 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: @@ -338,28 +418,64 @@ 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``. +Views which use the JSON renderer can vary non-body response +attributes by attaching properties to the request. See +:ref:`response_request_attrs`. -.. note:: Values prefixed with a period (``.``) for the ``context`` - and ``view`` attributes of a ``view`` declaration (such as those - above) mean "relative to the Python package directory in which this - :term:`ZCML` file is stored". So if the above ``view`` declaration - was made inside a ``configure.zcml`` file that lived in the - ``hello`` package, you could replace the relative ``.models.Hello`` - with the absolute ``hello.models.Hello``; likewise you could - replace the relative ``.views.hello_world`` with the absolute - ``hello.views.hello_world``. 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: renderer; chameleon -You can also declare a *default view callable* for a model type: +.. _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: @@ -367,622 +483,445 @@ 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. - -You can also declare that a view callable is good for any model type -by using the special ``*`` character in the ``context`` attribute: +Here's an example view configuration which uses a Chameleon text +renderer: .. code-block:: xml :linenos: -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. - -A ZCML ``view`` declaration's ``view`` attribute can also name a -class. In this case, the rules described in :ref:`class_as_view` -apply for the class which is named. - -See :ref:`view_directive` for complete ZCML directive documentation. +Views with use a Chameleon renderer can vary response attributes by +attaching properties to the request. See +:ref:`response_request_attrs`. .. index:: - triple: view; bfg_view; decorator + pair: renderer; response attributes + pair: renderer; changing headers + triple: headers; changing; renderer -.. _mapping_views_to_urls_using_a_decorator_section: +.. _response_request_attrs: -View Configuration Using the ``@bfg_view`` Decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Varying Attributes of Rendered Responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For better locality of reference, you may use the -:class:`repoze.bfg.view.bfg_view` decorator to associate your view -functions with URLs instead of using :term:`ZCML` or imperative -configuration for the same purpose. +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. -:class:`repoze.bfg.view.bfg_view` can be used to associate -``context``, ``name``, ``permission`` and ``request_method``, -``containment``, ``request_param`` and ``request_type``, ``attr``, -``renderer``, ``wrapper``, ``xhr``, ``accept``, and ``header`` -information -- as done via the equivalent ZCML -- with a function that -acts as a :mod:`repoze.bfg` view callable. All ZCML attributes (save -for the ``view`` attribute) are available in decorator form and mean -precisely the same thing. +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. -The mere existence of a ``@bfg_view`` decorator doesn't suffice to -perform view configuration. To make :mod:`repoze.bfg` process your -:class:`repoze.bfg.view.bfg_view` declarations, you *must* do one of -the following: +``response_content_type`` -- If you are using :term:`ZCML`, insert the following boilerplate into - your application's ``configure.zcml``: + Defines the content-type of the resulting response, + e.g. ``text/xml``. - .. code-block:: xml +``response_headerlist`` - - -- If you are using :term:`imperative configuration`, use the ``scan`` - method of a :class:`repoze.bfg.configuration.Configurator`: - - .. code-block:: python + A sequence of tuples describing cookie values that should be set in + the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header', + 'foo')]``. - # config is assumed to be an instance of the - # repoze.bfg.configuration.Configurator class - config.scan() +``response_status`` -If you invoke a scan, you will not need to use ZCML or imperative -configuration to create :mod:`repoze.bfg` view declarations. Instead, -you will be able to do all the work in -:class:`repoze.bfg.view.bfg_view` decorators. + A WSGI-style status code (e.g. ``200 OK``) describing the status of + the response. -Please see :ref:`decorations_and_code_scanning` for detailed -information about what happens when code is scanned for configuration -declarations resulting from use of decorators like -:class:`repoze.bfg.view.bfg_view`. +``response_charset`` -See :ref:`configuration_module` for additional API arguments to the -:meth:`repoze.bfg.configuration.Configurator.scan` method. For -example, the method allows you to supply a ``package`` argument to -better control exactly *which* code will be scanned. This is the same -value implied by the ``package`` attribute of the ZCML ```` -directive (see :ref:`scan_directive`). + The character set (e.g. ``UTF-8``) of the response. -.. warning:: using this feature tends to slows down application - startup slightly, as more work is performed at application startup - to scan for view declarations. Additionally, if you use - decorators, it means that other people will not be able to override - your view declarations externally using ZCML: this is a common - requirement if you're developing an extensible application (e.g. a - framework). See :ref:`extending_chapter` for more information - about building extensible applications. +``response_cache_for`` -An example of the :class:`repoze.bfg.view.bfg_view` decorator might -reside in a bfg application module ``views.py``: + 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. -.. ignore-next-block -.. code-block:: python - :linenos: +.. index:: + pair: renderers; adding - from models import MyModel - from repoze.bfg.view import bfg_view - from repoze.bfg.chameleon_zpt import render_template_to_response +.. _adding_and_overriding_renderers: - @bfg_view(name='my_view', request_method='POST', context=MyModel, - permission='read', renderer='templates/my.pt') - def my_view(request): - return {'a':1} +Adding and Overriding Renderers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Using this decorator as above replaces the need to add this ZCML to -your application registry: +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. -.. code-block:: xml - :linenos: +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 -Or replaces the need to add this imperative configuration stanza: + .. code-block:: xml + :linenos: -.. ignore-next-block -.. code-block:: python + - config.add_view(name='my_view', request_method='POST', context=MyModel, - permission='read') + The ``factory`` attribute is a :term:`dotted Python name` that must + point to an implementation of a :term:`renderer`. -``@bfg_view`` Arguments -+++++++++++++++++++++++ + The ``name`` attribute is the renderer name. -All arguments to :class:`repoze.bfg.view.bfg_view` are optional. -Every argument to :class:`repoze.bfg.view.bfg_view` matches the -meaning of the same-named attribute in ZCML view configuration -described in :ref:`view_directive`. +.. topic:: Via Imperative Configuration -If ``name`` is not supplied, the empty string is used (implying -the default view). + .. code-block:: python + :linenos: -If ``attr`` is not supplied, ``None`` is used (implying the function -itself if the view is a function, or the ``__call__`` callable -attribute if the view is a class). + from my.package import MyJinja2Renderer + config.add_renderer('.jinja2', MyJinja2Renderer) -If ``renderer`` is not supplied, ``None`` is used (meaning that no -renderer is associated with this view). + The first argument is the renderer name. -If ``request_type`` is not supplied, the value ``None`` is used, -implying any request type. Otherwise, this should be a class or -interface. + The second argument is a reference to an to an implementation of a + :term:`renderer`. -If ``context`` is not supplied, the interface -:class:`zope.interface.Interface` (which matches any model) is used. -``context`` can also name a class, like its ZCML brother. An alias for -``context`` is ``for_`` (``for_`` is an older spelling). +A renderer implementation is usually a class which has the following +interface: -If ``permission`` is not supplied, no permission is registered for -this view (it's accessible by any caller). +.. code-block:: python + :linenos: -If ``wrapper`` is not supplied, no wrapper view is used. + class RendererFactory: + def __init__(self, name): + """ Constructor: ``name`` may be a path """ -If ``route_name`` is supplied, the view will be invoked only if the -named route matches. *This is an advanced feature, not often used by -"civilians"*. + 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``). """ -If ``request_method`` is supplied, the view will be invoked only if -the ``REQUEST_METHOD`` of the request matches the value. +There are essentially two different kinds of ``renderer`` +registrations: registrations that use a dot (``.``) in their ``name`` +argument and ones which do not. -If ``request_param`` is supplied, the view will be invoked only if the -``request.params`` data structure contains a key matching the value -provided. +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). -If ``containment`` is supplied, the view will be invoked only if a -location parent supplies the interface or class implied by the -provided value. +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. -If ``xhr`` is specified, it must be a boolean value. If the value is -``True``, the view will only be invoked if the request's -``X-Requested-With`` header has the value ``XMLHttpRequest``. +Here's an example of a renderer registration in ZCML: -If ``accept`` is specified, it must be a mimetype value. If -``accept`` is specified, the view will only be invoked if the -``Accept`` HTTP header matches the value requested. See the -description of ``accept`` in :ref:`view_directive` for information -about the allowable composition and matching behavior of this value. +.. code-block:: xml + :linenos: -If ``header`` is specified, it must be a header name or a -``headername:headervalue`` pair. If ``header`` is specified, and -possesses a value the view will only be invoked if an HTTP header -matches the value requested. If ``header`` is specified without a -value (a bare header name only), the view will only be invoked if the -HTTP header exists with any value in the request. See the description -of ``header`` in :ref:`view_directive` for information about the -allowable composition and matching behavior of this value. + -All arguments may be omitted. For example: +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 webob import Response from repoze.bfg.view import bfg_view - @bfg_view() - def my_view(request): - """ My view """ - return Response() + @bfg_view(renderer='amf') + def myview(request): + return {'Hello':'world'} -Such a registration as the one directly above implies that the view -name will be ``my_view``, registered with a ``context`` argument that -matches any model type, using no permission, registered against -requests with any request method / request type / request param / -route name / containment. +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: -``@bfg_view`` Placement -+++++++++++++++++++++++ +.. code-block:: xml + :linenos: -A :class:`repoze.bfg.view.bfg_view` decorator can be placed in various -points in your application. + -If your view callable is a function, it may be used as a function -decorator: +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:: python +.. code-block:: xml :linenos: - from repoze.bfg.view import bfg_view - from webob import Response - - @bfg_view(name='edit') - def edit(request): - return Response('edited!') + -If your view callable is a class, the decorator can also be used as a -class decorator in Python 2.6 and better (Python 2.5 and below do not -support class decorators). All the arguments to the decorator are the -same when applied against a class as when they are applied against a -function. For example: +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:: python +.. code-block:: xml :linenos: - from webob import Response - from repoze.bfg.view import bfg_view - - @bfg_view() - class MyView(object): - def __init__(self, request): - self.request = request + - def __call__(self): - return Response('hello') - -You can use the :class:`repoze.bfg.view.bfg_view` decorator as a -simple callable to manually decorate classes in Python 2.5 and below -(without the decorator syntactic sugar), if you wish: - -.. code-block:: python - :linenos: - - from webob import Response - from repoze.bfg.view import bfg_view - - class MyView(object): - def __init__(self, request): - self.request = request - - def __call__(self): - return Response('hello') - - my_view = bfg_view()(MyView) - -More than one :class:`repoze.bfg.view.bfg_view` decorator can be -stacked on top of any number of others. Each decorator creates a -separate view registration. For example: - -.. code-block:: python - :linenos: - - from repoze.bfg.view import bfg_view - from webob import Response - - @bfg_view(name='edit') - @bfg_view(name='change') - def edit(request): - return Response('edited!') - -This registers the same view under two different names. - -.. note:: :class:`repoze.bfg.view.bfg_view` decorator stacking is a - feature new in :mod:`repoze.bfg` 1.1. Previously, these decorators - could not be stacked without the effect of the "upper" decorator - cancelling the effect of the the decorator "beneath" it. - -The decorator can also be used against class methods: - -.. code-block:: python - :linenos: - - from webob import Response - from repoze.bfg.view import bfg_view - - class MyView(object): - def __init__(self, request): - self.request = request - - @bfg_view(name='hello') - def amethod(self): - return Response('hello') - -When the decorator is used against a class method, a view is -registered for the *class*, so the class constructor must accept -either ``request`` or ``context, request``. The method which is -decorated must return a response (or rely on a :term:`renderer` to -generate one). Using the decorator against a particular method of a -class is equivalent to using the ``attr`` parameter in a decorator -attached to the class itself. For example, the above registration -implied by the decorator being used against the ``amethod`` method -could be spelled equivalently as the below: +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:: python +.. code-block:: xml :linenos: - from webob import Response - from repoze.bfg.view import bfg_view - - @bfg_view(attr='amethod', name='hello') - class MyView(object): - def __init__(self, request): - self.request = request - - def amethod(self): - return Response('hello') + -.. note:: The ability to use the :class:`repoze.bfg.view.bfg_view` - decorator as a method decorator is new in :mod:`repoze.bfg` - version 1.1. Previously it could only be used as a class or - function decorator. +See also :ref:`renderer_directive`. .. index:: - single: add_view - triple: imperative; adding; view + triple: exceptions; special; view -View Configuration Using the ``add_view`` Method of a Configurator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using Special Exceptions In View Callables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The :meth:`repoze.bfg.configuration.Configurator.add_view` method -within :ref:`configuration_module` is used to configure a view -imperatively. The arguments to this method are very similar to the -arguments that you provide to the ``@bfg_view`` decorator. For -example: +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. -.. code-block:: python - :linenos: +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``. - from webob import Response +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. - def hello_world(request): - return Response('hello!') +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. - # config is assumed to be an instance of the - # repoze.bfg.configuration.Configurator class - config.add_view(hello_world, name='hello.html') +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:: - 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. + triple: view; forms; unicode -The value of the ``attr`` attribute represents the attribute name -looked up on the view object to return a response. +Handling Form Submissions in View Callables (Unicode and Character Set Issues) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. index:: - pair: model; interfaces +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 +`_. +: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. -.. _using_model_interfaces: +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. -Using Model Interfaces In View Configuration --------------------------------------------- +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: -Instead of registering your views with a ``context`` that names a -Python model *class* as a context, you can optionally register a view -callable for an :term:`interface`. Since an interface can be attached -arbitrarily to any model instance (as opposed to its identity being -implied by only its class), associating a view with an interface can -provide more flexibility for sharing a single view between two or more -different implementations of a model type. For example, if two model -object instances of different Python class types share the same -interface, you can use the same view against each of them. +.. code-block:: xml + :linenos: -In order to make use of interfaces in your application during view -dispatch, you must create an interface and mark up your model classes -or instances with interface declarations that refer to this interface. + + + + +
+
+ +
+
+ +
+ +
+ -To attach an interface to a model *class*, you define the interface -and use the :func:`zope.interface.implements` function to associate -the interface with the class. +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: - from zope.interface import Interface - from zope.interface import implements - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - implements(IHello) + def myview(request): + firstname = request.params['firstname'] + lastname = request.params['lastname'] -To attach an interface to a model *instance*, you define the interface -and use the :func:`zope.interface.alsoProvides` function to associate -the interface with the instance. This function mutates the instance -in such a way that the interface is attached to it. +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: - from zope.interface import Interface - from zope.interface import alsoProvides + 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') - class IHello(Interface): - """ A marker interface """ +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. - class Hello(object): - pass +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. - def make_hello(): - hello = Hello() - alsoProvides(hello, IHello) - return hello +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``:: -Regardless of how you associate an interface with a model instance or -a model class, the resulting ZCML to associate that interface with a -view callable is the same. Assuming the above code that defines an -``IHello`` interface lives in the root of your application, and its -module is named "models.py", the below interface declaration will -associate the ``.views.hello_world`` view with models that implement -(aka provide) this interface. + -.. code-block:: xml - :linenos: +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). -Any time a model that is determined to be the :term:`context` provides -this interface, and a view named ``hello.html`` is looked up against -it as per the URL, the ``.views.hello_world`` view callable will be -invoked. +.. 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. -Note that views registered against a model class take precedence over -views registered for any interface the model class implements when an -ambiguity arises. If a view is registered for both the class type of -the context and an interface implemented by the context's class, the -view registered for the context's class will "win". -For more information about defining models with interfaces for use -within view configuration, see -:ref:`models_which_implement_interfaces`. .. index:: - pair: renderers; built-in + single: view configuration + pair: view; configuration -.. _built_in_renderers: +.. _view_configuration: -Built-In Renderers +View Configuration ------------------ -Several built-in "renderers" exist in :mod:`repoze.bfg`. These -renderers can be used in the ``renderer`` attribute of view -configurations. +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. -.. 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. +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`. -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: +View configuration is performed in one of three ways: -.. code-block:: python - :linenos: +- by adding a ```` declaration to :term:`ZCML` used by your + application (see :ref:`mapping_views_using_zcml_section` and + :ref:`view_directive`). - from webob import Response - from repoze.bfg.view import bfg_view +- 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`). - @bfg_view(renderer='string') - def hello_world(request): - return {'content':'Hello!'} +- 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`). -The body of the response returned by such a view will be a string -representing the ``str()`` serialization of the return value: +Each of these mechanisms is completely equivalent to the other. -.. code-block: python - :linenos: +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: - {'content': 'Hello!'} +- by using the :meth:`repoze.bfg.configuration.Configurator.add_route` + method. -Views which use the string renderer can vary non-body response -attributes by attaching properties to the request. See -:ref:`response_request_attrs`. +- by adding a ```` declaration to :term:`ZCML` used by + your application. .. 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: + triple: zcml; view; configuration - '{"content": "Hello!"}' +.. _mapping_views_using_zcml_section: -The return value needn't be a dictionary, but the return value must -contain values renderable by :func:`json.dumps`. +View Configuration Via ZCML +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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.: +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: @@ -990,64 +929,31 @@ You can configure a view to use the JSON renderer in ZCML by naming -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. +The above maps the ``.views.hello_world`` view callable function to +the following set of :term:`context finding` results: -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. +- A :term:`context` object which is an instance (or subclass) of the + Python class represented by ``.models.Hello`` -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). +- A :term:`view name` equalling ``hello.html``. -Here's an example view configuration which uses a Chameleon ZPT -renderer: +.. note:: Values prefixed with a period (``.``) for the ``context`` + and ``view`` attributes of a ``view`` declaration (such as those + above) mean "relative to the Python package directory in which this + :term:`ZCML` file is stored". So if the above ``view`` declaration + was made inside a ``configure.zcml`` file that lived in the + ``hello`` package, you could replace the relative ``.models.Hello`` + with the absolute ``hello.models.Hello``; likewise you could + replace the relative ``.views.hello_world`` with the absolute + ``hello.views.hello_world``. 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. + +You can also declare a *default view callable* for a model type: .. code-block:: xml :linenos: @@ -1055,12 +961,15 @@ renderer: -Here's an example view configuration which uses a Chameleon text -renderer: +A *default view callable* simply has no ``name`` attribute. When a +: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: @@ -1068,645 +977,584 @@ renderer: -Views with use a Chameleon renderer can vary response attributes by -attaching properties to the request. See -:ref:`response_request_attrs`. +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: + + + +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` +apply for the class which is named. + +See :ref:`view_directive` for complete ZCML directive documentation. .. index:: - pair: renderer; response attributes - pair: renderer; changing headers - triple: headers; changing; renderer + triple: view; bfg_view; decorator -.. _response_request_attrs: +.. _mapping_views_using_a_decorator_section: -Varying Attributes of Rendered Responses ----------------------------------------- +View Configuration Using the ``@bfg_view`` Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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. +For better locality of reference, you may use the +:class:`repoze.bfg.view.bfg_view` decorator to associate your view +functions with URLs instead of using :term:`ZCML` or imperative +configuration for the same purpose. -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. +:class:`repoze.bfg.view.bfg_view` can be used to associate +``context``, ``name``, ``permission`` and ``request_method``, +``containment``, ``request_param`` and ``request_type``, ``attr``, +``renderer``, ``wrapper``, ``xhr``, ``accept``, and ``header`` +information -- as done via the equivalent ZCML -- with a function that +acts as a :mod:`repoze.bfg` view callable. All ZCML attributes (save +for the ``view`` attribute) are available in decorator form and mean +precisely the same thing. -``response_content_type`` +The mere existence of a ``@bfg_view`` decorator doesn't suffice to +perform view configuration. To make :mod:`repoze.bfg` process your +:class:`repoze.bfg.view.bfg_view` declarations, you *must* do one of +the following: - Defines the content-type of the resulting response, - e.g. ``text/xml``. +- If you are using :term:`ZCML`, insert the following boilerplate into + your application's ``configure.zcml``: -``response_headerlist`` + .. code-block:: xml - 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`` +- If you are using :term:`imperative configuration`, use the ``scan`` + method of a :class:`repoze.bfg.configuration.Configurator`: - A WSGI-style status code (e.g. ``200 OK``) describing the status of - the response. + .. code-block:: python -``response_charset`` + # config is assumed to be an instance of the + # repoze.bfg.configuration.Configurator class + config.scan() - The character set (e.g. ``UTF-8``) of the response. +If you invoke a scan, you will not need to use ZCML or imperative +configuration to create :mod:`repoze.bfg` view declarations. Instead, +you will be able to do all the work in +:class:`repoze.bfg.view.bfg_view` decorators. -``response_cache_for`` +Please see :ref:`decorations_and_code_scanning` for detailed +information about what happens when code is scanned for configuration +declarations resulting from use of decorators like +:class:`repoze.bfg.view.bfg_view`. - 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. +See :ref:`configuration_module` for additional API arguments to the +:meth:`repoze.bfg.configuration.Configurator.scan` method. For +example, the method allows you to supply a ``package`` argument to +better control exactly *which* code will be scanned. This is the same +value implied by the ``package`` attribute of the ZCML ```` +directive (see :ref:`scan_directive`). -.. index:: - pair: renderers; adding +.. warning:: using this feature tends to slows down application + startup slightly, as more work is performed at application startup + to scan for view declarations. Additionally, if you use + decorators, it means that other people will not be able to override + your view declarations externally using ZCML: this is a common + requirement if you're developing an extensible application (e.g. a + framework). See :ref:`extending_chapter` for more information + about building extensible applications. -.. _adding_and_overriding_renderers: +An example of the :class:`repoze.bfg.view.bfg_view` decorator might +reside in a bfg application module ``views.py``: -Adding and Overriding Renderers -------------------------------- +.. ignore-next-block +.. code-block:: python + :linenos: -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. + from models import MyModel + from repoze.bfg.view import bfg_view + from repoze.bfg.chameleon_zpt import render_template_to_response -For example, to add a renderer which renders views which have a -``renderer`` attribute that is a path that ends in ``.jinja2``: + @bfg_view(name='my_view', request_method='POST', context=MyModel, + permission='read', renderer='templates/my.pt') + def my_view(request): + return {'a':1} -.. topic:: Via ZCML +Using this decorator as above replaces the need to add this ZCML to +your application registry: - .. code-block:: xml - :linenos: +.. code-block:: xml + :linenos: - + - The ``factory`` attribute is a :term:`dotted Python name` that must - point to an implementation of a :term:`renderer`. +Or replaces the need to add this imperative configuration stanza: - The ``name`` attribute is the renderer name. +.. ignore-next-block +.. code-block:: python -.. topic:: Via Imperative Configuration + config.add_view(name='my_view', request_method='POST', context=MyModel, + permission='read') - .. code-block:: python - :linenos: +``@bfg_view`` Arguments ++++++++++++++++++++++++ - from my.package import MyJinja2Renderer - config.add_renderer('.jinja2', MyJinja2Renderer) +All arguments to :class:`repoze.bfg.view.bfg_view` are optional. +Every argument to :class:`repoze.bfg.view.bfg_view` matches the +meaning of the same-named attribute in ZCML view configuration +described in :ref:`view_directive`. - The first argument is the renderer name. +If ``name`` is not supplied, the empty string is used (implying +the default view). - The second argument is a reference to an to an implementation of a - :term:`renderer`. +If ``attr`` is not supplied, ``None`` is used (implying the function +itself if the view is a function, or the ``__call__`` callable +attribute if the view is a class). -A renderer implementation is usually a class which has the following -interface: +If ``renderer`` is not supplied, ``None`` is used (meaning that no +renderer is associated with this view). -.. code-block:: python - :linenos: +If ``request_type`` is not supplied, the value ``None`` is used, +implying any request type. Otherwise, this should be a class or +interface. - class RendererFactory: - def __init__(self, name): - """ Constructor: ``name`` may be a path """ +If ``context`` is not supplied, the interface +:class:`zope.interface.Interface` (which matches any model) is used. +``context`` can also name a class, like its ZCML brother. An alias for +``context`` is ``for_`` (``for_`` is an older spelling). - 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``). """ +If ``permission`` is not supplied, no permission is registered for +this view (it's accessible by any caller). -There are essentially two different kinds of ``renderer`` -registrations: registrations that use a dot (``.``) in their ``name`` -argument and ones which do not. +If ``wrapper`` is not supplied, no wrapper view is used. -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). +If ``route_name`` is supplied, the view will be invoked only if the +named route matches. *This is an advanced feature, not often used by +"civilians"*. + +If ``request_method`` is supplied, the view will be invoked only if +the ``REQUEST_METHOD`` of the request matches the value. -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. +If ``request_param`` is supplied, the view will be invoked only if the +``request.params`` data structure contains a key matching the value +provided. -Here's an example of a renderer registration in ZCML: +If ``containment`` is supplied, the view will be invoked only if a +location parent supplies the interface or class implied by the +provided value. -.. code-block:: xml - :linenos: +If ``xhr`` is specified, it must be a boolean value. If the value is +``True``, the view will only be invoked if the request's +``X-Requested-With`` header has the value ``XMLHttpRequest``. - +If ``accept`` is specified, it must be a mimetype value. If +``accept`` is specified, the view will only be invoked if the +``Accept`` HTTP header matches the value requested. See the +description of ``accept`` in :ref:`view_directive` for information +about the allowable composition and matching behavior of this value. -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: +If ``header`` is specified, it must be a header name or a +``headername:headervalue`` pair. If ``header`` is specified, and +possesses a value the view will only be invoked if an HTTP header +matches the value requested. If ``header`` is specified without a +value (a bare header name only), the view will only be invoked if the +HTTP header exists with any value in the request. See the description +of ``header`` in :ref:`view_directive` for information about the +allowable composition and matching behavior of this value. + +All arguments may be omitted. For example: .. code-block:: python :linenos: + from webob import Response from repoze.bfg.view import bfg_view - @bfg_view(renderer='amf') - def myview(request): - return {'Hello':'world'} + @bfg_view() + def my_view(request): + """ My view """ + return Response() -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: +Such a registration as the one directly above implies that the view +name will be ``my_view``, registered with a ``context`` argument that +matches any model type, using no permission, registered against +requests with any request method / request type / request param / +route name / containment. -.. code-block:: xml - :linenos: +``@bfg_view`` Placement ++++++++++++++++++++++++ - +A :class:`repoze.bfg.view.bfg_view` decorator can be placed in various +points in your application. -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: +If your view callable is a function, it may be used as a function +decorator: -.. code-block:: xml +.. code-block:: python :linenos: - - -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: + from repoze.bfg.view import bfg_view + from webob import Response - + @bfg_view(name='edit') + def edit(request): + return Response('edited!') -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): +If your view callable is a class, the decorator can also be used as a +class decorator in Python 2.6 and better (Python 2.5 and below do not +support class decorators). All the arguments to the decorator are the +same when applied against a class as when they are applied against a +function. For example: -.. code-block:: xml +.. code-block:: python :linenos: - - -See also :ref:`renderer_directive`. - -.. index:: - pair: view; security + from webob import Response + from repoze.bfg.view import bfg_view -.. _view_security_section: + @bfg_view() + class MyView(object): + def __init__(self, request): + self.request = request -View Security -------------- + def __call__(self): + return Response('hello') -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: +You can use the :class:`repoze.bfg.view.bfg_view` decorator as a +simple callable to manually decorate classes in Python 2.5 and below +(without the decorator syntactic sugar), if you wish: -.. code-block:: xml +.. code-block:: python :linenos: - - -When an authentication policy is enabled, this view will be protected -with the ``add`` permission. The view will *not be called* if the -user does not possess the ``add`` permission relative to the current -:term:`context` and an authorization policy is enabled. Instead the -:term:`forbidden view` result will be returned to the client (see -:ref:`changing_the_forbidden_view`). - -.. note:: + from webob import Response + from repoze.bfg.view import bfg_view - See the :ref:`security_chapter` chapter to find out how to turn on - an authentication policy. + class MyView(object): + def __init__(self, request): + self.request = request -.. index:: - pair: view; http redirect + def __call__(self): + return Response('hello') -Using a View Callable to Do A HTTP Redirect -------------------------------------------- + my_view = bfg_view()(MyView) -You can issue an HTTP redirect from within a view by returning a -slightly different response. +More than one :class:`repoze.bfg.view.bfg_view` decorator can be +stacked on top of any number of others. Each decorator creates a +separate view registration. For example: .. code-block:: python :linenos: - from webob.exc import HTTPFound + from repoze.bfg.view import bfg_view + from webob import Response - def myview(request): - return HTTPFound(location='http://example.com') + @bfg_view(name='edit') + @bfg_view(name='change') + def edit(request): + return Response('edited!') -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. +This registers the same view under two different names. -.. index:: - triple: view; zcml; static resource - single: add_static_view +.. note:: :class:`repoze.bfg.view.bfg_view` decorator stacking is a + feature new in :mod:`repoze.bfg` 1.1. Previously, these decorators + could not be stacked without the effect of the "upper" decorator + cancelling the effect of the the decorator "beneath" it. -.. _static_resources_section: +The decorator can also be used against class methods: -Serving Static Resources Using a ZCML Directive ------------------------------------------------ +.. code-block:: python + :linenos: -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``. + from webob import Response + from repoze.bfg.view import bfg_view -Use of the ``add_static_view`` imperative configuration method is -completely equivalent to using ZCML for the same purpose. + class MyView(object): + def __init__(self, request): + self.request = request -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. + @bfg_view(name='hello') + def amethod(self): + return Response('hello') -.. code-block:: xml +When the decorator is used against a class method, a view is +registered for the *class*, so the class constructor must accept +either ``request`` or ``context, request``. The method which is +decorated must return a response (or rely on a :term:`renderer` to +generate one). Using the decorator against a particular method of a +class is equivalent to using the ``attr`` parameter in a decorator +attached to the class itself. For example, the above registration +implied by the decorator being used against the ``amethod`` method +could be spelled equivalently as the below: + +.. code-block:: python :linenos: - + from webob import Response + from repoze.bfg.view import bfg_view + + @bfg_view(attr='amethod', name='hello') + class MyView(object): + def __init__(self, request): + self.request = request -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``. + def amethod(self): + return Response('hello') -.. code-block:: xml - :linenos: +.. note:: The ability to use the :class:`repoze.bfg.view.bfg_view` + decorator as a method decorator is new in :mod:`repoze.bfg` + version 1.1. Previously it could only be used as a class or + function decorator. - +.. index:: + single: add_view + triple: imperative; adding; view -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. +.. _mapping_views_using_imperative_config_section: -.. code-block:: xml - :linenos: +View Configuration Using the ``add_view`` Method of a Configurator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +The :meth:`repoze.bfg.configuration.Configurator.add_view` method +within :ref:`configuration_module` is used to configure a view +imperatively. The arguments to this method are very similar to the +arguments that you provide to the ``@bfg_view`` decorator. For +example: -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. +.. code-block:: python + :linenos: -See :ref:`static_directive` for detailed information. + from webob import Response -.. note:: The :ref:`static_directive` ZCML directive is new in - :mod:`repoze.bfg` 1.1. + def hello_world(request): + return Response('hello!') -.. note:: The - :meth:`repoze.bfg.configuration.Configurator.add_static_view` - method offers an imperative equivalent to the ``static`` ZCML - directive. + # config is assumed to be an instance of the + # repoze.bfg.configuration.Configurator class + config.add_view(hello_world, name='hello.html') .. index:: - triple: generating; static resource; urls - -.. _generating_static_resource_urls: + pair: model; interfaces -Generating Static Resource URLs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _using_model_interfaces: -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. +Using Model Interfaces In View Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For example, let's assume you create a set of ``static`` declarations -in ZCML like so: +Instead of registering your views with a ``context`` that names a +Python model *class* as a context, you can optionally register a view +callable for an :term:`interface`. Since an interface can be attached +arbitrarily to any model instance (as opposed to its identity being +implied by only its class), associating a view with an interface can +provide more flexibility for sharing a single view between two or more +different implementations of a model type. For example, if two model +object instances of different Python class types share the same +interface, you can use the same view against each of them. -.. code-block:: xml - :linenos: +In order to make use of interfaces in your application during view +dispatch, you must create an interface and mark up your model classes +or instances with interface declarations that refer to this interface. - - - - -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: +To attach an interface to a model *class*, you define the interface +and use the :func:`zope.interface.implements` function to associate +the interface with the class. .. 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) + from zope.interface import Interface + from zope.interface import implements -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``. + class IHello(Interface): + """ A marker interface """ -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. + class Hello(object): + implements(IHello) -.. note:: The :func:`repoze.bfg.url.static_url` API is new in - :mod:`repoze.bfg` 1.1. +To attach an interface to a model *instance*, you define the interface +and use the :func:`zope.interface.alsoProvides` function to associate +the interface with the instance. This function mutates the instance +in such a way that the interface is attached to it. -.. index:: - pair: view; static resource +.. code-block:: python + :linenos: -Serving Static Resources Using a View Callable ----------------------------------------------- + from zope.interface import Interface + from zope.interface import alsoProvides -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. + class IHello(Interface): + """ A marker interface """ -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. + class Hello(object): + pass -.. ignore-next-block -.. code-block:: python - :linenos: + def make_hello(): + hello = Hello() + alsoProvides(hello, IHello) + return hello - 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 -```` ZCML directive in your application's ``configure.zcml`` -against either the class or interface that represents your root -object. For example (ZCML): +Regardless of how you associate an interface with a model instance or +a model class, the resulting ZCML to associate that interface with a +view callable is the same. Assuming the above code that defines an +``IHello`` interface lives in the root of your application, and its +module is named "models.py", the below interface declaration will +associate the ``.views.hello_world`` view with models that implement +(aka provide) this interface. .. code-block:: xml :linenos: - - -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" (``@@``). + + +Any time a model that is determined to be the :term:`context` provides +this interface, and a view named ``hello.html`` is looked up against +it as per the URL, the ``.views.hello_world`` view callable will be +invoked. + +Note that views registered against a model class take precedence over +views registered for any interface the model class implements when an +ambiguity arises. If a view is registered for both the class type of +the context and an interface implemented by the context's class, the +view registered for the context's class will "win". + +For more information about defining models with interfaces for use +within view configuration, see +:ref:`models_which_implement_interfaces`. .. index:: - triple: exceptions; special; view + pair: view; security -Special Exceptions ------------------- +.. _view_security_section: -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. +Configuring View Security +~~~~~~~~~~~~~~~~~~~~~~~~~ -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 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 configuration declaration in ZCML: -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. +.. code-block:: xml + :linenos: -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']``. +When an authentication policy is enabled, this view will be protected +with the ``add`` permission. The view will *not be called* if the +user does not possess the ``add`` permission relative to the current +:term:`context` and an authorization policy is enabled. Instead the +:term:`forbidden view` result will be returned to the client (see +:ref:`changing_the_forbidden_view`). -.. index:: - triple: view; forms; unicode +.. note:: -Using Views to Handle Form Submissions (Unicode and Character Set Issues) -------------------------------------------------------------------------- + See the :ref:`security_chapter` chapter to find out how to turn on + an authentication policy. -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 -`_. -: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. +.. index:: + pair: view; lookup -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. +.. _view_lookup: -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: +View Lookup +----------- -.. code-block:: xml - :linenos: +: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`. - - - - -
-
- -
-
- -
- -
- +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. - +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 `_. .. 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. - -- cgit v1.2.3