Steve Piercy
2017-10-22 4567204570eff25408278fd01919c1b048b9f7f1
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
.. _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 a ``__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, and so on. 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/pip install -e .
 
#. 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/py.test tutorial/tests.py -q
    ..
    2 passed in 0.40 seconds
 
#. 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 and 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 hardcoded the URLs, such as:
 
.. code-block:: html
 
  <a href="/howdy/jane/doe">Howdy</a>
 
In ``home.pt`` we switched to:
 
.. code-block:: xml
 
  <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 receive ``POST`` requests. 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>`_