Using Hooks

“Hooks” can be used to influence the behavior of the repoze.bfg framework in various ways.

Changing the Not Found View

When repoze.bfg can’t map a URL to view code, it invokes a not found view, which is a view callable. The view it invokes can be customized through application configuration. This view can be configured via imperative configuration or ZCML.

Using Imperative Configuration

If your application uses imperative configuration, you can replace the Not Found view by using the repoze.bfg.configuration.Configurator.set_notfound_view() method:

1
2
import helloworld.views
config.set_notfound_view(helloworld.views.notfound_view)

Replace helloworld.views.notfound_view with a reference to the Python view callable you want to use to represent the Not Found view.

Using ZCML

If your application uses ZCML, you can replace the Not Found view by placing something like the following ZCML in your configure.zcml file.

1
2
<notfound
    view="helloworld.views.notfound_view"/>

Replace helloworld.views.notfound_view with the Python dotted name to the notfound view you want to use.

Other attributes of the notfound directive are documented at notfound.

Here’s some sample code that implements a minimal NotFound view:

1
2
3
4
from webob.exc import HTTPNotFound

def notfound_view(request):
    return HTTPNotFound()

Note

When a NotFound view is invoked, it is passed a request. The environ attribute of the request is the WSGI environment. Within the WSGI environ will be a key named repoze.bfg.message that has a value explaining why the not found error was raised. This error will be different when the debug_notfound environment setting is true than it is when it is false.

Changing the Forbidden View

When repoze.bfg can’t authorize execution of a view based on the authorization policy in use, it invokes a forbidden view. The default forbidden response has a 401 status code and is very plain, but it can be overridden as necessary using either imperative configuration or ZCML:

Using Imperative Configuration

If your application uses imperative configuration, you can replace the Forbidden view by using the repoze.bfg.configuration.Configurator.set_forbidden_view() method:

1
2
import helloworld.views
config.set_forbiddden_view(helloworld.views.forbidden_view)

Replace helloworld.views.forbidden_view with a reference to the Python view callable you want to use to represent the Forbidden view.

Using ZCML

If your application uses ZCML, you can replace the Forbidden view by placing something like the following ZCML in your configure.zcml file.

1
2
<forbidden
    view="helloworld.views.forbidden_view"/>

Replace helloworld.views.forbidden_view with the Python dotted name to the forbidden view you want to use.

Other attributes of the forbidden directive are documented at forbidden.

Like any other view, the forbidden view must accept at least a request parameter, or both context and request. The context (available as request.context if you’re using the request-only view argument pattern) is the context found by the router when the view invocation was denied. The request is the current request representing the denied action.

Here’s some sample code that implements a minimal forbidden view:

1
2
3
4
from repoze.bfg.chameleon_zpt import render_template_to_response

def forbidden_view(request):
    return render_template_to_response('templates/login_form.pt')

Note

When a forbidden view is invoked, it is passed the request as the second argument. An attribute of the request is environ, which is the WSGI environment. Within the WSGI environ will be a key named repoze.bfg.message that has a value explaining why the current view invocation was forbidden. This error will be different when the debug_authorization environment setting is true than it is when it is false.

Warning

the default forbidden view sends a response with a 401 Unauthorized status code for backwards compatibility reasons. You can influence the status code of Forbidden responses by using an alternate forbidden view. For example, it would make sense to return a response with a 403 Forbidden status code.

Changing the Traverser

The default traversal algorithm that BFG uses is explained in The Traversal Algorithm. Though it is rarely necessary, this default algorithm can be swapped out selectively for a different traversal pattern via configuration.

Use an adapter stanza in your application’s configure.zcml to change the default traverser:

1
2
3
4
5
 <adapter
   factory="myapp.traversal.Traverser"
   provides="repoze.bfg.interfaces.ITraverser"
   for="*"
   />

In the example above, myapp.traversal.Traverser is assumed to be a class that implements the following interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Traverser(object):
    def __init__(self, root):
        """ Accept the root object returned from the root factory """

    def __call__(self, request):
        """ Return a dictionary with (at least) the keys ``root``,
        ``context``, ``view_name``, ``subpath``, ``traversed``,
        ``virtual_root``, and ``virtual_root_path``.  These values are
        typically the result of an object graph traversal.  ``root``
        is the physical root object, ``context`` will be a model
        object, ``view_name`` will be the view name used (a Unicode
        name), ``subpath`` will be a sequence of Unicode names that
        followed the view name but were not traversed, ``traversed``
        will be a sequence of Unicode names that were traversed
        (including the virtual root path, if any) ``virtual_root``
        will be a model object representing the virtual root (or the
        physical root if traversal was not performed), and
        ``virtual_root_path`` will be a sequence representing the
        virtual root path (a sequence of Unicode names) or None if
        traversal was not performed.

        Extra keys for special purpose functionality can be added as
        necessary.

        All values returned in the dictionary will be made available
        as attributes of the ``request`` object.
        """

Warning

In repoze.bfg. 1.0 and previous versions, the traverser __call__ method accepted a WSGI environment dictionary rather than a request object. The request object passed to the traverser implements a dictionary-like API which mutates and queries the environment, as a backwards compatibility shim, in order to allow older code to work. However, for maximum forward compatibility, traverser code targeting repoze.bfg 1.1 and higher should expect a request object directly.

More than one traversal algorithm can be active at the same time. For instance, if your root factory returns more than one type of object conditionally, you could claim that an alternate traverser adapter is for only one particular class or interface. When the root factory returned an object that implemented that class or interface, a custom traverser would be used. Otherwise, the default traverser would be used. For example:

1
2
3
4
5
 <adapter
   factory="myapp.traversal.Traverser"
   provides="repoze.bfg.interfaces.ITraverser"
   for="myapp.models.MyRoot"
   />

If the above stanza was added to a configure.zcml file, repoze.bfg would use the myapp.traversal.Traverser only when the application root factory returned an instance of the myapp.models.MyRoot object. Otherwise it would use the default repoze.bfg traverser to do traversal.

Example implementations of alternate traversers can be found “in the wild” within repoze.bfg.traversalwrapper and repoze.bfg.metatg.

Changing How repoze.bfg.url.model_url Generates a URL

When you add a traverser as described in Changing the Traverser, it’s often convenient to continue to use the repoze.bfg.url.model_url() API. However, since the way traversal is done will have been modified, the URLs it generates by default may be incorrect.

If you’ve added a traverser, you can change how repoze.bfg.url.model_url() generates a URL for a specific type of context by adding an adapter stanza for repoze.bfg.interfaces.IContextURL to your application’s configure.zcml:

1
2
3
4
5
 <adapter
   factory="myapp.traversal.URLGenerator"
   provides="repoze.bfg.interfaces.IContextURL"
   for="myapp.models.MyRoot *"
   />

In the above example, the myapp.traversal.URLGenerator class will be used to provide services to repoze.bfg.url.model_url() any time the context passed to model_url is of class myapp.models.MyRoot. The asterisk following represents the type of interface that must be possessed by the request (in this case, any interface, represented by asterisk).

The API that must be implemented by a class that provides repoze.bfg.interfaces.IContextURL is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from zope.interface import Interface

class IContextURL(Interface):
    """ An adapter which deals with URLs related to a context.
    """
    def __init__(self, context, request):
        """ Accept the context and request """

    def virtual_root(self):
        """ Return the virtual root object related to a request and the
        current context"""

    def __call__(self):
        """ Return a URL that points to the context """

The default context URL generator is available for perusal as the class repoze.bfg.traversal.TraversalContextURL in the traversal module of the Repoze Subversion repository.