Skip to content

Commit

Permalink
Merge pull request #496 from douglatornell/sql-tut-edits
Browse files Browse the repository at this point in the history
Improve text of SQLAlchemy wiki tutorial.
  • Loading branch information
mcdonc committed Mar 17, 2012
2 parents 2c6f63a + ef501bb commit fca8f01
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 93 deletions.
76 changes: 40 additions & 36 deletions docs/tutorials/wiki2/authorization.rst
Expand Up @@ -7,8 +7,8 @@ Adding Authorization
:app:`Pyramid` provides facilities for :term:`authentication` and :app:`Pyramid` provides facilities for :term:`authentication` and
:term:`authorization`. We'll make use of both features to provide security :term:`authorization`. We'll make use of both features to provide security
to our application. Our application currently allows anyone with access to 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 the server to view, edit, and add pages to our wiki. We'll change that
application to allow only people whom possess a specific username (`editor`) 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 to add and edit wiki pages but we'll continue allowing anyone with access to
the server to view pages. the server to view pages.


Expand Down Expand Up @@ -41,13 +41,12 @@ Open ``models.py`` and add the following statements:
:language: python :language: python


We're going to start to use a custom :term:`root factory` within our 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 ``__init__.py`` file. The objects generated by the root factory will
as the :term:`context` of each request to our application. We do this to be used as the :term:`context` of each request to our application.
allow :app:`Pyramid` declarative security to work properly. The context Those context objects will be decorated with security
object generated by the root factory during a request will be decorated with declarations. When we use a custom root factory to generate
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
our contexts, we can begin to make use of the declarative security features features of :app:`Pyramid`.
of :app:`Pyramid`.


We'll modify our ``__init__.py``, passing in a :term:`root factory` to our 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 :term:`Configurator` constructor. We'll point it at the new class we created
Expand All @@ -65,10 +64,12 @@ to a context is interpreted specially by :app:`Pyramid` as an access control
list during view callable execution. See :ref:`assigning_acls` for more list during view callable execution. See :ref:`assigning_acls` for more
information about what an :term:`ACL` represents. information about what an :term:`ACL` represents.


.. note: Although we don't use the functionality here, the ``factory`` used .. note::
to create route contexts may differ per-route as opposed to globally. See
the ``factory`` argument to Although we don't use the functionality here, the ``factory`` used
:meth:`pyramid.config.Configurator.add_route` for more info. 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.


We'll pass the ``RootFactory`` we created in the step above in as the We'll pass the ``RootFactory`` we created in the step above in as the
``root_factory`` argument to a :term:`Configurator`. ``root_factory`` argument to a :term:`Configurator`.
Expand Down Expand Up @@ -147,9 +148,10 @@ We've given the ``editor`` user membership to the ``group:editors`` by
mapping him to this group in the ``GROUPS`` data structure (``GROUPS = mapping him to this group in the ``GROUPS`` data structure (``GROUPS =
{'editor':['group:editors']}``). Since the ``groupfinder`` function {'editor':['group:editors']}``). Since the ``groupfinder`` function
consults the ``GROUPS`` data structure, this will mean that, as a consults the ``GROUPS`` data structure, this will mean that, as a
result of the ACL attached to the root returned by the root factory, result of the ACL attached to the :term:`context` object returned by
and the permission associated with the ``add_page`` and ``edit_page`` the root factory, and the permission associated with the ``add_page``
views, the ``editor`` user should be able to add and edit pages. and ``edit_page`` views, the ``editor`` user should be able to add and
edit pages.


Adding Login and Logout Views Adding Login and Logout Views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -176,20 +178,20 @@ The ``logout`` view callable will look something like this:
:language: python :language: python


The ``login`` view callable is decorated with two decorators, a The ``login`` view callable is decorated with two decorators, a
``@view_config`` decorators, which associates it with the ``login`` route, ``@view_config`` decorator, which associates it with the ``login``
the other a ``@forbidden_view_config`` decorator which turns it in to an route, and a ``@forbidden_view_config`` decorator which turns it in to
:term:`exception view` when Pyramid raises a an :term:`exception view`. The one which associates it with the
:class:`pyramid.httpexceptions.HTTPForbidden` exception. The one which ``login`` route makes it visible when we visit ``/login``. The other
associates it with the ``login`` route makes it visible when we visit one makes it a :term:`forbidden view`. The forbidden view is
``/login``. The other one makes it a :term:`forbidden view`. The forbidden displayed whenever Pyramid or your application raises an
view is displayed whenever Pyramid or your application raises an :class:`pyramid.httpexceptions.HTTPForbidden` exception. In this
HTTPForbidden exception. In this case, we'll be relying on the forbidden case, we'll be relying on the forbidden view to show the login form
view to show the login form whenver someone attempts to execute an action whenver someone attempts to execute an action which they're not yet
which they're not yet authorized to perform. authorized to perform.


The ``logout`` view callable is decorated with a ``@view_config`` decorator The ``logout`` view callable is decorated with a ``@view_config`` decorator
which associates it with the ``logout`` route. This makes it visible when we 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: 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 the ``pyramid.view.forbidden_view_config`` class, a number of values from the
Expand Down Expand Up @@ -222,8 +224,8 @@ something like this to each view body:
Return a logged_in flag to the renderer Return a logged_in flag to the renderer
--------------------------------------- ---------------------------------------


We'll then change the return value of these views to pass the `resulting We'll then change the return value of these views to pass the resulting
`logged_in`` value to the template, e.g.: ``logged_in`` value to the template, e.g.:


.. code-block:: python .. code-block:: python
:linenos: :linenos:
Expand All @@ -248,12 +250,14 @@ callables which these views reference cannot be invoked without the
authenticated user possessing the ``edit`` permission with respect to the authenticated user possessing the ``edit`` permission with respect to the
current :term:`context`. current :term:`context`.


Adding these ``permission`` arguments causes Pyramid to make the assertion Adding these ``permission`` arguments causes Pyramid to make the
that only users who possess the effective ``edit`` permission at the time of assertion that only users who possess the effective ``edit``
the request may invoke those two views. We've granted the ``group:editors`` permission at the time of the request may invoke those two views.
principal the ``edit`` permission at the root model via its ACL, so only the We've granted the ``group:editors`` :term:`principal` the ``edit``
a user whom is a member of the group named ``group:editors`` will able to permission in the :term:`root factory` via its ACL, so only a user who
invoke the views associated with the ``add_page`` or ``edit_page`` routes. 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 Adding the ``login.pt`` Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -322,7 +326,7 @@ each of the following URLs, check that the result is as expected:
- ``http://localhost:6543/FrontPage`` invokes - ``http://localhost:6543/FrontPage`` invokes
the ``view_page`` view of the FrontPage page object. 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 invokes the edit view for the FrontPage object. It is executable by
only the ``editor`` user. If a different user (or the anonymous only the ``editor`` user. If a different user (or the anonymous
user) invokes it, a login form will be displayed. Supplying the user) invokes it, a login form will be displayed. Supplying the
Expand Down
15 changes: 8 additions & 7 deletions docs/tutorials/wiki2/basiclayout.rst
Expand Up @@ -74,7 +74,7 @@ dictionary of settings parsed from the ``.ini`` file, which contains
deployment-related values such as ``pyramid.reload_templates``, deployment-related values such as ``pyramid.reload_templates``,
``db_string``, etc. ``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): two arguments: ``static`` (the name), and ``static`` (the path):


.. literalinclude:: src/basiclayout/tutorial/__init__.py .. literalinclude:: src/basiclayout/tutorial/__init__.py
Expand Down Expand Up @@ -123,10 +123,11 @@ Finally, ``main`` is finished configuring things, so it uses the
View Declarations via ``views.py`` View Declarations via ``views.py``
---------------------------------- ----------------------------------


Mapping a :term:`route` to code that will be executed when that route's Mapping a :term:`route` to code that will be executed when a match for
pattern matches is done by registering a :term:`view configuration`. Our the route's pattern occurs is done by registering a :term:`view
application uses the :meth:`pyramid.view.view_config` decorator to map view configuration`. Our application uses the
callables to each route, thereby mapping URL patterns to code. :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: Open ``tutorial/tutorial/views.py``. It should already contain the following:


Expand All @@ -151,7 +152,7 @@ Note that ``my_view()`` accepts a single argument named ``request``. This is
the standard call signature for a Pyramid :term:`view callable`. the standard call signature for a Pyramid :term:`view callable`.


Remember in our ``__init__.py`` when we executed the 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 purpose of calling the scan method was to find and process this
``@view_config`` decorator in order to create a view configuration within our ``@view_config`` decorator in order to create a view configuration within our
application. Without being processed by ``scan``, the decorator effectively application. Without being processed by ``scan``, the decorator effectively
Expand Down Expand Up @@ -199,7 +200,7 @@ To give a simple example of a model class, we define one named ``MyModel``:
:linenos: :linenos:
:language: py :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`` and ``value``). It stores these values as ``self.name`` and ``self.value``
within the ``__init__`` function itself. The ``MyModel`` class also has a within the ``__init__`` function itself. The ``MyModel`` class also has a
``__tablename__`` attribute. This informs SQLAlchemy which table to use to ``__tablename__`` attribute. This informs SQLAlchemy which table to use to
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/wiki2/definingmodels.rst
Expand Up @@ -57,7 +57,7 @@ attribute that will hold the body of each page.
Changing ``scripts/initializedb.py`` 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 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 in this file is executed whenever we run the ``initialize_tutorial_db`` command
(as we did in the installation step of this tutorial). (as we did in the installation step of this tutorial).
Expand Down
50 changes: 28 additions & 22 deletions docs/tutorials/wiki2/definingviews.rst
Expand Up @@ -157,17 +157,19 @@ that ``view_page()`` returns a dictionary (as opposed to a :term:`response`
object) is a cue to :app:`Pyramid` that it should try to use a :term:`renderer` 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, associated with the view configuration to render a template. In our case,
the template which will be rendered will be the ``templates/view.pt`` 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 The ``add_page`` view function
------------------------------ ------------------------------


``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet ``add_page()`` is invoked when a user clicks on a *WikiWord* which
represented as a page in the system. The ``check`` function 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 within the ``view_page`` view generates URLs to this view.
handler for the form that is generated when we want to add a page object. ``add_page()`` also acts as a handler for the form that is generated
The ``matchdict`` attribute of the request passed to the ``add_page()`` view when we want to add a page object. The ``matchdict`` attribute of the
will have the values we need to construct URLs and find model objects. 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 .. literalinclude:: src/views/tutorial/views.py
:lines: 45-56 :lines: 45-56
Expand All @@ -179,17 +181,17 @@ the page we'd like to add. If our add view is invoked via,
e.g. ``http://localhost:6543/add_page/SomeName``, the value for e.g. ``http://localhost:6543/add_page/SomeName``, the value for
``'pagename'`` in the ``matchdict`` will be ``'SomeName'``. ``'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 expression ``'form.submitted' in request.params`` is ``False``), the view
callable renders a template. To do so, it generates a "save url" which the 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 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 we're going 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 view as well as the page edit view. To do so we create a dummy Page object
to satisfy the edit form's desire to have *some* page object exposed as in order to satisfy the edit form's desire to have *some* page object
``page``, and :app:`Pyramid` will render the template associated with this exposed as ``page``. :app:`Pyramid` will render the template associated
view to a response. 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 ``'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 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 taken from ``matchdict['pagename']``, and save it into the database using
Expand All @@ -210,12 +212,12 @@ matching the name of the page the user wants to edit.
:linenos: :linenos:
:language: python :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 expression ``'form.submitted' in request.params`` is ``False``), the view
simply renders the edit form, passing the page object and a ``save_url`` simply renders the edit form, passing the page object and a ``save_url``
which will be used as the action of the generated form. 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 ``'form.submitted' in request.params`` is ``True``), the view grabs the
``body`` element of the request parameters and sets it as the ``data`` ``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 attribute of the page object. It then redirects to the ``view_page`` view
Expand All @@ -231,11 +233,12 @@ The views we've added all reference a :term:`template`. Each template is a
The ``view.pt`` Template The ``view.pt`` Template
------------------------ ------------------------


The ``view.pt`` template is used for viewing a single wiki page. It is used The ``view.pt`` template is used for viewing a single wiki page. It
by the ``view_page`` view function. It should have a div that is "structure is used by the ``view_page`` view function. It should have a ``div``
replaced" with the ``content`` value provided by the view. It should also that is "structure replaced" with the ``content`` value provided by
have a link on the rendered page that points at the "edit" URL (the URL which the view. It should also have a link on the rendered page that points
invokes the ``edit_page`` view for the page being viewed). 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 Once we're done with the ``view.pt`` template, it will look a lot like the
below: below:
Expand Down Expand Up @@ -323,11 +326,14 @@ the order they're found in the ``__init__.py`` file.
``route_name='edit_page'``. ``route_name='edit_page'``.


As a result of our edits, the ``__init__.py`` file should look As a result of our edits, the ``__init__.py`` file should look
something like so: something like:


.. literalinclude:: src/views/tutorial/__init__.py .. literalinclude:: src/views/tutorial/__init__.py
:linenos: :linenos:
:language: python :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 Viewing the Application in a Browser
==================================== ====================================
Expand Down
12 changes: 7 additions & 5 deletions docs/tutorials/wiki2/design.rst
Expand Up @@ -4,7 +4,7 @@ Design


Following is a quick overview of our wiki application, to help Following is a quick overview of our wiki application, to help
us understand the changes that we will be doing next in our 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 Overall
------- -------
Expand Down Expand Up @@ -34,9 +34,10 @@ be used as the wiki home page.
Views Views
----- -----


There will be four views to handle the normal operations of There will be four views to handle the normal operations of adding and
viewing, editing and adding wiki pages. Two additional views editing wiki pages, and viewing pages and the wiki front page. Two
will handle the login and logout tasks related to security. additional views will handle the login and logout tasks related to
security.


Security Security
-------- --------
Expand All @@ -59,7 +60,8 @@ Security
| Allow | group:editors | Edit | | 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 Summary
Expand Down
8 changes: 4 additions & 4 deletions docs/tutorials/wiki2/distributing.rst
Expand Up @@ -27,12 +27,12 @@ The output of such a command will be something like:
running sdist running sdist
# ... more output ... # ... more output ...
creating dist creating dist
tar -cf dist/tutorial-0.1.tar tutorial-0.1 tar -cf dist/tutorial-0.0.tar tutorial-0.0
gzip -f9 dist/tutorial-0.1.tar gzip -f9 dist/tutorial-0.0.tar
removing 'tutorial-0.1' (and everything under it) removing 'tutorial-0.0' (and everything under it)
Note that this command creates a tarball in the "dist" subdirectory 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 to show them your cool new application. They should be able to
install it by pointing the ``easy_install`` command directly at it. 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 Or you can upload it to `PyPI <http://pypi.python.org>`_ and share it
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/wiki2/installation.rst
Expand Up @@ -141,7 +141,7 @@ On Windows:
c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q 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::


. .
---------------------------------------------------------------------- ----------------------------------------------------------------------
Expand Down

0 comments on commit fca8f01

Please sign in to comment.