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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
.. _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 states 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
#. Our tests in ``more_view_classes/tutorial/tests.py`` fail, so let's modify
them:
.. literalinclude:: more_view_classes/tutorial/tests.py
:linenos:
#. Now run the tests:
.. code-block:: bash
$ $VENV/bin/nosetests tutorial
.
----------------------------------------------------------------------
Ran 2 tests in 0.248s
OK
#. 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, how 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>`_
|