File: more_view_classes.rst

package info (click to toggle)
python-pyramid 1.6%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 9,112 kB
  • ctags: 8,169
  • sloc: python: 41,764; makefile: 111; sh: 17
file content (198 lines) | stat: -rw-r--r-- 6,034 bytes parent folder | download
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>`_