.. _qtut_more_view_classes:

==========================
15: More With View Classes
==========================

Group views into a class, sharing configuration, state, and logic.

Background
==========

As part of its mission to help build more ambitious web applications,
Pyramid provides many more features for views and view classes.

The Pyramid documentation discusses views as a Python "callable". This
callable can be a function, an object with an ``__call__``,
or a Python class. In this last case, methods on the class can be
decorated with ``@view_config`` to register the class methods with the
:term:`configurator` as a view.

At first, our views were simple, free-standing functions. Many times
your views are related: different ways to look at or work on the same
data or a REST API that handles multiple operations. Grouping these
together as a :ref:`view class <class_as_view>` makes sense:

- Group views

- Centralize some repetitive defaults

- Share some state and helpers

Pyramid views have :ref:`view predicates <view_configuration_parameters>`
that determine which view is matched to a request, based on factors
such as the request method, the form parameters, etc. These predicates
provide many axes of flexibility.

The following shows a simple example with four operations:
view a home page which leads to a form, save a change,
and press the delete button.

Objectives
==========

- Group related views into a view class

- Centralize configuration with class-level ``@view_defaults``

- Dispatch one route/URL to multiple views based on request data

- Share stated and logic between views and templates via the view class

Steps
=====

#. First we copy the results of the previous step:

   .. code-block:: bash

    $ cd ..; cp -r templating more_view_classes; cd more_view_classes
    $ $VENV/bin/python setup.py develop

#. Our route in ``more_view_classes/tutorial/__init__.py`` needs some
   replacement patterns:

   .. literalinclude:: more_view_classes/tutorial/__init__.py
    :linenos:

#. Our ``more_view_classes/tutorial/views.py`` now has a view class with
   several views:

   .. literalinclude:: more_view_classes/tutorial/views.py
    :linenos:

#. Our primary view needs a template at
   ``more_view_classes/tutorial/home.pt``:

   .. literalinclude:: more_view_classes/tutorial/home.pt
    :language: html

#. Ditto for our other view from the previous section at
   ``more_view_classes/tutorial/hello.pt``:

   .. literalinclude:: more_view_classes/tutorial/hello.pt
    :language: html

#. We have an edit view that also needs a template at
   ``more_view_classes/tutorial/edit.pt``:

   .. literalinclude:: more_view_classes/tutorial/edit.pt
    :language: html

#. And finally the delete view's template at
   ``more_view_classes/tutorial/delete.pt``:

   .. literalinclude:: more_view_classes/tutorial/delete.pt
    :language: html

#. Run your Pyramid application with:

   .. code-block:: bash

    $ $VENV/bin/pserve development.ini --reload

#. Open http://localhost:6543/howdy/jane/doe in your browser. Click
   the ``Save`` and ``Delete`` buttons and watch the output in the
   console window.

Analysis
========

As you can see, the four views are logically grouped together.
Specifically:

- We have a ``home`` view available at http://localhost:6543/ with
  a clickable link to the ``hello`` view.

- The second view is returned when you go to ``/howdy/jane/doe``. This
  URL is
  mapped to the ``hello`` route that we centrally set using the optional
  ``@view_defaults``.

- The third view is returned when the form is submitted with a ``POST``
  method. This rule is specified in the ``@view_config`` for that view.

- The fourth view is returned when clicking on a button such
  as ``<input type="submit" name="form.delete" value="Delete"/>``.

In this step we show using the following information as criteria to
decide which view to use:

- Method of the HTTP request (``GET``, ``POST``, etc.)

- Parameter information in the request (submitted form field names)

We also centralize part of the view configuration to the class level
with ``@view_defaults``, then in one view, override that default just
for that one view. Finally, we put this commonality between views to
work in the view class by sharing:

- State assigned in ``TutorialViews.__init__``

- A computed value

These are then available both in the view methods but also in the
templates (e.g. ``${view.view_name}`` and ``${view.full_name}``.

As a note, we made a switch in our templates on how we generate URLs.
We previously hardcode the URLs, such as::

  <a href="/howdy/jane/doe">Howdy</a>

In ``home.pt`` we switched to::

  <a href="${request.route_url('hello', first='jane',
        last='doe')}">form</a>

Pyramid has rich facilities to help generate URLs in a flexible,
non-error-prone fashion.

Extra Credit
============

#. Why could our template do ``${view.full_name}`` and not have to do
   ``${view.full_name()}``?

#. The ``edit`` and ``delete`` views are both submitted to with
   ``POST``. Why does the ``edit`` view configuration not catch the
   ``POST`` used by ``delete``?

#. We used Python ``@property`` on ``full_name``. If we reference this
   many times in a template or view code, it would re-compute this
   every time. Does Pyramid provide something that will cache the initial
   computation on a property?

#. Can you associate more than one route with the same view?

#. There is also a ``request.route_path`` API.  How does this differ from 
   ``request.route_url``?

.. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With
   URL Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
