.. _handlers_chapter:

View Handlers
=============

Along with normal view callables, :app:`Pyramid` provides the concept of a
:term:`view handler`.  Using a view handler instead of a plain :term:`view
callable` makes it unnecessary to call
:meth:`pyramid.config.Configurator.add_route` (and/or
:meth:`pyramid.config.Configurator.add_view`) "by hand" multiple
times, making it more pleasant to register a collection of views as a single
class when using :term:`url dispatch`.  The view handler machinery also
introduces the concept of an ``action``, which is used as a :term:`view
predicate` to control which method of the handler is called.

.. note:: 

   View handlers are not useful when using :term:`traversal`, only when using
   :term:`url dispatch`.  The concept of a view handler is analogous to a
   "controller" in Pylons 1.0.

The view handler class is initialized by :app:`Pyramid` in the same
manner as a view class.  Its ``__init__`` is called with a request
object (see :ref:`class_as_view`) as its argument when a request enters
the system which corresponds with a view handler registration made
during configuration.  After the view handler class is instantiated, a
method on the instance is called. The method which is called depends on
the view handler configuration.

The :meth:`pyramid.config.Configurator.add_handler` method will
scan the handler class and automatically set up views for methods that
are auto-exposed, or were decorated with the
:class:`~pyramid.view.action` decorator. This decorator is used to setup
additional view configuration information for individual methods of the
class, and can be used repeatedly for a single view method to
register multiple view configurations for it.

Here's an example view handler class:

.. code-block:: python
    :linenos:
    
    from pyramid.response import Response
   
    from pyramid.view import action
   
    class Hello(object):
        def __init__(self, request):
            self.request = request
       
        def index(self):
            return Response('Hello world!')

        @action(renderer="mytemplate.mak")
        def bye(self):
            return {}

An accompanying call to the
:meth:`~pyramid.config.Configurator.add_handler` for the handler must
be performed in order to register it with the system:

.. code-block:: python
    :linenos:

    config.add_handler('hello', '/hello/{action}', handler=Hello)

This example will result in a route being added for the pattern
``/hello/{action}``, each method of the ``Hello`` class will then be examined
to register the views. The value of ``{action}`` in the route pattern will be
used to determine which view should be called, and each view in the class will
be setup with a view predicate that requires a specific ``action`` name.

If the URL in the above example was ``/hello/index``, then the ``index``
method of the Hello class would be called.

Alternatively, the action can be declared specifically for a URL to go to a
specific ``action`` name:

.. code-block:: python
    :linenos:
    
    config.add_handler('hello_index', '/hello/index', 
                       handler=Hello, action='index')

This will result one of the methods that are configured for the ``action`` of
'index' in the ``Hello`` handler class to be called. In this case the name
of the method is the same as the action name: 'index'. However, this
need not be the case, as we will see below.

.. note::

  Handler configuration may also be added to the system via :term:`ZCML` (see
  :ref:`zcml_handler_configuration`).

.. _using_add_handler:

Using :meth:`~pyramid.config.Configurator.add_handler`
-------------------------------------------------------------

When calling :meth:`~pyramid.config.Configurator.add_handler`, an
``action`` is required in either the route pattern or as a keyword argument,
but **cannot appear in both places**. A ``handler`` argument must also be
supplied, which can be either a :term:`resource specification` or a Python
reference to the handler class. Additional keyword arguments are passed
directly through to :meth:`pyramid.config.Configurator.add_route`.

For example:

.. code-block:: python
    :linenos:
    
    config.add_handler('hello', '/hello/{action}',
                       handler='mypackage.handlers:MyHandler')

In larger applications, it is advised to use a :term:`resource specification`
with :meth:`~pyramid.config.Configurator.add_handler` to avoid having
to import every handler class.

Multiple :meth:`~pyramid.config.Configurator.add_handler` calls can
specify the same handler, to register specific route names for different
handler/action combinations. For example:

.. code-block:: python
    :linenos:
    
    config.add_handler('hello_index', '/hello/index', 
                       handler=Hello, action='index')
    config.add_handler('bye_index', '/hello/bye', 
                       handler=Hello, action='bye')


View Setup in the Handler Class
-------------------------------

The handler class specified can have a single class level attribute called
``__autoexpose__`` which should be a regular expression or the value
``None``. It's used to determine which method names will result in additional
view configurations being registered.

When :meth:`~pyramid.config.Configurator.add_handler` runs, every
method in the handler class will be searched and a view registered if the
method name matches the ``__autoexpose__`` regular expression, or if the
method was decorated with :class:`~pyramid.view.action`.

Auto-exposed Views
------------------

Every method in the handler class that has a name meeting the
``_autoexpose__`` regular expression will have a view registered for an
``action`` name corresponding to the method name. This functionality can be
disabled by setting the ``__autoexpose__`` attribute to ``None``:

.. code-block:: python
    :linenos:

    from pyramid.view import action
   
    class Hello(object):
        __autoexpose__ = None
        
        def __init__(self, request):
            self.request = request
        
        @action()
        def index(self):
            return Response('Hello world!')

        @action(renderer="mytemplate.mak")
        def bye(self):
            return {}

With auto-expose effectively disabled, no views will be registered for a
method unless it is specifically decorated with :class:`~pyramid.view.action`.

Action Decorator
----------------

The :class:`~pyramid.view.action` decorator registers view configuration
information on the handler method, which is used by
:meth:`~pyramid.config.Configurator.add_handler` to setup the view
configuration.

All keyword arguments are recorded, and passed to
:meth:`~pyramid.config.Configurator.add_view`. Any valid keyword
arguments for :meth:`~pyramid.config.Configurator.add_view` can thus be
used with the :class:`~pyramid.view.action` decorator to further restrict when
the view will be called.

One important difference is that a handler method can respond to an ``action``
name that is different from the method name by passing in a ``name`` argument.

Example:

.. code-block:: python
    :linenos:
    
    from pyramid.view import action
   
    class Hello(object):
        def __init__(self, request):
            self.request = request
        
        @action(name='index', renderer='created.mak', request_method='POST')
        def create(self):
            return {}

        @action(renderer="view_all.mak", request_method='GET')
        def index(self):
            return {}

This will register two views that require the ``action`` to be ``index``, with
the additional view predicate requiring a specific request method.

It can be useful to decorate a single method multiple times with
:class:`~pyramid.view.action`. Each action decorator will register a new view
for the method. By specifying different names and renderers for each action,
the same view logic can be exposed and rendered differently on multiple
URLs.

Example:

.. code-block:: python
    :linenos:
    
    from pyramid.view import action
   
    class Hello(object):
        def __init__(self, request):
            self.request = request
        
        @action(name='home', renderer='home.mak')
        @action(name='about', renderer='about.mak')
        def show_template(self):
            # prep some template vars
            return {}

    # in the config
    config.add_handler('hello', '/hello/{action}', handler=Hello)

With this configuration, the url ``/hello/home`` will find a view configuration
that results in calling the ``show_template`` method, then rendering the
template with ``home.mak``, and the url ``/hello/about`` will call the same
method and render the ``about.mak`` template.
