summaryrefslogtreecommitdiff
path: root/docs/tutorials/wiki2/definingviews.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tutorials/wiki2/definingviews.rst')
-rw-r--r--docs/tutorials/wiki2/definingviews.rst138
1 files changed, 80 insertions, 58 deletions
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index a434039ca..c4712faf0 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -26,14 +26,15 @@ is not a dependency of the original "tutorial" application.
We need to add a dependency on the ``docutils`` package to our ``tutorial``
package's ``setup.py`` file by assigning this dependency to the ``requires``
-parameter in the ``setup()`` function.
+list.
Open ``tutorial/setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
- :linenos:
- :emphasize-lines: 14
- :language: python
+ :lines: 11-25
+ :lineno-match:
+ :emphasize-lines: 4
+ :language: python
Only the highlighted line needs to be added.
@@ -50,7 +51,7 @@ were provided at the time we created the project.
As an example, the CSS file will be accessed via
``http://localhost:6543/static/theme.css`` by virtue of the call to the
-``add_static_view`` directive we've made in the ``routes.py`` file. Any number
+``add_static_view`` directive we've made in the ``tutorial/routes.py`` file. Any number
and type of static assets can be placed in this directory (or subdirectories)
and are just referred to by URL or by using the convenience method
``static_url``, e.g., ``request.static_url('<package>:static/foo.css')`` within
@@ -63,7 +64,7 @@ Adding routes to ``routes.py``
This is the `URL Dispatch` tutorial, so let's start by adding some URL patterns
to our app. Later we'll attach views to handle the URLs.
-The ``routes.py`` file contains :meth:`pyramid.config.Configurator.add_route`
+The ``tutorial/routes.py`` file contains :meth:`pyramid.config.Configurator.add_route`
calls which serve to add routes to our application. First we'll get rid of the
existing route created by the template using the name ``'home'``. It's only an
example and isn't relevant to our application.
@@ -96,13 +97,13 @@ order they're registered.
decorator attached to the ``edit_page`` view function, which in turn will be
indicated by ``route_name='edit_page'``.
-As a result of our edits, the ``routes.py`` file should look like the
+As a result of our edits, the ``tutorial/routes.py`` file should look like the
following:
.. literalinclude:: src/views/tutorial/routes.py
- :linenos:
- :emphasize-lines: 3-6
- :language: python
+ :linenos:
+ :emphasize-lines: 3-6
+ :language: python
The highlighted lines are the ones that need to be added or edited.
@@ -117,18 +118,40 @@ The highlighted lines are the ones that need to be added or edited.
behavior in your own apps.
+CSRF protection
+===============
+
+When handling HTML forms that mutate data in our database we need to verify that the form submission is legitimate and not from a URL embedded in a third-party website.
+This is done by adding a unique token to each form that a third-party could not easily guess.
+Read more about CSRF at :ref:`csrf_protection`.
+For this tutorial, we'll store the active CSRF token in a cookie.
+
+Let's add a new ``tutorial/security.py`` file:
+
+.. literalinclude:: src/views/tutorial/security.py
+ :linenos:
+ :language: python
+
+Since we've added a new ``tutorial/security.py`` module, we need to include it.
+Open the file ``tutorial/__init__.py`` and edit the following lines:
+
+.. literalinclude:: src/views/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 9
+ :language: python
+
+On forms that mutate data, we'll be sure to add the CSRF token to the form, using :func:`pyramid.csrf.get_csrf_token`.
+
+
Adding view functions in ``views/default.py``
=============================================
It's time for a major change. Open ``tutorial/views/default.py`` and
-edit it to look like the following:
+replace it with the following:
.. literalinclude:: src/views/tutorial/views/default.py
- :linenos:
- :language: python
- :emphasize-lines: 1-9,14-
-
-The highlighted lines need to be added or edited.
+ :linenos:
+ :language: python
We added some imports, and created a regular expression to find "WikiWords".
@@ -137,7 +160,7 @@ when originally rendered after we selected the ``sqlalchemy`` backend option in
the cookiecutter. It was only an example and isn't relevant to our
application. We also deleted the ``db_err_msg`` string.
-Then we added four :term:`view callable` functions to our ``views/default.py``
+Then we added four :term:`view callable` functions to our ``tutorial/views/default.py``
module, as mentioned in the previous step:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
@@ -163,10 +186,10 @@ The ``view_wiki`` view function
Following is the code for the ``view_wiki`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 17-20
- :lineno-match:
- :linenos:
- :language: python
+ :lines: 16-19
+ :lineno-match:
+ :linenos:
+ :language: python
``view_wiki()`` is the :term:`default view` that gets called when a request is
made to the root URL of our wiki. It always redirects to a URL which
@@ -174,12 +197,12 @@ represents the path to our "FrontPage".
The ``view_wiki`` view callable always redirects to the URL of a Page resource
named "FrontPage". To do so, it returns an instance of the
-:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
+:class:`pyramid.httpexceptions.HTTPSeeOther` class (instances of which implement
the :class:`pyramid.interfaces.IResponse` interface, like
:class:`pyramid.response.Response`). It uses the
:meth:`pyramid.request.Request.route_url` API to construct a URL to the
``FrontPage`` page (i.e., ``http://localhost:6543/FrontPage``), and uses it as
-the "location" of the ``HTTPFound`` response, forming an HTTP redirect.
+the "location" of the ``HTTPSeeOther`` response, forming an HTTP redirect.
The ``view_page`` view function
@@ -188,10 +211,10 @@ The ``view_page`` view function
Here is the code for the ``view_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 22-42
- :lineno-match:
- :linenos:
- :language: python
+ :lines: 21-41
+ :lineno-match:
+ :linenos:
+ :language: python
``view_page()`` is used to display a single page of our wiki. It renders the
:term:`reStructuredText` body of a page (stored as the ``data`` attribute of a
@@ -241,10 +264,10 @@ The ``edit_page`` view function
Here is the code for the ``edit_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 44-56
- :lineno-match:
- :linenos:
- :language: python
+ :lines: 43-55
+ :lineno-match:
+ :linenos:
+ :language: python
``edit_page()`` is invoked when a user clicks the "Edit this Page" button on
the view form. It renders an edit form, but it also acts as the handler for the
@@ -252,14 +275,13 @@ form which it renders. The ``matchdict`` attribute of the request passed to the
``edit_page`` view will have a ``'pagename'`` key matching the name of the page
that the user wants to edit.
-If the view execution *is* a result of a form submission (i.e., the expression
-``'form.submitted' in request.params`` is ``True``), the view grabs the
+If the view execution *is* a result of a form submission (i.e., ``request.method == 'POST'``), the view grabs the
``body`` element of the request parameters and sets it as the ``data``
attribute of the page object. It then redirects to the ``view_page`` view
of the wiki page.
If the view execution is *not* a result of a form submission (i.e., the
-expression ``'form.submitted' in request.params`` is ``False``), the view
+expression ``request.method != 'POST'``), the view
simply renders the edit form, passing the page object and a ``save_url``
which will be used as the action of the generated form.
@@ -279,10 +301,10 @@ The ``add_page`` view function
Here is the code for the ``add_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 58-
- :lineno-match:
- :linenos:
- :language: python
+ :lines: 57-
+ :lineno-match:
+ :linenos:
+ :language: python
``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet
represented as a page in the system. The ``add_link`` function within the
@@ -301,7 +323,7 @@ the database. If it already exists, then the client is redirected to the
``edit_page`` view, else we continue to the next check.
If the view execution *is* a result of a form submission (i.e., the expression
-``'form.submitted' in request.params`` is ``True``), we grab the page body from
+``request.method == 'POST'``), we grab the page body from
the form data, create a Page object with this page body and the name taken from
``matchdict['pagename']``, and save it into the database using
``request.dbession.add``. Since we have not yet covered authentication, we
@@ -312,7 +334,7 @@ Finally, we redirect the client back to the ``view_page`` view for the newly
created page.
If the view execution is *not* a result of a form submission (i.e., the
-expression ``'form.submitted' in request.params`` is ``False``), the view
+expression ``request.method != 'POST'`` is ``False``), the view
callable renders a template. To do so, it generates a ``save_url`` which the
template uses as the form post URL during rendering. We're lazy here, so
we're going to use the same template (``templates/edit.jinja2``) for the add
@@ -339,9 +361,9 @@ Update ``tutorial/templates/layout.jinja2`` with the following content, as
indicated by the emphasized lines:
.. literalinclude:: src/views/tutorial/templates/layout.jinja2
- :linenos:
- :emphasize-lines: 11,35-37
- :language: html
+ :linenos:
+ :emphasize-lines: 11,35-37
+ :language: html
Since we're using a templating engine, we can factor common boilerplate out of
our page templates into reusable components. One method for doing this is
@@ -350,8 +372,7 @@ template inheritance via blocks.
- We have defined two placeholders in the layout template where a child
template can override the content. These blocks are named ``subtitle`` (line
11) and ``content`` (line 36).
-- Please refer to the `Jinja2 documentation <https://palletsprojects.com/p/jinja/>`_ for more information about template
- inheritance.
+- Please refer to the `Jinja2 documentation <https://palletsprojects.com/p/jinja/>`_ for more information about template inheritance.
The ``view.jinja2`` template
@@ -360,8 +381,8 @@ The ``view.jinja2`` template
Create ``tutorial/templates/view.jinja2`` and add the following content:
.. literalinclude:: src/views/tutorial/templates/view.jinja2
- :linenos:
- :language: html
+ :linenos:
+ :language: html
This template is used by ``view_page()`` for displaying a single wiki page.
@@ -384,9 +405,8 @@ The ``edit.jinja2`` template
Create ``tutorial/templates/edit.jinja2`` and add the following content:
.. literalinclude:: src/views/tutorial/templates/edit.jinja2
- :linenos:
- :emphasize-lines: 1,3,12,14,17
- :language: html
+ :linenos:
+ :language: html
This template serves two use cases. It is used by ``add_page()`` and
``edit_page()`` for adding and editing a wiki page. It displays a page
@@ -396,11 +416,13 @@ containing a form and which provides the following:
of the page (line 1).
- Override the ``subtitle`` block to affect the ``<title>`` tag in the
``head`` of the page (line 3).
+- Add the CSRF token to the form (line 13).
+ Without this line, attempts to edit the page would result in a ``400 Bad Request`` error.
- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with
- any existing page data when it is rendered (line 14).
-- A submit button that has the name ``form.submitted`` (line 17).
+ any existing page data when it is rendered (line 15).
+- A submit button (line 18).
- The form POSTs back to the ``save_url`` argument supplied by the view (line
- 12). The view will use the ``body`` and ``form.submitted`` values.
+ 12). The view will use the ``body`` value.
The ``404.jinja2`` template
@@ -409,16 +431,16 @@ The ``404.jinja2`` template
Replace ``tutorial/templates/404.jinja2`` with the following content:
.. literalinclude:: src/views/tutorial/templates/404.jinja2
- :linenos:
- :language: html
+ :linenos:
+ :language: html
This template is linked from the ``notfound_view`` defined in
``tutorial/views/notfound.py`` as shown here:
.. literalinclude:: src/views/tutorial/views/notfound.py
- :linenos:
- :emphasize-lines: 6
- :language: python
+ :linenos:
+ :emphasize-lines: 5
+ :language: python
There are several important things to note about this configuration: