Michael Merickel
2017-06-18 75c30dfe18b26ca04efae2acbe35052fa0d93ed6
commit | author | age
93a7a1 1 .. _wiki_defining_views:
SP 2
e53e13 3 ==============
CM 4 Defining Views
5 ==============
6
beb4f1 7 A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid`
d63eaf 8 application is typically a simple Python function that accepts two
PP 9 parameters: :term:`context` and :term:`request`.  A view callable is
10 assumed to return a :term:`response` object.
e53e13 11
012b97 12 .. note::
M 13
14    A :app:`Pyramid` view can also be defined as callable
d63eaf 15    which accepts *only* a :term:`request` argument.  You'll see
PP 16    this one-argument pattern used in other :app:`Pyramid` tutorials
17    and applications.  Either calling convention will work in any
18    :app:`Pyramid` application; the calling conventions can be used
beb4f1 19    interchangeably as necessary. In :term:`traversal`-based applications,
d63eaf 20    URLs are mapped to a context :term:`resource`, and since our
PP 21    :term:`resource tree` also represents our application's
a940e1 22    "domain model", we're often interested in the context because
d63eaf 23    it represents the persistent storage of our application.  For
PP 24    this reason, in this tutorial we define views as callables that
25    accept ``context`` in the callable argument list.  If you do
26    need the ``context`` within a view function that only takes
27    the request as a single argument, you can obtain it via
28    ``request.context``.
2a5ae0 29
879bb5 30 We're going to define several :term:`view callable` functions, then wire them
2a5ae0 31 into :app:`Pyramid` using some :term:`view configuration`.
e53e13 32
CM 33
db638c 34 Declaring Dependencies in Our ``setup.py`` File
279ba5 35 ===============================================
db638c 36
279ba5 37 The view code in our application will depend on a package which is not a
PP 38 dependency of the original "tutorial" application.  The original "tutorial"
38cf5b 39 application was generated by the cookiecutter; it doesn't know
a940e1 40 about our custom application requirements.
db638c 41
a940e1 42 We need to add a dependency on the ``docutils`` package to our ``tutorial``
SP 43 package's ``setup.py`` file by assigning this dependency to the ``requires``
44 parameter in the ``setup()`` function.
45
b8f579 46 Open ``setup.py`` and edit it to look like the following:
db638c 47
b1e277 48 .. literalinclude:: src/views/setup.py
db638c 49    :linenos:
909ae0 50    :emphasize-lines: 22
db638c 51    :language: python
PP 52
a940e1 53 Only the highlighted line needs to be added.
279ba5 54
b01a02 55 .. _wiki-running-pip-install:
b8f579 56
SP 57 Running ``pip install -e .``
a940e1 58 ============================
e53e13 59
b8f579 60 Since a new software dependency was added, you will need to run ``pip install
SP 61 -e .`` again inside the root of the ``tutorial`` package to obtain and register
62 the newly added dependency distribution.
a940e1 63
SP 64 Make sure your current working directory is the root of the project (the
65 directory in which ``setup.py`` lives) and execute the following command.
66
67 On UNIX:
68
b8f579 69 .. code-block:: bash
a940e1 70
SP 71    $ cd tutorial
b8f579 72    $ $VENV/bin/pip install -e .
a940e1 73
SP 74 On Windows:
75
a651b3 76 .. code-block:: doscon
a940e1 77
beb4f1 78    c:\> cd tutorial
SP 79    c:\tutorial> %VENV%\Scripts\pip install -e .
a940e1 80
SP 81 Success executing this command will end with a line to the console something
b8f579 82 like:
a940e1 83
b8f579 84 .. code-block:: text
SP 85
beb4f1 86    Successfully installed docutils-0.13.1 tutorial
b8f579 87
a940e1 88
SP 89 Adding view functions in ``views.py``
90 =====================================
91
b8f579 92 It's time for a major change.  Open ``tutorial/views.py`` and edit it to look
SP 93 like the following:
a940e1 94
SP 95 .. literalinclude:: src/views/tutorial/views.py
96    :linenos:
97    :language: python
98
99 We added some imports and created a regular expression to find "WikiWords".
100
101 We got rid of the ``my_view`` view function and its decorator that was added
beb4f1 102 when we originally rendered the ``zodb`` cookiecutter.  It was only an example and
a940e1 103 isn't relevant to our application.
SP 104
105 Then we added four :term:`view callable` functions to our ``views.py``
beb4f1 106 module:
a940e1 107
SP 108 * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
109 * ``view_page()`` - Displays an individual page.
110 * ``add_page()`` - Allows the user to add a page.
111 * ``edit_page()`` - Allows the user to edit a page.
112
113 We'll describe each one briefly in the following sections.
e53e13 114
a4e90c 115 .. note::
PP 116
117   There is nothing special about the filename ``views.py``.  A project may
a940e1 118   have many view callables throughout its codebase in arbitrarily named
a4e90c 119   files.  Files implementing view callables often have ``view`` in their
PP 120   filenames (or may live in a Python subpackage of your application package
121   named ``views``), but this is only by convention.
122
e53e13 123 The ``view_wiki`` view function
CM 124 -------------------------------
125
a940e1 126 Following is the code for the ``view_wiki`` view function and its decorator:
f56480 127
SP 128 .. literalinclude:: src/views/tutorial/views.py
129    :lines: 12-14
beb4f1 130    :lineno-match:
f56480 131    :language: python
SP 132
a940e1 133 .. note:: In our code, we use an *import* that is *relative* to our package
SP 134     named ``tutorial``, meaning we can omit the name of the package in the
135     ``import`` and ``context`` statements. In our narrative, however, we refer
136     to a *class* and thus we use the *absolute* form, meaning that the name of
137     the package is included.
2a5ae0 138
a940e1 139 ``view_wiki()`` is the :term:`default view` that gets called when a request is
SP 140 made to the root URL of our wiki.  It always redirects to an URL which
141 represents the path to our "FrontPage".
142
143 We provide it with a ``@view_config`` decorator which names the class
144 ``tutorial.models.Wiki`` as its context. This means that when a Wiki resource
145 is the context and no :term:`view name` exists in the request, then this view
146 will be used.  The view configuration associated with ``view_wiki`` does not
147 use a ``renderer`` because the view callable always returns a :term:`response`
148 object rather than a dictionary. No renderer is necessary when a view returns
149 a response object.
150
151 The ``view_wiki`` view callable always redirects to the URL of a Page resource
152 named "FrontPage".  To do so, it returns an instance of the
b743bb 153 :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
a940e1 154 the :class:`pyramid.interfaces.IResponse` interface, like
SP 155 :class:`pyramid.response.Response` does). It uses the
156 :meth:`pyramid.request.Request.route_url` API to construct an URL to the
f56480 157 ``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and
a940e1 158 uses it as the "location" of the ``HTTPFound`` response, forming an HTTP
42d31c 159 redirect.
e53e13 160
CM 161 The ``view_page`` view function
162 -------------------------------
f56480 163
a940e1 164 Here is the code for the ``view_page`` view function and its decorator:
f56480 165
SP 166 .. literalinclude:: src/views/tutorial/views.py
167    :lines: 16-33
beb4f1 168    :lineno-match:
f56480 169    :language: python
e53e13 170
a940e1 171 The ``view_page`` function is configured to respond as the default view
SP 172 of a Page resource.  We provide it with a ``@view_config`` decorator which
455196 173 names the class ``tutorial.models.Page`` as its context.  This means that
b743bb 174 when a Page resource is the context, and no :term:`view name` exists in the
CM 175 request, this view will be used.  We inform :app:`Pyramid` this view will use
176 the ``templates/view.pt`` template file as a ``renderer``.
2a5ae0 177
f56480 178 The ``view_page`` function generates the :term:`reStructuredText` body of a
2a5ae0 179 page (stored as the ``data`` attribute of the context passed to the view; the
a940e1 180 context will be a ``Page`` resource) as HTML.  Then it substitutes an HTML
SP 181 anchor for each *WikiWord* reference in the rendered HTML using a compiled
182 regular expression.
e53e13 183
CM 184 The curried function named ``check`` is used as the first argument to
2a5ae0 185 ``wikiwords.sub``, indicating that it should be called to provide a value for
CM 186 each WikiWord match found in the content.  If the wiki (our page's
187 ``__parent__``) already contains a page with the matched WikiWord name, the
188 ``check`` function generates a view link to be used as the substitution value
043ccd 189 and returns it.  If the wiki does not already contain a page with the
2a5ae0 190 matched WikiWord name, the function generates an "add" link as the
CM 191 substitution value and returns it.
192
193 As a result, the ``content`` variable is now a fully formed bit of HTML
194 containing various view and add links for WikiWords based on the content of
b743bb 195 our current page resource.
2a5ae0 196
a940e1 197 We then generate an edit URL because it's easier to do here than in the
SP 198 template, and we wrap up a number of arguments in a dictionary and return
e53e13 199 it.
CM 200
2a5ae0 201 The arguments we wrap into a dictionary include ``page``, ``content``, and
CM 202 ``edit_url``.  As a result, the *template* associated with this view callable
203 (via ``renderer=`` in its configuration) will be able to use these names to
204 perform various rendering tasks.  The template associated with this view
205 callable will be a template which lives in ``templates/view.pt``.
e53e13 206
2a5ae0 207 Note the contrast between this view callable and the ``view_wiki`` view
CM 208 callable.  In the ``view_wiki`` view callable, we unconditionally return a
209 :term:`response` object.  In the ``view_page`` view callable, we return a
210 *dictionary*.  It is *always* fine to return a :term:`response` object from a
211 :app:`Pyramid` view.  Returning a dictionary is allowed only when there is a
212 :term:`renderer` associated with the view callable in the view configuration.
e53e13 213
CM 214 The ``add_page`` view function
215 ------------------------------
216
a940e1 217 Here is the code for the ``add_page`` view function and its decorator:
f56480 218
SP 219 .. literalinclude:: src/views/tutorial/views.py
220    :lines: 35-50
beb4f1 221    :lineno-match:
f56480 222    :language: python
SP 223
a940e1 224 The ``add_page`` function is configured to respond when the context resource
SP 225 is a Wiki and the :term:`view name` is ``add_page``.  We provide it with a
226 ``@view_config`` decorator which names the string ``add_page`` as its
227 :term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its
228 context, and the renderer named ``templates/edit.pt``.  This means that when a
229 Wiki resource is the context, and a :term:`view name` named ``add_page``
b743bb 230 exists as the result of traversal, this view will be used.  We inform
a940e1 231 :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a
SP 232 ``renderer``.  We share the same template between add and edit views, thus
2a5ae0 233 ``edit.pt`` instead of ``add.pt``.
e53e13 234
2a5ae0 235 The ``add_page`` function will be invoked when a user clicks on a WikiWord
CM 236 which isn't yet represented as a page in the system.  The ``check`` function
237 within the ``view_page`` view generates URLs to this view.  It also acts as a
b743bb 238 handler for the form that is generated when we want to add a page resource.
CM 239 The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a
240 Page resource).
e53e13 241
2a5ae0 242 The request :term:`subpath` in :app:`Pyramid` is the sequence of names that
CM 243 are found *after* the :term:`view name` in the URL segments given in the
244 ``PATH_INFO`` of the WSGI request as the result of :term:`traversal`.  If our
a940e1 245 add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``,
2a5ae0 246 the :term:`subpath` will be a tuple: ``('SomeName',)``.
CM 247
beb4f1 248 The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name),
2a5ae0 249 and aliases it to the name attribute in order to know the name of the page
CM 250 we're trying to add.
e53e13 251
CM 252 If the view rendering is *not* a result of a form submission (if the
2a5ae0 253 expression ``'form.submitted' in request.params`` is ``False``), the view
CM 254 renders a template.  To do so, it generates a "save url" which the template
f56480 255 uses as the form post URL during rendering.  We're lazy here, so we're trying
2a5ae0 256 to use the same template (``templates/edit.pt``) for the add view as well as
b743bb 257 the page edit view.  To do so, we create a dummy Page resource object in
CM 258 order to satisfy the edit form's desire to have *some* page object exposed as
2a5ae0 259 ``page``, and we'll render the template to a response.
e53e13 260
2a5ae0 261 If the view rendering *is* a result of a form submission (if the expression
a940e1 262 ``'form.submitted' in request.params`` is ``True``), we grab the page body
2a5ae0 263 from the form data, create a Page object using the name in the subpath and
b743bb 264 the page body, and save it into "our context" (the Wiki) using the
2a5ae0 265 ``__setitem__`` method of the context. We then redirect back to the
CM 266 ``view_page`` view (the default view for a page) for the newly created page.
e53e13 267
CM 268 The ``edit_page`` view function
269 -------------------------------
270
a940e1 271 Here is the code for the ``edit_page`` view function and its decorator:
f56480 272
SP 273 .. literalinclude:: src/views/tutorial/views.py
274    :lines: 52-60
beb4f1 275    :lineno-match:
f56480 276    :language: python
SP 277
a940e1 278 The ``edit_page`` function is configured to respond when the context is
SP 279 a Page resource and the :term:`view name` is ``edit_page``.  We provide it
b743bb 280 with a ``@view_config`` decorator which names the string ``edit_page`` as its
CM 281 :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its
2a5ae0 282 context, and the renderer named ``templates/edit.pt``.  This means that when
b743bb 283 a Page resource is the context, and a :term:`view name` exists as the result
b1e277 284 of traversal named ``edit_page``, this view will be used.  We inform
2a5ae0 285 :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as
CM 286 a ``renderer``.
287
288 The ``edit_page`` function will be invoked when a user clicks the "Edit this
289 Page" button on the view form.  It renders an edit form but it also acts as
b743bb 290 the form post view callable for the form it renders.  The ``context`` of the
CM 291 ``edit_page`` view will *always* be a Page resource (never a Wiki resource).
e53e13 292
CM 293 If the view execution is *not* a result of a form submission (if the
2a5ae0 294 expression ``'form.submitted' in request.params`` is ``False``), the view
879bb5 295 simply renders the edit form, passing the page resource, and a ``save_url``
CZ 296 which will be used as the action of the generated form.
e53e13 297
2a5ae0 298 If the view execution *is* a result of a form submission (if the expression
CM 299 ``'form.submitted' in request.params`` is ``True``), the view grabs the
300 ``body`` element of the request parameter and sets it as the ``data``
301 attribute of the page context.  It then redirects to the default view of the
302 context (the page), which will always be the ``view_page`` view.
e53e13 303
a940e1 304 Adding templates
e53e13 305 ================
CM 306
f97aa6 307 The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
a940e1 308 reference a :term:`template`.  Each template is a :term:`Chameleon`
SP 309 :term:`ZPT` template.  These templates will live in the ``templates``
310 directory of our tutorial package.  Chameleon templates must have a ``.pt``
311 extension to be recognized as such.
e53e13 312
a940e1 313 The ``view.pt`` template
e53e13 314 ------------------------
CM 315
beb4f1 316 Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` and edit the emphasized lines to look like the following:
e53e13 317
CM 318 .. literalinclude:: src/views/tutorial/templates/view.pt
8e7df0 319    :linenos:
a940e1 320    :language: html
beb4f1 321    :emphasize-lines: 11-12,37-52
e53e13 322
8e7df0 323 This template is used by ``view_page()`` for displaying a single
PP 324 wiki page. It includes:
012b97 325
a940e1 326 - A ``div`` element that is replaced with the ``content`` value provided by
beb4f1 327   the view (lines 37-39).  ``content`` contains HTML, so the ``structure``
a940e1 328   keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.)
SP 329 - A link that points at the "edit" URL which invokes the ``edit_page`` view
beb4f1 330   for the page being viewed (lines 41-43).
e53e13 331
a940e1 332 The ``edit.pt`` template
e53e13 333 ------------------------
CM 334
beb4f1 335 Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit the emphasized lines to look like the following:
e53e13 336
CM 337 .. literalinclude:: src/views/tutorial/templates/edit.pt
8e7df0 338    :linenos:
a940e1 339    :language: html
e53e13 340
a940e1 341 This template is used by ``add_page()`` and ``edit_page()`` for adding and
SP 342 editing a wiki page.  It displays a page containing a form that includes:
8e7df0 343
beb4f1 344 - A 10-row by 60-column ``textarea`` field named ``body`` that is filled
SP 345   with any existing page data when it is rendered (line 46).
346 - A submit button that has the name ``form.submitted`` (line 49).
8e7df0 347
a940e1 348 The form POSTs back to the ``save_url`` argument supplied by the view (line
beb4f1 349 44).  The view will use the ``body`` and ``form.submitted`` values.
8e7df0 350
a940e1 351 .. note:: Our templates use a ``request`` object that none of our tutorial
SP 352    views return in their dictionary. ``request`` is one of several names that
353    are available "by default" in a template when a template renderer is used.
354    See :ref:`renderer_system_values` for information about other names that
355    are available by default when a template is used as a renderer.
8e7df0 356
b8f579 357
6901d7 358 Static assets
b743bb 359 -------------
e53e13 360
a940e1 361 Our templates name static assets, including CSS and images.  We don't need
SP 362 to create these files within our package's ``static`` directory because they
363 were provided at the time we created the project.
e53e13 364
a940e1 365 As an example, the CSS file will be accessed via
SP 366 ``http://localhost:6543/static/theme.css`` by virtue of the call to the
b1e277 367 ``add_static_view`` directive we've made in the ``__init__.py`` file.  Any
b743bb 368 number and type of static assets can be placed in this directory (or
a940e1 369 subdirectories) and are just referred to by URL or by using the convenience
SP 370 method ``static_url``, e.g.,
371 ``request.static_url('<package>:static/foo.css')`` within templates.
e53e13 372
b8f579 373
6901d7 374 Viewing the application in a browser
04ecdb 375 ====================================
PP 376
218ad4 377 We can finally examine our application in a browser (See
607524 378 :ref:`wiki-start-the-application`).  Launch a browser and visit
a940e1 379 each of the following URLs, checking that the result is as expected:
04ecdb 380
6901d7 381 - http://localhost:6543/ invokes the ``view_wiki`` view.  This always
a940e1 382   redirects to the ``view_page`` view of the ``FrontPage`` Page resource.
04ecdb 383
6901d7 384 - http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front
SP 385   page resource.  This is because it's the :term:`default view` (a view
a940e1 386   without a ``name``) for Page resources.
04ecdb 387
6901d7 388 - http://localhost:6543/FrontPage/edit_page invokes the edit view for the
a940e1 389   ``FrontPage`` Page resource.
04ecdb 390
6901d7 391 - http://localhost:6543/add_page/SomePageName invokes the add view for a Page.
04ecdb 392
6901d7 393 - To generate an error, visit http://localhost:6543/add_page which will
617f19 394   generate an ``IndexError: tuple index out of range`` error. You'll see an
a940e1 395   interactive traceback facility provided by :term:`pyramid_debugtoolbar`.