summaryrefslogtreecommitdiff
path: root/docs/tutorials
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tutorials')
-rw-r--r--docs/tutorials/wiki/authorization.rst481
-rw-r--r--docs/tutorials/wiki/definingviews.rst92
-rw-r--r--docs/tutorials/wiki/design.rst19
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models.py6
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py22
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models.py6
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py22
-rw-r--r--docs/tutorials/wiki2/authorization.rst426
-rw-r--r--docs/tutorials/wiki2/definingviews.rst92
-rw-r--r--docs/tutorials/wiki2/design.rst17
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py6
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py6
12 files changed, 688 insertions, 507 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index e599e7086..9e0bf0f09 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -2,121 +2,186 @@
Adding Authorization
====================
-Our application currently allows anyone with access to the server to view,
-edit, and add pages to our wiki. For purposes of demonstration we'll change
-our application to allow people who are members of a *group* named
-``group:editors`` to add and edit wiki pages but we'll continue allowing
-anyone with access to the server to view pages. :app:`Pyramid` provides
-facilities for :term:`authorization` and :term:`authentication`. We'll make
-use of both features to provide security to our application.
-
-We will add an :term:`authentication policy` and an
-:term:`authorization policy` to our :term:`application
-registry`, add a ``security.py`` module and give our :term:`root`
-resource an :term:`ACL`.
-
-Then we will add ``login`` and ``logout`` views, and modify the
-existing views to make them return a ``logged_in`` flag to the
-renderer and add :term:`permission` declarations to their ``view_config``
-decorators.
-
-Finally, we will add a ``login.pt`` template and change the existing
-``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in.
-
-The source code for this tutorial stage can be browsed via
+: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 that
+to allow only people who are members of a *group* named ``group:editors``
+to add and edit wiki pages but we'll continue allowing
+anyone with access to the server to view pages.
+
+We will also add a login page and a logout link on all the
+pages. The login page will be shown when a user is denied
+access to any of the views that require a permission, instead of
+a default "403 Forbidden" page.
+
+We will implement the access control with the following steps:
+
+* Add users and groups (``security.py``, a new module).
+* Add an :term:`ACL` (``models.py``).
+* Add an :term:`authentication policy` and an :term:`authorization policy`
+ (``__init__.py``).
+* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
+ views (``views.py``).
+
+Then we will add the login and logout feature:
+
+* Add ``login`` and ``logout`` views (``views.py``).
+* Add a login template (``login.pt``).
+* Make the existing views return a ``logged_in`` flag to the renderer (``views.py``).
+* Add a "Logout" link to be shown when logged in and viewing or editing a page
+ (``view.pt``, ``edit.pt``).
+
+The source code for this tutorial stage can be browsed at
`http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki/src/authorization/
<http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki/src/authorization/>`_.
-Add Authentication and Authorization Policies
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Access Control
+--------------
-We'll change our package's ``__init__.py`` file to enable an
-``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable
-declarative security checking. We need to import the new policies:
+Add users and groups
+~~~~~~~~~~~~~~~~~~~~
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 4-5,8
+Create a new ``tutorial/tutorial/security.py`` module with the
+following content:
+
+.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
:language: python
-Then, we'll add those policies to the configuration:
+The ``groupfinder`` function accepts a userid and a request and
+returns one of these values:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 17-22
+- If the userid exists in the system, it will return a
+ sequence of group identifiers (or an empty sequence if the user
+ isn't a member of any groups).
+- If the userid *does not* exist in the system, it will
+ return ``None``.
+
+For example, ``groupfinder('editor', request )`` returns ['group:editor'],
+``groupfinder('viewer', request)`` returns [], and ``groupfinder('admin', request)``
+returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy`
+"callback" that will provide the :term:`principal` or principals
+for a user.
+
+In a production system, user and group
+data will most often come from a database, but here we use "dummy"
+data to represent user and groups sources.
+
+Add an ACL
+~~~~~~~~~~
+
+Open ``tutorial/tutorial/models.py`` and add the following import
+statement at the head:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 4-7
:linenos:
:language: python
-Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two
-arguments: ``secret`` and ``callback``. ``secret`` is a string representing
-an encryption key used by the "authentication ticket" machinery represented
-by this policy: it is required. The ``callback`` is a reference to a
-``groupfinder`` function in the ``tutorial`` package's ``security.py`` file.
-We haven't added that module yet, but we're about to.
+Add the following lines to the ``Wiki`` class:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 9-13
+ :linenos:
+ :emphasize-lines: 4-5
+ :language: python
-When you're done, your ``__init__.py`` will
-look like so:
+We import :data:`~pyramid.security.Allow`, an action that
+means that permission is allowed:, and
+:data:`~pyramid.security.Everyone`, a special :term:`principal`
+that is associated to all requests. Both are used in the
+:term:`ACE` entries that make up the ACL.
+
+The ACL is a list that needs to be named `__acl__` and be an
+attribute of a class. We define an :term:`ACL` with two
+:term:`ACE` entries: the first entry allows any user the `view`
+permission. The second entry allows the ``group:editors``
+principal the `edit` permission.
+
+The ``Wiki`` class that contains the ACL is the :term:`resource`
+constructor for the :term:`root` resource, which is
+a ``Wiki`` instance. The ACL is
+provided to each view in the :term:`context` of the request, as
+the ``context`` attribute.
+
+It's only happenstance that we're assigning this ACL at class scope. An ACL
+can be attached to an object *instance* too; this is how "row level security"
+can be achieved in :app:`Pyramid` applications. We actually only need *one*
+ACL for the entire system, however, because our security requirements are
+simple, so this feature is not demonstrated. See
+:ref:`assigning_acls` for more information about what an
+:term:`ACL` represents.
+
+Add Authentication and Authorization Policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Open ``tutorial/__init__.py`` and
+add these import statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 4-5,8
:linenos:
:language: python
-Add ``security.py``
-~~~~~~~~~~~~~~~~~~~
-
-Add a ``security.py`` module within your package (in the same
-directory as ``__init__.py``, ``views.py``, etc.) with the following
-content:
+Now add those policies to the configuration:
-.. literalinclude:: src/authorization/tutorial/security.py
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 17-22
:linenos:
+ :emphasize-lines: 1-3,5-6
:language: python
-The ``groupfinder`` function defined here is an :term:`authentication policy`
-"callback"; it is a callable that accepts a userid and a request. If the
-userid exists in the system, the callback will return a sequence of group
-identifiers (or an empty sequence if the user isn't a member of any groups).
-If the userid *does not* exist in the system, the callback will return
-``None``. In a production system, user and group data will most often come
-from a database, but here we use "dummy" data to represent user and groups
-sources. Note that the ``editor`` user is a member of the ``group:editors``
-group in our dummy group data (the ``GROUPS`` data structure).
-
-Give Our Root Resource an ACL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+(Only the highlighted lines need to be added.)
+
+We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth
+ticket that may be included in the request, and an ``ACLAuthorizationPolicy``
+that uses an ACL to determine the allow or deny outcome for a view.
-We need to give our root resource object an :term:`ACL`. This ACL will be
-sufficient to provide enough information to the :app:`Pyramid` security
-machinery to challenge a user who doesn't have appropriate credentials when
-he attempts to invoke the ``add_page`` or ``edit_page`` views.
+Note that the
+:class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor
+accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string
+representing an encryption key used by the "authentication ticket" machinery
+represented by this policy: it is required. The ``callback`` is the
+``groupfinder()`` function that we created before.
-We need to perform some imports at module scope in our ``models.py`` file:
+Add permission declarations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Add a ``permission='edit'`` parameter to the ``@view_config``
+decorator for ``add_page()`` and ``edit_page()``, for example:
.. code-block:: python
:linenos:
+ :emphasize-lines: 2
+
+ @view_config(route_name='add_page', renderer='templates/edit.pt',
+ permission='edit')
+
+(Only the highlighted line needs to be added.)
- from pyramid.security import Allow
- from pyramid.security import Everyone
+The result is that only users who possess the ``edit``
+permission at the time of the request may invoke those two views.
-Our root resource object is a ``Wiki`` instance. We'll add the following
-line at class scope to our ``Wiki`` class:
+Add a ``permission='view'`` parameter to the ``@view_config``
+decorator for ``view_wiki()`` and ``view_page()``, like this:
.. code-block:: python
:linenos:
+ :emphasize-lines: 2
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
+ @view_config(route_name='view_page', renderer='templates/view.pt',
+ permission='view')
-It's only happenstance that we're assigning this ACL at class scope. An ACL
-can be attached to an object *instance* too; this is how "row level security"
-can be achieved in :app:`Pyramid` applications. We actually only need *one*
-ACL for the entire system, however, because our security requirements are
-simple, so this feature is not demonstrated.
+(Only the highlighted line needs to be added.)
-Our resulting ``models.py`` file will now look like so:
+This allows anyone to invoke these two views.
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
- :language: python
+We are done with the changes needed to control access. The
+changes that follow will add the login and logout feature.
+
+Login, Logout
+-------------
Add Login and Logout Views
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -124,124 +189,103 @@ Add Login and Logout Views
We'll add a ``login`` view which renders a login form and processes
the post from the login form, checking credentials.
-We'll also add a ``logout`` view to our application and provide a link
-to it. This view will clear the credentials of the logged in user and
-redirect back to the front page.
+We'll also add a ``logout`` view callable to our application and
+provide a link to it. This view will clear the credentials of the
+logged in user and redirect back to the front page.
-We'll add these views to the existing ``views.py`` file we have in our
-project. Here's what the ``login`` view callable will look like:
+Add the following import statements to the
+head of ``tutorial/tutorial/views.py``:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 86-113
+ :lines: 6-13,15-17
:linenos:
+ :emphasize-lines: 3,6-9,11
:language: python
-Here's what the ``logout`` view callable will look like:
+(Only the highlighted lines need to be added.)
+
+:meth:`~pyramid.view.forbidden_view_config` will be used
+to customize the default 403 Forbidden page.
+:meth:`~pyramid.security.remember` and
+:meth:`~pyramid.security.forget` help to create and
+expire an auth ticket cookie.
+
+Now add the ``login`` and ``logout`` views:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 115-119
+ :lines: 87-120
:linenos:
:language: python
-Note that the ``login`` view callable has *two* view configuration
-decorators. The order of these decorators is unimportant. Each just adds a
-different :term:`view configuration` for the ``login`` view callable.
-
-The first view configuration decorator configures the ``login`` view callable
-so it will be invoked when someone visits ``/login`` (when the context is a
-Wiki and the view name is ``login``). The second decorator, named
-``forbidden_view_config`` specifies a :term:`forbidden view`. This
-configures our login view to be presented to the user when :app:`Pyramid`
-detects that a view invocation can not be authorized. Because we've
-configured a forbidden view, the ``login`` view callable will be invoked
-whenever one of our users tries to execute a view callable that they are not
-allowed to invoke as determined by the :term:`authorization policy` in use.
-In our application, for example, this means that if a user has not logged in,
-and he tries to add or edit a Wiki page, he will be shown the login form.
-Before being allowed to continue on to the add or edit form, he will have to
-provide credentials that give him permission to add or edit via this login
-form.
-
-Note that we're relying on some additional imports within the bodies of these
-views (e.g. ``remember`` and ``forget``). We'll see a rendering of the
-entire views.py file a little later here to show you where those come from.
-
-Change Existing Views
-~~~~~~~~~~~~~~~~~~~~~
-
-In order to indicate whether the current user is logged in, we need to change
-each of our ``view_page``, ``edit_page`` and ``add_page`` views in
-``views.py`` to pass a "logged in" parameter into its template. We'll add
-something like this to each view body:
+``login()`` is decorated with two decorators:
-.. code-block:: python
+- a ``@view_config`` decorator which associates it with the
+ ``login`` route and makes it visible when we visit ``/login``,
+- a ``@forbidden_view_config`` decorator which turns it into
+ an :term:`forbidden view`. ``login()`` will be invoked
+ when a users tries to execute a view callable that
+ they are not allowed to. For example, if a user has not logged in
+ and tries to add or edit a Wiki page, he will be shown the
+ login form before being allowed to continue on.
+
+The order of these two :term:`view configuration` decorators
+is unimportant.
+
+``logout()`` is decorated with a ``@view_config`` decorator
+which associates it with the ``logout`` route. It will be
+invoked when we visit ``/logout``.
+
+Add the ``login.pt`` Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create ``tutorial/tutorial/templates/login.pt`` with the following
+content:
+
+.. literalinclude:: src/authorization/tutorial/templates/login.pt
+ :language: xml
+
+The above template is referred to within the login view we just
+added to ``views.py``.
+
+Return a logged_in flag to the renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Add the following line to the import at the head of
+``tutorial/tutorial/views.py``:
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 11-15
:linenos:
+ :emphasize-lines: 4
+ :language: python
- from pyramid.security import authenticated_userid
- logged_in = authenticated_userid(request)
+(Only the highlighted line needs to be added.)
-We'll then change the return value of each view that has an associated
-``renderer`` to pass the resulting ``logged_in`` value to the
-template. For example:
+Add a ``logged_in`` parameter to the return value of
+``view_page()``, ``edit_page()`` and ``add_page()``,
+like this:
.. code-block:: python
:linenos:
+ :emphasize-lines: 4
- return dict(page = context,
+ return dict(page = page,
content = content,
- logged_in = logged_in,
- edit_url = edit_url)
-
-Add ``permission`` Declarations to our ``view_config`` Decorators
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To protect each of our views with a particular permission, we need to pass a
-``permission`` argument to each of our :class:`pyramid.view.view_config`
-decorators. To do so, within ``views.py``:
-
-- We add ``permission='view'`` to the decorator attached to the
- ``view_wiki`` and ``view_page`` view functions. This makes the
- assertion that only users who possess the ``view`` permission
- against the context resource at the time of the request may
- invoke these views. We've granted
- :data:`pyramid.security.Everyone` the view permission at the
- root model via its ACL, so everyone will be able to invoke the
- ``view_wiki`` and ``view_page`` views.
-
-- We add ``permission='edit'`` to the decorator attached to the
- ``add_page`` and ``edit_page`` view functions. This makes the
- assertion that only users who possess the effective ``edit``
- permission against the context resource at the time of the
- request may invoke these views. We've granted the
- ``group:editors`` principal the ``edit`` permission at the
- root model via its ACL, so only a user whom is a member of
- the group named ``group:editors`` will able to invoke the
- ``add_page`` or ``edit_page`` views. We've likewise given
- the ``editor`` user membership to this group via the
- ``security.py`` file by mapping him to the ``group:editors``
- group in the ``GROUPS`` data structure (``GROUPS
- = {'editor':['group:editors']}``); the ``groupfinder``
- function consults the ``GROUPS`` data structure. This means
- that the ``editor`` user can add and edit pages.
+ edit_url = edit_url,
+ logged_in = authenticated_userid(request))
-Add the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Add a ``login.pt`` template to your templates directory. It's
-referred to within the login view we just added to ``views.py``.
+(Only the highlighted line needs to be added.)
-.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: xml
+:meth:`~pyramid.security.authenticated_userid()` will return None
+if the user is not authenticated, or some user id it the user
+is authenticated.
-Change ``view.pt`` and ``edit.pt``
+Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
-display a "Logout" link if someone is logged in. This link will
-invoke the logout view.
-
-To do so we'll add this to both templates within the ``<div id="right"
-class="app-welcome align-right">`` div:
+Open ``tutorial/tutorial/templates/edit.pt`` and
+``tutorial/tutorial/templates/view.pt`` and add this within the
+``<div id="right" class="app-welcome align-right">`` div:
.. code-block:: xml
@@ -249,57 +293,96 @@ class="app-welcome align-right">`` div:
<a href="${request.application_url}/logout">Logout</a>
</span>
-See Our Changes To ``views.py`` and our Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The attribute ``tal:condition="logged_in"`` will make the element be
+included when ``logged_in`` is any user id. The link will invoke
+the logout view. The above element will not be included if ``logged_in``
+is ``None``, such as when a user is not authenticated.
+
+Seeing Our Changes
+------------------
+
+Our ``tutorial/tutorial/__init__.py`` will look something like this
+when we're done:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 4-5,8,17-19,21-22
+ :language: python
+
+(Only the highlighted lines need to be added.)
+
+Our ``tutorial/tutorial/models.py`` will look something like this
+when we're done:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :linenos:
+ :emphasize-lines: 4-7,12-13
+ :language: python
-Our ``views.py`` module will look something like this when we're done:
+(Only the highlighted lines need to be added.)
+
+Our ``tutorial/tutorial/views.py`` will look something like this
+when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
+ :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120
:language: python
-Our ``edit.pt`` template will look something like this when we're done:
+(Only the highlighted lines need to be added.)
+
+Our ``tutorial/tutorial/templates/edit.pt`` template will look
+something like this when we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
:linenos:
+ :emphasize-lines: 41-43
:language: xml
-Our ``view.pt`` template will look something like this when we're done:
+(Only the highlighted lines need to be added.)
+
+Our ``tutorial/tutorial/templates/view.pt`` template will look
+something like this when we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
:linenos:
+ :emphasize-lines: 41-43
:language: xml
-View the Application in a Browser
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+(Only the highlighted lines need to be added.)
-We can finally examine our application in a browser. The views we'll try are
-as follows:
+Viewing the Application in a Browser
+------------------------------------
-- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki``
- view. This always redirects to the ``view_page`` view of the ``FrontPage``
- page resource. It is executable by any user.
+We can finally examine our application in a browser (See
+:ref:`wiki-start-the-application`). Launch a browser and visit
+each of the following URLs, check that the result is as expected:
-- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the
- ``view_page`` view of the ``FrontPage`` Page resource. This is because
+- ``http://localhost:6543/`` invokes the
+ ``view_wiki`` view. This always redirects to the ``view_page`` view
+ of the ``FrontPage`` Page resource. It is executable by any user.
+
+- ``http://localhost:6543/FrontPage`` invokes
+ the ``view_page`` view of the ``FrontPage`` Page resource. This is because
it's the :term:`default view` (a view without a ``name``) for ``Page``
resources. It is executable by any user.
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes
- the edit view for the ``FrontPage`` Page resource. 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 credentials with
- the username ``editor``, password ``editor`` will show the edit page form
- being displayed.
+- ``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
+ credentials with the username ``editor``, password ``editor`` will
+ display the edit page form.
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
- browser invokes the add view for a page. It is executable by only
+- ``http://localhost:6543/add_page/SomePageName``
+ invokes the add view for a page. 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
credentials with the username ``editor``, password ``editor`` will
- show the edit page form being displayed.
+ display the edit page form.
-- After logging in (as a result of hitting an edit or add page and
- submitting the login form with the ``editor`` credentials), we'll see
- a Logout link in the upper right hand corner. When we click it,
- we're logged out, and redirected back to the front page.
+- After logging in (as a result of hitting an edit or add page
+ and submitting the login form with the ``editor``
+ credentials), we'll see a Logout link in the upper right hand
+ corner. When we click it, we're logged out, and redirected
+ back to the front page.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 12bfa8b84..28cecb787 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -229,60 +229,63 @@ this:
Adding Templates
================
-Most view callables we've added expected to be rendered via a
-:term:`template`. The default templating systems in :app:`Pyramid` are
-:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`,
-which is an XML-based templating language. Mako is a non-XML-based
-templating language. Because we had to pick one, we chose Chameleon for this
-tutorial.
-
-The templates we create will live in the ``templates`` directory of our
+The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
+reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT`
+template. These templates will live in the ``templates`` directory of our
tutorial package. Chameleon templates must have a ``.pt`` extension to be
recognized as such.
The ``view.pt`` Template
------------------------
-The ``view.pt`` template is used for viewing a single 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:
+Create ``tutorial/tutorial/templates/view.pt`` and add the following
+content:
.. literalinclude:: src/views/tutorial/templates/view.pt
+ :linenos:
:language: xml
-.. note::
+This template is used by ``view_page()`` for displaying a single
+wiki page. It includes:
- The names available for our use in a template are always those that
- are present in the dictionary returned by the view callable. But our
- templates make use of a ``request`` object that none of our tutorial views
- return in their dictionary. This value appears as if "by magic".
- However, ``request`` is one of several names that are available "by
- default" in a template when a template renderer is used. See
- :ref:`chameleon_template_renderers` for more information about other names
- that are available by default in a template when a template is used as a
- renderer.
+- A ``div`` element that is replaced with the ``content``
+ value provided by the view (rows 45-47). ``content``
+ contains HTML, so the ``structure`` keyword is used
+ to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+- A link that points
+ at the "edit" URL which invokes the ``edit_page`` view for
+ the page being viewed (rows 49-51).
The ``edit.pt`` Template
------------------------
-The ``edit.pt`` template is used for adding and editing a Page. It is used
-by the ``add_page`` and ``edit_page`` view functions. It should display a
-page containing a form that POSTs back to the "save_url" argument supplied by
-the view. The form should have a "body" textarea field (the page data), and
-a submit button that has the name "form.submitted". The textarea in the form
-should be filled with any existing page data when it is rendered.
-
-Once we're done with the ``edit.pt`` template, it will look a lot like the
-below:
+Create ``tutorial/tutorial/templates/edit.pt`` and add the following
+content:
.. literalinclude:: src/views/tutorial/templates/edit.pt
+ :linenos:
:language: xml
+This template is used by ``add_page()`` and ``edit_page()`` for adding
+and editing a wiki page. It displays
+a page containing a form that includes:
+
+- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
+ with any existing page data when it is rendered (rows 46-47).
+- A submit button that has the name ``form.submitted`` (row 48).
+
+The form POSTs back to the "save_url" argument supplied
+by the view (row 45). The view will use the ``body`` and
+``form.submitted`` values.
+
+.. note:: Our templates use a ``request`` object that
+ none of our tutorial views return in their dictionary.
+ ``request`` is one of several
+ names that are available "by default" in a template when a template
+ renderer is used. See :ref:`chameleon_template_renderers` for
+ information about other names that are available by default
+ when a Chameleon template is used as a renderer.
+
Static Assets
-------------
@@ -302,24 +305,25 @@ Viewing the Application in a Browser
====================================
We can finally examine our application in a browser (See
-:ref:`wiki-start-the-application`). The views we'll try are as follows:
+:ref:`wiki-start-the-application`). Launch a browser and visit
+each of the following URLs, check that the result is as expected:
-- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki``
+- ``http://localhost:6543/`` invokes the ``view_wiki``
view. This always redirects to the ``view_page`` view of the ``FrontPage``
Page resource.
-- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes
+- ``http://localhost:6543/FrontPage/`` invokes
the ``view_page`` view of the front page resource. This is
- because it's the *default view* (a view without a ``name``) for Page
+ because it's the :term:`default view` (a view without a ``name``) for Page
resources.
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
+- ``http://localhost:6543/FrontPage/edit_page``
invokes the edit view for the ``FrontPage`` Page resource.
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
- browser invokes the add view for a Page.
+- ``http://localhost:6543/add_page/SomePageName``
+ invokes the add view for a Page.
- To generate an error, visit ``http://localhost:6543/add_page`` which
- will generate an ``IndexError`` for the expression
- ``request.subpath[0]``. You'll see an interactive traceback
+ will generate an ``IndexErrorr: tuple index out of range`` error.
+ You'll see an interactive traceback
facility provided by :term:`pyramid_debugtoolbar`.
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index 2b613377a..c94612fb1 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -36,9 +36,16 @@ be used as the wiki home page.
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 three views to handle the normal operations of adding,
+editing and viewing wiki pages, plus one view for the wiki front page.
+Two templates will be used, one for viewing, and one for both for adding
+and editing wiki pages.
+
+The default templating systems in :app:`Pyramid` are
+:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
+:term:`ZPT`, which is an XML-based templating language. Mako is a
+non-XML-based templating language. Because we had to pick one,
+we chose Chameleon for this tutorial.
Security
--------
@@ -52,11 +59,11 @@ use to do this are below.
- GROUPS, a dictionary mapping user names to a
list of groups they belong to.
-- *groupfinder*, an *authorization callback* that looks up
+- ``groupfinder``, an *authorization callback* that looks up
USERS and GROUPS. It will be provided in a new
*security.py* file.
-- An :term:`ACL` is attached to the root resource. Each
+- An :term:`ACL` is attached to the root :term:`resource`. Each
row below details an :term:`ACE`:
+----------+----------------+----------------+
@@ -70,6 +77,8 @@ use to do this are below.
- Permission declarations are added to the views to assert the security
policies as each request is handled.
+Two additional views and one template will handle the login and
+logout tasks.
Summary
-------
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py
index 0a31c38be..582ff0d7e 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models.py
@@ -1,8 +1,10 @@
from persistent import Persistent
from persistent.mapping import PersistentMapping
-from pyramid.security import Allow
-from pyramid.security import Everyone
+from pyramid.security import (
+ Allow,
+ Everyone,
+ )
class Wiki(PersistentMapping):
__name__ = None
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index fcbe6fe25..21f12b31d 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -9,9 +9,9 @@ from pyramid.view import (
)
from pyramid.security import (
- authenticated_userid,
remember,
forget,
+ authenticated_userid,
)
from .security import USERS
@@ -20,12 +20,13 @@ from .models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki', permission='view')
+@view_config(context='.models.Wiki',
+ permission='view')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page',
- renderer='templates/view.pt', permission='view')
+@view_config(context='.models.Page', renderer='templates/view.pt',
+ permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -43,10 +44,8 @@ def view_page(context, request):
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
- logged_in = authenticated_userid(request)
-
return dict(page = context, content = content, edit_url = edit_url,
- logged_in = logged_in)
+ logged_in = authenticated_userid(request))
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -65,9 +64,8 @@ def add_page(context, request):
page.__name__ = name
page.__parent__ = context
- logged_in = authenticated_userid(request)
-
- return dict(page = page, save_url = save_url, logged_in = logged_in)
+ return dict(page = page, save_url = save_url,
+ logged_in = authenticated_userid(request))
@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt',
@@ -77,11 +75,9 @@ def edit_page(context, request):
context.data = request.params['body']
return HTTPFound(location = request.resource_url(context))
- logged_in = authenticated_userid(request)
-
return dict(page = context,
save_url = request.resource_url(context, 'edit_page'),
- logged_in = logged_in)
+ logged_in = authenticated_userid(request))
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py
index 0a31c38be..582ff0d7e 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/models.py
@@ -1,8 +1,10 @@
from persistent import Persistent
from persistent.mapping import PersistentMapping
-from pyramid.security import Allow
-from pyramid.security import Everyone
+from pyramid.security import (
+ Allow,
+ Everyone,
+ )
class Wiki(PersistentMapping):
__name__ = None
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index fcbe6fe25..21f12b31d 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -9,9 +9,9 @@ from pyramid.view import (
)
from pyramid.security import (
- authenticated_userid,
remember,
forget,
+ authenticated_userid,
)
from .security import USERS
@@ -20,12 +20,13 @@ from .models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki', permission='view')
+@view_config(context='.models.Wiki',
+ permission='view')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page',
- renderer='templates/view.pt', permission='view')
+@view_config(context='.models.Page', renderer='templates/view.pt',
+ permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -43,10 +44,8 @@ def view_page(context, request):
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
- logged_in = authenticated_userid(request)
-
return dict(page = context, content = content, edit_url = edit_url,
- logged_in = logged_in)
+ logged_in = authenticated_userid(request))
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -65,9 +64,8 @@ def add_page(context, request):
page.__name__ = name
page.__parent__ = context
- logged_in = authenticated_userid(request)
-
- return dict(page = page, save_url = save_url, logged_in = logged_in)
+ return dict(page = page, save_url = save_url,
+ logged_in = authenticated_userid(request))
@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt',
@@ -77,11 +75,9 @@ def edit_page(context, request):
context.data = request.params['body']
return HTTPFound(location = request.resource_url(context))
- logged_in = authenticated_userid(request)
-
return dict(page = context,
save_url = request.resource_url(context, 'edit_page'),
- logged_in = logged_in)
+ logged_in = authenticated_userid(request))
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 8c1c50ff6..2ef55d15b 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -8,21 +8,31 @@ Adding Authorization
: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 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.
+to allow only people who are members of a *group* named ``group:editors``
+to add and edit wiki pages but we'll continue allowing
+anyone with access to the server to view pages.
-We will do the following steps:
+We will also add a login page and a logout link on all the
+pages. The login page will be shown when a user is denied
+access to any of the views that require a permission, instead of
+a default "403 Forbidden" page.
-* Add a :term:`root factory` with an :term:`ACL` (``models.py``).
+We will implement the access control with the following steps:
+
+* Add users and groups (``security.py``, a new module).
+* Add an :term:`ACL` (``models.py`` and
+ ``__init__.py``).
* Add an :term:`authentication policy` and an :term:`authorization policy`
(``__init__.py``).
-* Add an authentication policy callback (new ``security.py`` module).
-* Add ``login`` and ``logout`` views (``views.py``).
* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
views (``views.py``).
+
+Then we will add the login and logout feature:
+
+* Add routes for /login and /logout (``__init__.py``).
+* Add ``login`` and ``logout`` views (``views.py``).
+* Add a login template (``login.pt``).
* Make the existing views return a ``logged_in`` flag to the renderer (``views.py``).
-* Add a login template (new ``login.pt``).
* Add a "Logout" link to be shown when logged in and viewing or editing a page
(``view.pt``, ``edit.pt``).
@@ -30,39 +40,88 @@ The source code for this tutorial stage can be browsed at
`http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/authorization/
<http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/authorization/>`_.
-Adding A Root Factory
-~~~~~~~~~~~~~~~~~~~~~
+Access Control
+--------------
+
+Add users and groups
+~~~~~~~~~~~~~~~~~~~~
+
+Create a new ``tutorial/tutorial/security.py`` module with the
+following content:
+
+.. literalinclude:: src/authorization/tutorial/security.py
+ :linenos:
+ :language: python
+
+The ``groupfinder`` function accepts a userid and a request and
+returns one of these values:
+
+- If the userid exists in the system, it will return a
+ sequence of group identifiers (or an empty sequence if the user
+ isn't a member of any groups).
+- If the userid *does not* exist in the system, it will
+ return ``None``.
-Open ``models.py`` and add the following statements:
+For example, ``groupfinder('editor', request )`` returns ['group:editor'],
+``groupfinder('viewer', request)`` returns [], and ``groupfinder('admin', request)``
+returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy`
+"callback" that will provide the :term:`principal` or principals
+for a user.
+
+In a production system, user and group
+data will most often come from a database, but here we use "dummy"
+data to represent user and groups sources.
+
+Add an ACL
+~~~~~~~~~~
+
+Open ``tutorial/tutorial/models.py`` and add the following import
+statement at the head:
.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 1-4,35-39
+ :lines: 1-4
:linenos:
: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.
-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
-inside our ``models.py`` file.
-
-The ``RootFactory`` class we've just added will be used by :app:`Pyramid` to
-construct a ``context`` object. The context is attached to the request
-object passed to our view callables as the ``context`` attribute.
-
-The context object generated by our root factory will possess an ``__acl__``
-attribute that allows :data:`pyramid.security.Everyone` (a special principal)
-to view all pages, while allowing only a :term:`principal` named
-``group:editors`` to edit and add pages. The ``__acl__`` attribute attached
-to a context is interpreted specially by :app:`Pyramid` as an access control
-list during view callable execution. See :ref:`assigning_acls` for more
-information about what an :term:`ACL` represents.
+Add the following class definition:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 35-39
+ :linenos:
+ :language: python
+
+We import :data:`~pyramid.security.Allow`, an action that
+means that permission is allowed:, and
+:data:`~pyramid.security.Everyone`, a special :term:`principal`
+that is associated to all requests. Both are used in the
+:term:`ACE` entries that make up the ACL.
+
+The ACL is a list that needs to be named `__acl__` and be an
+attribute of a class. We define an :term:`ACL` with two
+:term:`ACE` entries: the first entry allows any user the `view`
+permission. The second entry allows the ``group:editors``
+principal the `edit` permission.
+
+The ``RootFactory`` class that contains the ACL is a :term:`root factory`.
+We need to associate it to our :app:`Pyramid` application, so the ACL is
+provided to each view in the :term:`context` of the request, as
+the ``context`` attribute.
+
+Open ``tutorial/tutorial/__init__.py`` and add a ``root_factory``
+parameter to our :term:`Configurator` constructor, that points to
+the class we created above:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 19-20
+ :linenos:
+ :emphasize-lines: 2
+ :language: python
+
+(Only the highlighted line needs to be added.)
+
+We are now providing the ACL to the application. See
+:ref:`assigning_acls` for more information about what an
+:term:`ACL` represents.
.. note::
@@ -71,23 +130,10 @@ information about what an :term:`ACL` represents.
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
-``root_factory`` argument to a :term:`Configurator`.
-
-Add an Authorization Policy and an Authentication Policy
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We're going to be making several changes to our ``__init__.py`` file which
-will help us configure an authorization policy.
+Add Authentication and Authorization Policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-For any :app:`Pyramid` application to perform authorization, we need to add a
-``security.py`` module (we'll do that shortly) and we'll need to change our
-``__init__.py`` file to add an :term:`authentication policy` and an
-:term:`authorization policy` which uses the ``security.py`` file for a
-*callback*.
-
-We'll enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy``
-to implement declarative security checking. Open ``tutorial/__init__.py`` and
+Open ``tutorial/__init__.py`` and
add these import statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
@@ -100,183 +146,173 @@ Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
:lines: 16-22
:linenos:
+ :emphasize-lines: 1-3,6-7
:language: python
+(Only the highlighted lines need to be added.)
+
+We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth
+ticket that may be included in the request, and an ``ACLAuthorizationPolicy``
+that uses an ACL to determine the allow or deny outcome for a view.
+
Note that the
:class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor
accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string
representing an encryption key used by the "authentication ticket" machinery
-represented by this policy: it is required. The ``callback`` is a
-``groupfinder`` function in the current directory's ``security.py`` file. We
-haven't added that module yet, but we're about to.
+represented by this policy: it is required. The ``callback`` is the
+``groupfinder()`` function that we created before.
-Viewing Your Changes
---------------------
+Add permission declarations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When we're done configuring a root factory, adding a authentication and
-authorization policies, and adding routes for ``/login`` and ``/logout``,
-your application's ``__init__.py`` will look like this:
+Add a ``permission='edit'`` parameter to the ``@view_config``
+decorator for ``add_page()`` and ``edit_page()``, for example:
-.. literalinclude:: src/authorization/tutorial/__init__.py
+.. code-block:: python
:linenos:
- :emphasize-lines: 2-3,7,16-18,20-22,25-26
- :language: python
+ :emphasize-lines: 2
-Adding an authentication policy callback
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ @view_config(route_name='add_page', renderer='templates/edit.pt',
+ permission='edit')
-Add a ``tutorial/security.py`` module within your package (in the same
-directory as :file:`__init__.py`, :file:`views.py`, etc.) with the
-following content:
+(Only the highlighted line needs to be added.)
-.. literalinclude:: src/authorization/tutorial/security.py
+The result is that only users who possess the ``edit``
+permission at the time of the request may invoke those two views.
+
+Add a ``permission='view'`` parameter to the ``@view_config``
+decorator for ``view_wiki()`` and ``view_page()``, like this:
+
+.. code-block:: python
+ :linenos:
+ :emphasize-lines: 2
+
+ @view_config(route_name='view_page', renderer='templates/view.pt',
+ permission='view')
+
+(Only the highlighted line needs to be added.)
+
+This allows anyone to invoke these two views.
+
+We are done with the changes needed to control access. The
+changes that follow will add the login and logout feature.
+
+Login, Logout
+-------------
+
+Add routes for /login and /logout
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Go back to ``tutorial/tutorial/__init__.py`` and add these two
+routes:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 25-26
:linenos:
:language: python
-The ``groupfinder`` function defined here is an :term:`authentication policy`
-"callback"; it is a callable that accepts a userid and a request. If
-the userid exists in the system, the callback will return a sequence
-of group identifiers (or an empty sequence if the user isn't a member
-of any groups). If the userid *does not* exist in the system, the
-callback will return ``None``. In a production system, user and group
-data will most often come from a database, but here we use "dummy"
-data to represent user and groups sources. Note that the ``editor``
-user is a member of the ``group:editors`` group in our dummy group
-data (the ``GROUPS`` data structure).
-
-We've given the ``editor`` user membership to the ``group:editors`` by
-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 :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
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Add Login and Logout Views
+~~~~~~~~~~~~~~~~~~~~~~~~~~
-To our ``views.py`` we'll add a ``login`` view callable which renders a login
-form and processes the post from the login form, checking credentials.
+We'll add a ``login`` view which renders a login form and processes
+the post from the login form, checking credentials.
We'll also add a ``logout`` view callable to our application and
provide a link to it. This view will clear the credentials of the
logged in user and redirect back to the front page.
-The ``login`` view callable will look something like this:
+Add the following import statements to the
+head of ``tutorial/tutorial/views.py``:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 89-115
+ :lines: 9-16,18,24-25
:linenos:
+ :emphasize-lines: 3,6-9,11
:language: python
-The ``logout`` view callable will look something like this:
+(Only the highlighted lines need to be added.)
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 117-121
- :linenos:
- :language: python
+:meth:`~pyramid.view.forbidden_view_config` will be used
+to customize the default 403 Forbidden page.
+:meth:`~pyramid.security.remember` and
+:meth:`~pyramid.security.forget` help to create and
+expire an auth ticket cookie.
-The ``login`` view callable is decorated with two decorators, a
-``@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 ``/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
-``pyramid.security`` module, and a value from our newly added
-``tutorial.security`` package. Add the following import statements to the
-head of the ``views.py`` file:
+Now add the ``login`` and ``logout`` views:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 9-18,24-25
+ :lines: 91-123
:linenos:
:language: python
-Changing Existing Views
-~~~~~~~~~~~~~~~~~~~~~~~
+``login()`` is decorated with two decorators:
-Add permision declarations
---------------------------
+- a ``@view_config`` decorator which associates it with the
+ ``login`` route and makes it visible when we visit ``/login``,
+- a ``@forbidden_view_config`` decorator which turns it into
+ an :term:`forbidden view`. ``login()`` will be invoked
+ when a users tries to execute a view callable that
+ they are not allowed to. For example, if a user has not logged in
+ and tries to add or edit a Wiki page, he will be shown the
+ login form before being allowed to continue on.
-Then we need to change each of our ``view_page``, ``edit_page`` and
-``add_page`` view callables in ``views.py``. Within each of these views,
-we'll need to pass a "logged in" parameter to its template. We'll add
-something like this to each view body:
+The order of these two :term:`view configuration` decorators
+is unimportant.
-.. code-block:: python
- :linenos:
+``logout()`` is decorated with a ``@view_config`` decorator
+which associates it with the ``logout`` route. It will be
+invoked when we visit ``/logout``.
- from pyramid.security import authenticated_userid
- logged_in = authenticated_userid(request)
+Add the ``login.pt`` Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Return a logged_in flag to the renderer
----------------------------------------
+Create ``tutorial/tutorial/templates/login.pt`` with the following
+content:
-We'll then change the return value of these views to pass the resulting
-``logged_in`` value to the template, e.g.:
+.. literalinclude:: src/authorization/tutorial/templates/login.pt
+ :language: xml
-.. code-block:: python
- :linenos:
+The above template is referred to within the login view we just
+added to ``views.py``.
- return dict(page = page,
- content = content,
- logged_in = logged_in,
- edit_url = edit_url)
+Return a logged_in flag to the renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll also need to add a ``permission`` value to the ``@view_config``
-decorator for each of the ``add_page`` and ``edit_page`` view callables. For
-each, we'll add ``permission='edit'``, for example:
+Add the following line to the import at the head of
+``tutorial/tutorial/views.py``:
-.. code-block:: python
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 14-18
:linenos:
+ :emphasize-lines: 4
+ :language: python
- @view_config(route_name='edit_page', renderer='templates/edit.pt',
- permission='edit')
+(Only the highlighted line needs to be added.)
-See the ``permission='edit'`` we added there? This indicates that the view
-callables which these views reference cannot be invoked without the
-authenticated user possessing the ``edit`` permission with respect to the
-current :term:`context`.
+Add a ``logged_in`` parameter to the return value of
+``view_page()``, ``edit_page()`` and ``add_page()``,
+like this:
-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.
+.. code-block:: python
+ :linenos:
+ :emphasize-lines: 4
-Adding the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ return dict(page = page,
+ content = content,
+ edit_url = edit_url,
+ logged_in = authenticated_userid(request))
-Add a ``login.pt`` template to your templates directory. It's
-referred to within the login view we just added to ``views.py``.
+(Only the highlighted line needs to be added.)
-.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: xml
+:meth:`~pyramid.security.authenticated_userid()` will return None
+if the user is not authenticated, or some user id it the user
+is authenticated.
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
-display a "Logout" link if someone is logged in. This link will
-invoke the logout view.
-
-To do so we'll add this to both templates within the ``<div id="right"
-class="app-welcome align-right">`` div:
+Open ``tutorial/tutorial/templates/edit.pt`` and
+``tutorial/tutorial/templates/view.pt`` and add this within the
+``<div id="right" class="app-welcome align-right">`` div:
.. code-block:: xml
@@ -284,36 +320,66 @@ class="app-welcome align-right">`` div:
<a href="${request.application_url}/logout">Logout</a>
</span>
-Seeing Our Changes To ``views.py`` and our Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The attribute ``tal:condition="logged_in"`` will make the element be
+included when ``logged_in`` is any user id. The link will invoke
+the logout view. The above element will not be included if ``logged_in``
+is ``None``, such as when a user is not authenticated.
+
+Seeing Our Changes
+------------------
+
+Our ``tutorial/tutorial/__init__.py`` will look something like this
+when we're done:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 2-3,7,16-18,20-22,25-26
+ :language: python
+
+(Only the highlighted lines need to be added.)
-Our ``views.py`` module will look something like this when we're done:
+Our ``tutorial/tutorial/models.py`` will look something like this
+when we're done:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :linenos:
+ :emphasize-lines: 1-4,35-39
+ :language: python
+
+(Only the highlighted lines need to be added.)
+
+Our ``tutorial/tutorial/views.py`` will look something like this
+when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
- :emphasize-lines: 11,14-18,56,59,71,74,89-115,117-121
+ :emphasize-lines: 11,14-18,31,37,58,61,73,76,88,91-117,119-123
:language: python
(Only the highlighted lines need to be added.)
-Our ``edit.pt`` template will look something like this when we're done:
+Our ``tutorial/tutorial/templates/edit.pt`` template will look
+something like this when we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+ :linenos:
:emphasize-lines: 41-43
:language: xml
(Only the highlighted lines need to be added.)
-Our ``view.pt`` template will look something like this when we're done:
+Our ``tutorial/tutorial/templates/view.pt`` template will look
+something like this when we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
+ :linenos:
:emphasize-lines: 41-43
:language: xml
(Only the highlighted lines need to be added.)
Viewing the Application in a Browser
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------------
We can finally examine our application in a browser (See
:ref:`wiki2-start-the-application`). Launch a browser and visit
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index ac58e1e46..efb72230e 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -226,52 +226,63 @@ of the wiki page.
Adding Templates
================
-The views we've added all reference a :term:`template`. Each template is a
-:term:`Chameleon` :term:`ZPT` template. These templates will live in the
-``templates`` directory of our tutorial package.
+The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
+reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT`
+template. These templates will live in the ``templates`` directory of our
+tutorial package. Chameleon templates must have a ``.pt`` extension to be
+recognized as such.
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).
-
-Once we're done with the ``view.pt`` template, it will look a lot like the
-below:
+Create ``tutorial/tutorial/templates/view.pt`` and add the following
+content:
.. literalinclude:: src/views/tutorial/templates/view.pt
+ :linenos:
:language: xml
-.. note:: The names available for our use in a template are always
- those that are present in the dictionary returned by the view
- callable. But our templates make use of a ``request`` object that
- none of our tutorial views return in their dictionary. This value
- appears as if "by magic". However, ``request`` is one of several
- names that are available "by default" in a template when a template
- renderer is used. See :ref:`chameleon_template_renderers` for more
- information about other names that are available by default in a
- template when a Chameleon template is used as a renderer.
+This template is used by ``view_page()`` for displaying a single
+wiki page. It includes:
+
+- A ``div`` element that is replaced with the ``content``
+ value provided by the view (rows 45-47). ``content``
+ contains HTML, so the ``structure`` keyword is used
+ to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+- A link that points
+ at the "edit" URL which invokes the ``edit_page`` view for
+ the page being viewed (rows 49-51).
The ``edit.pt`` Template
------------------------
-The ``edit.pt`` template is used for adding and editing a wiki page. It is
-used by the ``add_page`` and ``edit_page`` view functions. It should display
-a page containing a form that POSTs back to the "save_url" argument supplied
-by the view. The form should have a "body" textarea field (the page data),
-and a submit button that has the name "form.submitted". The textarea in the
-form should be filled with any existing page data when it is rendered.
-
-Once we're done with the ``edit.pt`` template, it will look a lot like
-the following:
+Create ``tutorial/tutorial/templates/edit.pt`` and add the following
+content:
.. literalinclude:: src/views/tutorial/templates/edit.pt
+ :linenos:
:language: xml
+This template is used by ``add_page()`` and ``edit_page()`` for adding
+and editing a wiki page. It displays
+a page containing a form that includes:
+
+- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
+ with any existing page data when it is rendered (rows 46-47).
+- A submit button that has the name ``form.submitted`` (row 48).
+
+The form POSTs back to the "save_url" argument supplied
+by the view (row 45). The view will use the ``body`` and
+``form.submitted`` values.
+
+.. note:: Our templates use a ``request`` object that
+ none of our tutorial views return in their dictionary.
+ ``request`` is one of several
+ names that are available "by default" in a template when a template
+ renderer is used. See :ref:`chameleon_template_renderers` for
+ information about other names that are available by default
+ when a Chameleon template is used as a renderer.
+
Static Assets
-------------
@@ -339,25 +350,24 @@ Viewing the Application in a Browser
====================================
We can finally examine our application in a browser (See
-:ref:`wiki2-start-the-application`). The views we'll try are
-as follows:
+:ref:`wiki2-start-the-application`). Launch a browser and visit
+each of the following URLs, check that the result is as expected:
-- Visiting ``http://localhost:6543`` in a browser invokes the
+- ``http://localhost:6543`` in a browser invokes the
``view_wiki`` view. This always redirects to the ``view_page`` view
of the FrontPage page object.
-- Visiting ``http://localhost:6543/FrontPage`` in a browser invokes
+- ``http://localhost:6543/FrontPage`` in a browser invokes
the ``view_page`` view of the front page page object.
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
+- ``http://localhost:6543/FrontPage/edit_page`` in a browser
invokes the edit view for the front page object.
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
+- ``http://localhost:6543/add_page/SomePageName`` in a
browser invokes the add view for a page.
-Try generating an error within the body of a view by adding code to
-the top of it that generates an exception (e.g. ``raise
-Exception('Forced Exception')``). Then visit the error-raising view
-in a browser. You should see an interactive exception handler in the
-browser which allows you to examine values in a post-mortem mode.
+- To generate an error, visit ``http://localhost:6543/add_page`` which
+ will generate a ``NoResultFound: No row was found for one()`` error.
+ You'll see an interactive traceback facility provided
+ by :term:`pyramid_debugtoolbar`.
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 1ff000549..2e6fc0e77 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -36,9 +36,16 @@ page.
Views
-----
-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.
+There will be three views to handle the normal operations of adding,
+editing and viewing wiki pages, plus one view for the wiki front page.
+Two templates will be used, one for viewing, and one for both for adding
+and editing wiki pages.
+
+The default templating systems in :app:`Pyramid` are
+:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
+:term:`ZPT`, which is an XML-based templating language. Mako is a
+non-XML-based templating language. Because we had to pick one,
+we chose Chameleon for this tutorial.
Security
--------
@@ -67,6 +74,8 @@ use to do this are below.
- Permission declarations are added to the views to assert the security
policies as each request is handled.
+Two additional views and one template will handle the login and
+logout tasks.
Summary
-------
@@ -87,7 +96,7 @@ listed in the following table:
| | | | | |
| | | | | |
+----------------------+-----------------------+-------------+------------+------------+
-| /edit_page/PageName | Display edit form | edit_page | edit.pt | edit |
+| /PageName/edit_page | Display edit form | edit_page | edit.pt | edit |
| | with existing | | | |
| | content. | | | |
| | | | | |
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index 1453cd2e6..c7670b049 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -27,12 +27,14 @@ from .security import USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(route_name='view_wiki')
+@view_config(route_name='view_wiki',
+ permission='view')
def view_wiki(request):
return HTTPFound(location = request.route_url('view_page',
pagename='FrontPage'))
-@view_config(route_name='view_page', renderer='templates/view.pt')
+@view_config(route_name='view_page', renderer='templates/view.pt',
+ permission='view')
def view_page(request):
pagename = request.matchdict['pagename']
page = DBSession.query(Page).filter_by(name=pagename).first()
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
index 465d98ae1..f2a33af1e 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -27,12 +27,14 @@ from .security import USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(route_name='view_wiki')
+@view_config(route_name='view_wiki',
+ permission='view')
def view_wiki(request):
return HTTPFound(location = request.route_url('view_page',
pagename='FrontPage'))
-@view_config(route_name='view_page', renderer='templates/view.pt')
+@view_config(route_name='view_page', renderer='templates/view.pt',
+ permission='view')
def view_page(request):
pagename = request.matchdict['pagename']
session = DBSession()