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
|
====================================
18: Forms and Validation With Deform
====================================
Schema-driven, autogenerated forms with validation.
Background
==========
Modern web applications deal extensively with forms. Developers,
though, have a wide range of philosophies about how frameworks should
help them with their forms. As such, Pyramid doesn't directly bundle
one particular form library. Instead, there are a variety of form
libraries that are easy to use in Pyramid.
:ref:`Deform <deform:overview>`
is one such library. In this step, we introduce Deform for our
forms and validation. This also gives us the
:ref:`Colander <colander:overview>` for schemas and validation.
Deform is getting a facelift, with styling from Twitter Bootstrap and
advanced widgets from popular JavaScript projects. The work began in
``deform_bootstrap`` and is being merged into an update to Deform.
Objectives
==========
- Make a schema using Colander, the companion to Deform
- Create a form with Deform and change our views to handle validation
Steps
=====
#. First we copy the results of the ``view_classes`` step:
.. code-block:: bash
(env27)$ cd ..; cp -r view_classes forms; cd forms
#. Let's edit ``forms/setup.py`` to declare a dependency on Deform
(which then pulls in Colander as a dependency:
.. literalinclude:: forms/setup.py
:linenos:
#. We can now install our project in development mode:
.. code-block:: bash
(env27)$ python setup.py develop
#. Register a static view in ``forms/tutorial/__init__.py`` for
Deform's CSS/JS etc. as well as our demo wikipage scenario's
views:
.. literalinclude:: forms/tutorial/__init__.py
:linenos:
#. Implement the new views, as well as the form schemas and some
dummy data, in ``forms/tutorial/views.py``:
.. literalinclude:: forms/tutorial/views.py
:linenos:
#. A template for the top of the "wiki" in
``forms/tutorial/wiki_view.pt``:
.. literalinclude:: forms/tutorial/wiki_view.pt
:language: html
:linenos:
#. Another template for adding/editing in
``forms/tutorial/wikipage_addedit.pt``:
.. literalinclude:: forms/tutorial/wikipage_addedit.pt
:language: html
:linenos:
#. Finally, a template at ``forms/tutorial/wikipage_view.pt``
for viewing a wiki page:
.. literalinclude:: forms/tutorial/wikipage_view.pt
:language: html
:linenos:
#. Run your Pyramid application with:
.. code-block:: bash
(env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in a browser.
Analysis
========
This step helps illustrate the utility of asset specifications for
static assets. We have an outside package called Deform with static
assets which need to be published. We don't have to know where on disk
it is located. We point at the package, then the path inside the package.
We just need to include a call to ``add_static_view`` to make that
directory available at a URL. For Pyramid-specific pages,
Pyramid provides a facility (``config.include()``) which even makes
that unnecessary for consumers of a package. (Deform is not specific to
Pyramid.)
Our forms have rich widgets which need the static CSS and JS just
mentioned. Deform has a :term:`resource registry` which allows widgets
to specify which JS and CSS are needed. Our ``wikipage_addedit.pt``
template shows how we iterated over that data to generate markup that
includes the needed resources.
Our add and edit views use a pattern called *self-posting forms*.
Meaning, the same URL is used to ``GET`` the form as is used to
``POST`` the form. The route, the view, and the template are the same
whether you are walking up to it the first time or you clicked a button.
Inside the view we do ``if 'submit' in self.request.params:`` to see if
this form was a ``POST`` where the user clicked on a particular button
``<input name="submit">``.
The form controller then follows a typical pattern:
- If you are doing a GET, skip over and just return the form
- If you are doing a POST, validate the form contents
- If the form is invalid, bail out by re-rendering the form with the
supplied ``POST`` data
- If the validation succeeded, perform some action and issue a
redirect via ``HTTPFound``.
We are, in essence, writing our own form controller. Other
Pyramid-based systems, including ``pyramid_deform``, provide a
form-centric view class which automates much of this branching and
routing.
Extra Credit
============
#. Give a try at a button that goes to a delete view for a
particular wiki page.
|