Doug Latornell
2012-03-17 b3e608bac815a7d3fc639d78ec38edc5f97df859
Improve text of SQLAlchemy wiki tutorial.

A mixture of typo fixes and wording improvements.
8 files modified
190 ■■■■ changed files
docs/tutorials/wiki2/authorization.rst 68 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/basiclayout.rst 15 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingmodels.rst 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingviews.rst 50 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/design.rst 12 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/distributing.rst 8 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/installation.rst 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/tests.rst 33 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/authorization.rst
@@ -7,8 +7,8 @@
:app:`Pyramid` provides facilities for :term:`authentication` and
:term:`authorization`.  We'll make use of both features to provide security
to our application.  Our application currently allows anyone with access to
the server to view, edit, and add pages to our wiki.  We'll change our
application to allow only people whom possess a specific username (`editor`)
the server to view, edit, and add pages to our wiki.  We'll change that
to allow only people who possess a specific username (`editor`)
to add and edit wiki pages but we'll continue allowing anyone with access to
the server to view pages.
@@ -41,13 +41,12 @@
   :language: python
We're going to start to use a custom :term:`root factory` within our
``__init__.py`` file.  The objects generated by the root factory will be used
as the :term:`context` of each request to our application.  We do this to
allow :app:`Pyramid` declarative security to work properly.  The context
object generated by the root factory during a request will be decorated with
security declarations. When we begin to use a custom root factory to generate
our contexts, we can begin to make use of the declarative security features
of :app:`Pyramid`.
``__init__.py`` file.  The objects generated by the root factory will
be used as the :term:`context` of each request to our application.
Those context objects will be decorated with security
declarations. When we use a custom root factory to generate
our contexts, we can begin to make use of the declarative security
features of :app:`Pyramid`.
We'll modify our ``__init__.py``, passing in a :term:`root factory` to our
:term:`Configurator` constructor.  We'll point it at the new class we created
@@ -65,7 +64,7 @@
list during view callable execution.  See :ref:`assigning_acls` for more
information about what an :term:`ACL` represents.
.. note: Although we don't use the functionality here, the ``factory`` used
.. note:: Although we don't use the functionality here, the ``factory`` used
   to create route contexts may differ per-route as opposed to globally.  See
   the ``factory`` argument to
   :meth:`pyramid.config.Configurator.add_route` for more info.
@@ -147,9 +146,10 @@
mapping him to this group in the ``GROUPS`` data structure (``GROUPS =
{'editor':['group:editors']}``).  Since the ``groupfinder`` function
consults the ``GROUPS`` data structure, this will mean that, as a
result of the ACL attached to the root returned by the root factory,
and the permission associated with the ``add_page`` and ``edit_page``
views, the ``editor`` user should be able to add and edit pages.
result of the ACL attached to the :term:`context` object returned by
the root factory, and the permission associated with the ``add_page``
and ``edit_page`` views, the ``editor`` user should be able to add and
edit pages.
Adding Login and Logout Views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -176,20 +176,20 @@
   :language: python
The ``login`` view callable is decorated with two decorators, a
``@view_config`` decorators, which associates it with the ``login`` route,
the other a ``@forbidden_view_config`` decorator which turns it in to an
:term:`exception view` when Pyramid raises a
:class:`pyramid.httpexceptions.HTTPForbidden` exception.  The one which
associates it with the ``login`` route makes it visible when we visit
``/login``.  The other one makes it a :term:`forbidden view`.  The forbidden
view is displayed whenever Pyramid or your application raises an
HTTPForbidden exception.  In this case, we'll be relying on the forbidden
view to show the login form whenver someone attempts to execute an action
which they're not yet authorized to perform.
``@view_config`` decorator, which associates it with the ``login``
route, and a ``@forbidden_view_config`` decorator which turns it in to
an :term:`exception view`.  The one which associates it with the
``login`` route makes it visible when we visit ``/login``.  The other
one makes it a :term:`forbidden view`.  The forbidden view is
displayed whenever Pyramid or your application raises an
:class:`pyramid.httpexceptions.HTTPForbidden` exception.  In this
case, we'll be relying on the forbidden view to show the login form
whenver someone attempts to execute an action which they're not yet
authorized to perform.
The ``logout`` view callable is decorated with a ``@view_config`` decorator
which associates it with the ``logout`` route.  This makes it visible when we
visit ``/login``.
visit ``/logout``.
We'll need to import some stuff to service the needs of these two functions:
the ``pyramid.view.forbidden_view_config`` class, a number of values from the
@@ -222,8 +222,8 @@
Return a logged_in flag to the renderer
---------------------------------------
We'll then change the return value of these views to pass the `resulting
`logged_in`` value to the template, e.g.:
We'll then change the return value of these views to pass the resulting
``logged_in`` value to the template, e.g.:
.. code-block:: python
   :linenos:
@@ -248,12 +248,14 @@
authenticated user possessing the ``edit`` permission with respect to the
current :term:`context`.
Adding these ``permission`` arguments causes Pyramid to make the assertion
that only users who possess the effective ``edit`` permission at the time of
the request may invoke those two views.  We've granted the ``group:editors``
principal the ``edit`` permission at the root model via its ACL, so only the
a user whom is a member of the group named ``group:editors`` will able to
invoke the views associated with the ``add_page`` or ``edit_page`` routes.
Adding these ``permission`` arguments causes Pyramid to make the
assertion that only users who possess the effective ``edit``
permission at the time of the request may invoke those two views.
We've granted the ``group:editors`` :term:`principal` the ``edit``
permission in the :term:`root factory` via its ACL, so only a user who
is a member of the group named ``group:editors`` will be able to
invoke the views associated with the ``add_page`` or ``edit_page``
routes.
Adding the ``login.pt`` Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -322,7 +324,7 @@
- ``http://localhost:6543/FrontPage`` invokes
  the ``view_page`` view of the FrontPage page object.
- ``http://localhost:6543/edit_page/FrontPage``
- ``http://localhost:6543/FrontPage/edit_page``
  invokes the edit view for the FrontPage object.  It is executable by
  only the ``editor`` user.  If a different user (or the anonymous
  user) invokes it, a login form will be displayed.  Supplying the
docs/tutorials/wiki2/basiclayout.rst
@@ -74,7 +74,7 @@
deployment-related values such as ``pyramid.reload_templates``,
``db_string``, etc.
``'main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
two arguments: ``static`` (the name), and ``static`` (the path):
   .. literalinclude:: src/basiclayout/tutorial/__init__.py
@@ -123,10 +123,11 @@
View Declarations via ``views.py``
----------------------------------
Mapping a :term:`route` to code that will be executed when that route's
pattern matches is done by registering a :term:`view configuration`. Our
application uses the :meth:`pyramid.view.view_config` decorator to map view
callables to each route, thereby mapping URL patterns to code.
Mapping a :term:`route` to code that will be executed when a match for
the route's pattern occurs is done by registering a :term:`view
configuration`. Our application uses the
:meth:`pyramid.view.view_config` decorator to map view callables to
each route, thereby mapping URL patterns to code.
Open ``tutorial/tutorial/views.py``.  It should already contain the following:
@@ -151,7 +152,7 @@
the standard call signature for a Pyramid :term:`view callable`.
Remember in our ``__init__.py`` when we executed the
:meth:`pyramid.config.Configurator.scan` method, e.g. ``config.scan()``?  The
:meth:`pyramid.config.Configurator.scan` method, i.e. ``config.scan()``?  The
purpose of calling the scan method was to find and process this
``@view_config`` decorator in order to create a view configuration within our
application.  Without being processed by ``scan``, the decorator effectively
@@ -199,7 +200,7 @@
      :linenos:
      :language: py
Our sample model has an ``__init__`` that takes a two arguments (``name``,
Our example model has an ``__init__`` that takes a two arguments (``name``,
and ``value``).  It stores these values as ``self.name`` and ``self.value``
within the ``__init__`` function itself.  The ``MyModel`` class also has a
``__tablename__`` attribute.  This informs SQLAlchemy which table to use to
docs/tutorials/wiki2/definingmodels.rst
@@ -57,7 +57,7 @@
Changing ``scripts/initializedb.py``
------------------------------------
We haven't looked at the guts of this file yet, but within the ``scripts``
We haven't looked at the details of this file yet, but within the ``scripts``
directory of your ``tutorial`` package is a file named ``initializedb.py``.  Code
in this file is executed whenever we run the ``initialize_tutorial_db`` command
(as we did in the installation step of this tutorial).
docs/tutorials/wiki2/definingviews.rst
@@ -157,17 +157,19 @@
object) is a cue to :app:`Pyramid` that it should try to use a :term:`renderer`
associated with the view configuration to render a template.  In our case,
the template which will be rendered will be the ``templates/view.pt``
template, as per the configuration put into effect in ``__init__.py``.
template, as indicated in the ``@view_config`` decorator that is applied to
``view_page()``.
The ``add_page`` view function
------------------------------
``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet
represented as a page in the system.  The ``check`` function
within the ``view_page`` view generates URLs to this view.  It also acts as a
handler for the form that is generated when we want to add a page object.
The ``matchdict`` attribute of the request passed to the ``add_page()`` view
will have the values we need to construct URLs and find model objects.
``add_page()`` is invoked when a user clicks on a *WikiWord* which
isn't yet represented as a page in the system.  The ``check`` function
within the ``view_page`` view generates URLs to this view.
``add_page()`` also acts as a handler for the form that is generated
when we want to add a page object.  The ``matchdict`` attribute of the
request passed to the ``add_page()`` view will have the values we need
to construct URLs and find model objects.
.. literalinclude:: src/views/tutorial/views.py
   :lines: 45-56
@@ -179,17 +181,17 @@
e.g. ``http://localhost:6543/add_page/SomeName``, the value for
``'pagename'`` in the ``matchdict`` will be ``'SomeName'``.
If the view execution is *not* a result of a form submission (if the
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
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 trying to use the same template (``templates/edit.pt``) for the add
view as well as the page edit view, so we create a dummy Page object in order
to satisfy the edit form's desire to have *some* page object exposed as
``page``, and :app:`Pyramid` will render the template associated with this
view to a response.
we're going to use the same template (``templates/edit.pt``) for the add
view as well as the page edit view. To do so we create a dummy Page object
in order to satisfy the edit form's desire to have *some* page object
exposed as ``page``. :app:`Pyramid` will render the template associated
with this view to a response.
If the view execution *is* a result of a form submission (if the expression
If the view execution *is* a result of a form submission (i.e. the expression
``'form.submitted' in request.params`` is ``True``), we scrape 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
@@ -210,12 +212,12 @@
   :linenos:
   :language: python
If the view execution is *not* a result of a form submission (if the
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
simply renders the edit form, passing the page object and a ``save_url``
which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
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
``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
@@ -231,11 +233,12 @@
The ``view.pt`` Template
------------------------
The ``view.pt`` template is used for viewing a single wiki page.  It is used
by the ``view_page`` view function.  It should have a div that is "structure
replaced" with the ``content`` value provided by the view.  It should also
have a link on the rendered page that points at the "edit" URL (the URL which
invokes the ``edit_page`` view for the page being viewed).
The ``view.pt`` template is used for viewing a single wiki page.  It
is used by the ``view_page`` view function.  It should have a ``div``
that is "structure replaced" with the ``content`` value provided by
the view.  It should also have a link on the rendered page that points
at the "edit" URL (the URL which invokes the ``edit_page`` view for
the page being viewed).
Once we're done with the ``view.pt`` template, it will look a lot like the
below:
@@ -323,11 +326,14 @@
   ``route_name='edit_page'``.
As a result of our edits, the ``__init__.py`` file should look
something like so:
something like:
.. literalinclude:: src/views/tutorial/__init__.py
   :linenos:
   :language: python
   :emphasize-lines: 13-16
(The highlighted lines are the ones that need to be added or edited.)
Viewing the Application in a Browser
====================================
docs/tutorials/wiki2/design.rst
@@ -4,7 +4,7 @@
Following is a quick overview of our wiki application, to help
us understand the changes that we will be doing next in our
default files generated by the paster scafffold.
default files generated by the ``alchemy`` scaffold.
Overall
-------
@@ -34,9 +34,10 @@
Views
-----
There will be four views to handle the normal operations of
viewing, editing and adding wiki pages.  Two additional views
will handle the login and logout tasks related to security.
There will be four views to handle the normal operations of adding and
editing wiki pages, and viewing pages and the wiki front page.  Two
additional views will handle the login and logout tasks related to
security.
Security
--------
@@ -59,7 +60,8 @@
  | Allow    | group:editors  | Edit           |
  +----------+----------------+----------------+
- Permission declarations for the views.
- Permission declarations are added to the views to assert the
  security policies as each request is handled.
Summary
docs/tutorials/wiki2/distributing.rst
@@ -27,12 +27,12 @@
   running sdist
   # ... more output ...
   creating dist
   tar -cf dist/tutorial-0.1.tar tutorial-0.1
   gzip -f9 dist/tutorial-0.1.tar
   removing 'tutorial-0.1' (and everything under it)
   tar -cf dist/tutorial-0.0.tar tutorial-0.0
   gzip -f9 dist/tutorial-0.0.tar
   removing 'tutorial-0.0' (and everything under it)
Note that this command creates a tarball in the "dist" subdirectory
named ``tutorial-0.1.tar.gz``.  You can send this file to your friends
named ``tutorial-0.0.tar.gz``.  You can send this file to your friends
to show them your cool new application.  They should be able to
install it by pointing the ``easy_install`` command directly at it.
Or you can upload it to `PyPI <http://pypi.python.org>`_ and share it
docs/tutorials/wiki2/installation.rst
@@ -141,7 +141,7 @@
   c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
For a successful test run, you should see output like this::
For a successful test run, you should see output that ends like this::
  .
  ----------------------------------------------------------------------
docs/tutorials/wiki2/tests.rst
@@ -4,31 +4,30 @@
We will now add tests for the models and the views and a few functional
tests in the ``tests.py``.  Tests ensure that an application works, and
that it continues to work after some changes are made in the future.
that it continues to work after changes are made in the future.
Testing the Models
==================
We write a test class for the model class ``Page`` and another test class
for the ``initialize_sql`` function.
To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
result of the ``alchemy`` scaffold.  We'll add a test class named
``PageModelTests`` for the ``Page`` model.
To test the model class ``Page`` we'll add a new ``PageModelTests``
class to our ``tests.py`` file that was generated as part of the
``alchemy`` scaffold.
Testing the Views
=================
We'll modify our ``tests.py`` file, adding tests for each view function we
added above.  As a result, we'll *delete* the ``ViewTests`` test in the file,
and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
``AddPageTests``, and ``EditPageTests``.  These test the ``view_wiki``,
``view_page``, ``add_page``, and ``edit_page`` views respectively.
We'll modify our ``tests.py`` file, adding tests for each view
function we added above.  As a result, we'll *delete* the
``ViewTests`` class that the ``alchemy`` scaffold provided, and add
four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
``AddPageTests``, and ``EditPageTests``.  These test the
``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views
respectively.
Functional tests
================
We test the whole application, covering security aspects that are not
We'll test the whole application, covering security aspects that are not
tested in the unit tests, like logging in, logging out, checking that
the ``viewer`` user cannot add or edit pages, but the ``editor`` user
can, and so on.
@@ -36,8 +35,7 @@
Viewing the results of all our edits to ``tests.py``
====================================================
Once we're done with the ``tests.py`` module, it will look a lot like the
below:
Once we're done with the ``tests.py`` module, it will look a lot like:
.. literalinclude:: src/tests/tutorial/tests.py
   :linenos:
@@ -55,6 +53,7 @@
   :linenos:
   :language: python
   :lines: 9-20
   :emphasize-lines: 19
After we've added a dependency on WebTest in ``setup.py``, we need to rerun
``setup.py develop`` to get WebTest installed into our virtualenv.  Assuming
@@ -88,12 +87,12 @@
   c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
The expected result looks something like:
The expected result ends something like:
.. code-block:: text
   ......................
   ----------------------------------------------------------------------
   Ran 22 tests in 2.700s
   Ran 21 tests in 2.700s
   OK