1.. _qtut_more_view_classes:
2
3==========================
415: More With View Classes
5==========================
6
7Group views into a class, sharing configuration, state, and logic.
8
9
10Background
11==========
12
13As part of its mission to help build more ambitious web applications, Pyramid
14provides many more features for views and view classes.
15
16The Pyramid documentation discusses views as a Python "callable". This callable
17can be a function, an object with a ``__call__``, or a Python class. In this
18last case, methods on the class can be decorated with ``@view_config`` to
19register the class methods with the :term:`configurator` as a view.
20
21At first, our views were simple, free-standing functions. Many times your views
22are related: different ways to look at or work on the same data, or a REST API
23that handles multiple operations. Grouping these together as a :ref:`view class
24<class_as_view>` makes sense:
25
26- Group views.
27
28- Centralize some repetitive defaults.
29
30- Share some state and helpers.
31
32Pyramid views have :ref:`view predicates <view_configuration_parameters>` that
33determine which view is matched to a request, based on factors such as the
34request method, the form parameters, and so on. These predicates provide many
35axes of flexibility.
36
37The following shows a simple example with four operations: view a home page
38which leads to a form, save a change, and press the delete button.
39
40
41Objectives
42==========
43
44- Group related views into a view class.
45
46- Centralize configuration with class-level ``@view_defaults``.
47
48- Dispatch one route/URL to multiple views based on request data.
49
50- Share states and logic between views and templates via the view class.
51
52
53Steps
54=====
55
56#. First we copy the results of the previous step:
57
58   .. code-block:: bash
59
60    $ cd ..; cp -r templating more_view_classes; cd more_view_classes
61    $ $VENV/bin/pip install -e .
62
63#. Our route in ``more_view_classes/tutorial/__init__.py`` needs some
64   replacement patterns:
65
66   .. literalinclude:: more_view_classes/tutorial/__init__.py
67    :linenos:
68
69#. Our ``more_view_classes/tutorial/views.py`` now has a view class with
70   several views:
71
72   .. literalinclude:: more_view_classes/tutorial/views.py
73    :linenos:
74
75#. Our primary view needs a template at ``more_view_classes/tutorial/home.pt``:
76
77   .. literalinclude:: more_view_classes/tutorial/home.pt
78    :language: html
79
80#. Ditto for our other view from the previous section at
81   ``more_view_classes/tutorial/hello.pt``:
82
83   .. literalinclude:: more_view_classes/tutorial/hello.pt
84    :language: html
85
86#. We have an edit view that also needs a template at
87   ``more_view_classes/tutorial/edit.pt``:
88
89   .. literalinclude:: more_view_classes/tutorial/edit.pt
90    :language: html
91
92#. And finally the delete view's template at
93   ``more_view_classes/tutorial/delete.pt``:
94
95   .. literalinclude:: more_view_classes/tutorial/delete.pt
96    :language: html
97
98#. Our tests in ``more_view_classes/tutorial/tests.py`` fail, so let's modify
99   them:
100
101   .. literalinclude:: more_view_classes/tutorial/tests.py
102    :linenos:
103
104#. Now run the tests:
105
106   .. code-block:: bash
107
108    $ $VENV/bin/py.test tutorial/tests.py -q
109    ..
110    2 passed in 0.40 seconds
111
112#. Run your Pyramid application with:
113
114   .. code-block:: bash
115
116    $ $VENV/bin/pserve development.ini --reload
117
118#. Open http://localhost:6543/howdy/jane/doe in your browser. Click the
119   ``Save`` and ``Delete`` buttons, and watch the output in the console window.
120
121
122Analysis
123========
124
125As you can see, the four views are logically grouped together. Specifically:
126
127- We have a ``home`` view available at http://localhost:6543/ with a clickable
128  link to the ``hello`` view.
129
130- The second view is returned when you go to ``/howdy/jane/doe``. This URL is
131  mapped to the ``hello`` route that we centrally set using the optional
132  ``@view_defaults``.
133
134- The third view is returned when the form is submitted with a ``POST`` method.
135  This rule is specified in the ``@view_config`` for that view.
136
137- The fourth view is returned when clicking on a button such as ``<input
138  type="submit" name="form.delete" value="Delete"/>``.
139
140In this step we show, using the following information as criteria, how to
141decide which view to use:
142
143- Method of the HTTP request (``GET``, ``POST``, etc.)
144
145- Parameter information in the request (submitted form field names)
146
147We also centralize part of the view configuration to the class level with
148``@view_defaults``, then in one view, override that default just for that one
149view. Finally, we put this commonality between views to work in the view class
150by sharing:
151
152- State assigned in ``TutorialViews.__init__``
153
154- A computed value
155
156These are then available both in the view methods and in the templates (e.g.,
157``${view.view_name}`` and ``${view.full_name}``).
158
159As a note, we made a switch in our templates on how we generate URLs. We
160previously hardcoded the URLs, such as:
161
162.. code-block:: html
163
164  <a href="/howdy/jane/doe">Howdy</a>
165
166In ``home.pt`` we switched to:
167
168.. code-block:: xml
169
170  <a href="${request.route_url('hello', first='jane',
171        last='doe')}">form</a>
172
173Pyramid has rich facilities to help generate URLs in a flexible, non-error
174prone fashion.
175
176Extra credit
177============
178
179#. Why could our template do ``${view.full_name}`` and not have to do
180   ``${view.full_name()}``?
181
182#. The ``edit`` and ``delete`` views are both receive ``POST`` requests. Why
183   does the ``edit`` view configuration not catch the ``POST`` used by
184   ``delete``?
185
186#. We used Python ``@property`` on ``full_name``. If we reference this many
187   times in a template or view code, it would re-compute this every time. Does
188   Pyramid provide something that will cache the initial computation on a
189   property?
190
191#. Can you associate more than one route with the same view?
192
193#. There is also a ``request.route_path`` API. How does this differ from
194   ``request.route_url``?
195
196.. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With
197   URL Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
198