Commits

Blaise Laflamme committed 3a186ab

Initial import

Comments (0)

Files changed (86)

+syntax:glob
+.svn
+.coverage
+*.orig
+*.pyd
+*.pyc
+*.egg-info
+ez_setup
+*~
+build/
+dist/
+docs/html/docs
+output/ProjectName
+.DS_Store
+*.kpf
+*.pdf
+

authorization.rst

+.. _wiki2_adding_authorization:
+
+====================
+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 only people whom 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.
+:mod:`repoze.bfg` provides facilities for *authorization* and
+*authentication*.  We'll make use of both features to provide security
+to our application.
+
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org
+<http://docs.repoze.org/bfgwiki2-1.3/authorization>`_.
+
+Adding A Root Factory
+---------------------
+
+We're going to start to use a custom :term:`root factory` within our
+``run.py`` file.  The objects generated by the root factory will be
+used as the :term:`context` of each request to our application.  In
+order for :mod:`repoze.bfg` declarative security to work properly, the
+context object generated during a request must be decorated with
+security declarations; when we begin to use a custom root factory to
+generate our contexts, we can begin to make use of the declarative
+security features of :mod:`repoze.bfg`.
+
+Let's modify our ``run.py``, passing in a :term:`root factory` to our
+:term:`Configurator` constructor.  We'll point it at a new class we
+create inside our ``models.py`` file.  Add the following statements to
+your ``models.py`` file:
+
+.. code-block:: python
+
+   from repoze.bfg.security import Allow
+   from repoze.bfg.security import Everyone
+
+   class RootFactory(object):
+       __acl__ = [ (Allow, Everyone, 'view'), 
+                   (Allow, 'group:editors', 'edit') ]
+       def __init__(self, request):
+           self.__dict__.update(request.matchdict)
+
+The ``RootFactory`` class we've just added will be used by
+:mod:`repoze.bfg` to construct a ``context`` object.  The context is
+attached to the request object passed to our view callables as the
+``context`` attribute.
+
+All of our context objects will possess an ``__acl__`` attribute that
+allows :data:`repoze.bfg.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 :mod:`repoze.bfg` as
+an access control list during view callable execution.  See
+:ref:`assigning_acls` for more information about what an :term:`ACL`
+represents.
+
+.. note: Although we don't use the functionality here, the ``factory``
+   used to create route contexts may differ per-route as opposed to
+   globally.  See the ``factory`` attribute in
+   :ref:`route_zcml_directive` for more info.
+
+We'll pass the ``RootFactory`` we created in the step above in as the
+``root_factory`` argument to a :term:`Configurator`.  When we're done,
+your application's ``run.py`` will look like this.
+
+.. literalinclude:: src/authorization/tutorial/run.py
+   :linenos:
+   :language: python
+
+Configuring a ``repoze.bfg`` Authorization Policy
+-------------------------------------------------
+
+For any :mod:`repoze.bfg` application to perform authorization, we
+need to add a ``security.py`` module and we'll need to change our
+``configure.zcml`` file to add an :term:`authentication policy` and an
+:term:`authorization policy`.
+
+Changing ``configure.zcml``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We'll change our ``configure.zcml`` file to enable an
+``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to
+enable declarative security checking.  We'll also change
+``configure.zcml`` to add a view stanza which points at our ``login``
+:term:`view callable`, also known as a :term:`forbidden view`.  This
+configures our newly created login view to show up when
+:mod:`repoze.bfg` detects that a view invocation can not be
+authorized.  Also, we'll add ``view_permission`` attributes with the
+value ``edit`` to the ``edit_page`` and ``add_page`` route
+declarations.  This indicates that the view callables which these
+routes reference cannot be invoked without the authenticated user
+possessing the ``edit`` permission with respect to the current
+context.
+
+This makes the assertion that only users who possess the effective
+``edit`` permission at the time of the request may invoke those two
+views.  We've granted the ``group:editors`` principal the ``edit``
+permission at the root model via its ACL, so only the a user whom is a
+member of the group named ``group:editors`` will able to invoke the
+views associated with the ``add_page`` or ``edit_page`` routes.  
+
+When you're done, your ``configure.zcml`` will look like so
+
+.. literalinclude:: src/authorization/tutorial/configure.zcml
+   :linenos:
+   :language: xml
+
+Note that the ``authtktauthenticationpolicy`` tag has two attributes:
+``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
+string, representing a :term:`dotted Python name`, which points at the
+``groupfinder`` function in the current directory's ``security.py``
+file.  We haven't added that module yet, but we're about to.
+
+Adding ``security.py``
+~~~~~~~~~~~~~~~~~~~~~~
+
+Add a ``security.py`` module within your package (in the same
+directory as "run.py", "views.py", etc) with the following content:
+
+.. literalinclude:: src/authorization/tutorial/security.py
+   :linenos:
+   :language: python
+
+The groupfinder 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 root returned by the root factory,
+and the permission associated with the ``add_page`` and ``edit_page``
+views, the ``editor`` user should be able to add and edit pages.
+
+Adding Login and Logout Views
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We'll add a ``login`` view callable 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.
+
+We'll add a different file (for presentation convenience) to add login
+and logout view callables.  Add a file named ``login.py`` to your
+application (in the same directory as ``views.py``) with the following
+content:
+
+.. literalinclude:: src/authorization/tutorial/login.py
+   :linenos:
+   :language: python
+
+Changing Existing Views
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Then we need to change each of our ``view_page``, ``edit_page`` and
+``add_page`` views in ``views.py`` to pass a "logged in" parameter to
+its template.  We'll add something like this to each view body:
+
+.. ignore-next-block
+.. code-block:: python
+   :linenos:
+
+   from repoze.bfg.security import authenticated_userid
+   logged_in = authenticated_userid(request)
+
+We'll then change the return value of these views to pass the
+`resulting `logged_in`` value to the template, e.g.:
+
+.. ignore-next-block
+.. code-block:: python
+   :linenos:
+
+   return dict(page = context,
+               content = content,
+               logged_in = logged_in,
+               edit_url = edit_url)
+
+Adding 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 ``login.py``.
+
+.. literalinclude:: src/authorization/tutorial/templates/login.pt
+   :linenos:
+   :language: xml
+
+Change ``view.pt`` and ``edit.pt``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+class="main_content">`` div:
+
+.. code-block:: xml
+   :linenos:
+
+   <span tal:condition="logged_in">
+      <a href="${request.application_url}/logout">Logout</a>
+   </span>
+
+Viewing the Application in a Browser
+------------------------------------
+
+We can finally examine our application in a browser.  The views we'll
+try are as follows:
+
+- Visiting ``http://localhost:6543/`` in a browser invokes the
+  ``view_wiki`` view.  This always redirects to the ``view_page`` view
+  of the FrontPage page object.  It is executable by any user.
+
+- Visiting ``http://localhost:6543/FrontPage`` in a browser invokes
+  the ``view_page`` view of the FrontPage page object.
+
+- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
+  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
+  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.
+
+Seeing Our Changes To ``views.py`` and our Templates
+----------------------------------------------------
+
+Our ``views.py`` module will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/views.py
+   :linenos:
+   :language: python
+
+Our ``edit.pt`` template will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+   :linenos:
+   :language: xml
+
+Our ``view.pt`` template will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/templates/view.pt
+   :linenos:
+   :language: xml
+
+Revisiting the Application
+---------------------------
+
+When we revisit the application in a browser, and log 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.
+
+
+
+==========
+Background
+==========
+
+This tutorial presents a :mod:`repoze.bfg` application that uses
+technologies which will be familiar to someone with :term:`Pylons`
+experience.  It uses :term:`SQLAlchemy` as a persistence mechanism and
+:term:`url dispatch` to map URLs to code.  It can also be followed by
+people without any prior Python web framework experience.
+
+To code along with this tutorial, the developer will need a UNIX
+machine with development tools (Mac OS X with XCode, any Linux or BSD
+variant, etc) *or* he will need a Windows system of any kind.
+
+This tutorial is targeted at :mod:`repoze.bfg` version 1.2.
+
+Have fun!
+============
+Basic Layout
+============
+
+The starter files generated by the ``bfg_routesalchemy`` template are
+basic, but they provide a good orientation for the high-level patterns
+common to most :term:`url dispatch` -based :mod:`repoze.bfg` projects.
+
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.3/basiclayout>`_.
+
+``__init__.py``
+---------------
+
+A directory on disk can be turned into a Python :term:`package` by
+containing an ``__init__.py`` file.  Even if empty, this marks a
+directory as a Python package.
+
+Configuration With ``configure.zcml``
+--------------------------------------
+
+:mod:`repoze.bfg` uses a configuration markup language syntactically
+the same as Zope's implementation of :term:`ZCML`, but using a
+different default XML namespace.  Our sample ZCML file looks like the
+following:
+
+   .. literalinclude:: src/basiclayout/tutorial/configure.zcml
+      :linenos:
+      :language: xml
+
+#. *Line 1*.  The root ``<configure>`` element, using the
+   ``http://namespaces.repoze.org/bfg`` namespace.
+
+#. *Line 4*. Boilerplate, the comment explains.
+
+#. *Lines 6-11*.  Register a ``<route>`` :term:`route configuration`
+   that will be used when the URL is ``/``.  Since this ``<route>``
+   has an empty ``path`` attribute, it is the "default" route. The
+   attribute named ``view`` with the value ``.views.my_view`` is the
+   dotted name to a *function* we write (generated by the
+   ``bfg_routesalchemy`` template) that is given a ``request`` object
+   and which returns a response or a dictionary.  You will use mostly
+   ``<route>`` statements in a :term:`URL dispatch` based application
+   to map URLs to code.  This ``route`` also names a
+   ``view_renderer``, which is a template which lives in the
+   ``templates`` subdirectory of the package.  When the
+   ``.views.my_view`` view returns a dictionary, a :term:`renderer`
+   will use this template to create a response.
+
+#. *Lines 13-16*.  Register a ``<static>`` directive that will match
+   any URL that starts with ``/static/``.  This will serve up static
+   resources for us, in this case, at
+   ``http://localhost:6543/static/`` and below.  With this
+   declaration, we're saying that any URL that starts with ``/static``
+   should go to the static view; any remainder of its path (e.g. the
+   ``/foo`` in ``/static/foo``) will be used to compose a path to a
+   static file resource, such as a CSS file.
+
+Content Models with ``models.py``
+---------------------------------
+
+In a SQLAlchemy-based application, a *model* object is an object
+composed by querying the SQL database which backs an application.
+SQLAlchemy is an "object relational mapper" (an ORM).  The
+``models.py`` file is where the ``bfg_routesalchemy`` Paster template
+put the classes that implement our models.
+
+Here is the source for ``models.py``:
+
+   .. literalinclude:: src/basiclayout/tutorial/models.py
+      :linenos:
+      :language: py
+
+#. *Lines 1-16*.  Imports to support later code.
+
+#. *Line 18*.  We set up a SQLAlchemy "DBSession" object here.  We
+   specify that we'd like to use the "ZopeTransactionExtension".  This
+   extension is an extension which allows us to use a *transaction
+   manager* instead of controlling commits and aborts to database
+   operations by hand.
+
+#. *Line 21*. Set up a SQLAlchemy metadata object.
+
+#. *Lines 23-25*.  A model class named ``Model``.  It has an
+   ``__init__`` that takes a single argument (``name``).  It stores a
+   single attribute named ``name``.
+
+#. *Lines 27-32*.  A SQLAlchemy ``Table`` declaration named
+   ``models_table`` which we'll use later to map onto our ``Model``
+   class.
+
+#. *Line 34*.  We map our ``models_table`` table to our Models class
+   here.  This makes an association between the ``Model`` class and
+   the ``models`` table in the database, as far as SQLAlchemy is
+   concerned.
+
+#. *Lines 36-41*.  A function named ``populate`` which adds a single
+   model instance into our SQL storage and commits a transaction.
+
+#. *Lines 43-51*.  A function named ``initialize_sql`` which sets up
+   an actual SQL database and binds it to our SQLAlchemy DBSession
+   object.  It also calls the ``populate`` function, to do initial
+   database population.
+
+App Startup with ``run.py``
+---------------------------
+
+When you run the application using the ``paster`` command using the
+``tutorial.ini`` generated config file, the application configuration
+points at an Setuptools *entry point* described as
+``egg:tutorial#app``.  In our application, because the application's
+``setup.py`` file says so, this entry point happens to be the ``app``
+function within the file named ``run.py``:
+
+   .. literalinclude:: src/basiclayout/tutorial/run.py
+      :linenos:
+      :language: py
+
+#. *Lines 1-3*. Imports to support later code.
+
+#. *Lines 11-14*. Get the database configuration string from the
+   ``tutorial.ini`` file's ``[app:sql]`` section.  This will be a URI
+   (something like ``sqlite://``).
+
+#. Line *15*. We initialize our SQL database using SQLAlchemy, passing
+   it the db string.
+
+#. *Line 16*.  We construct a :term:`Configurator`.  ``settings`` is
+   passed as a keyword argument with the dictionary values passed by
+   PasteDeploy as the ``settings`` argument.  This will be a
+   dictionary of settings parsed by PasteDeploy, which contains
+   deployment-related values such as ``reload_templates``,
+   ``db_string``, etc.
+
+#. *Lines 17-20*.  We then load a ZCML file to do application
+   configuration, and use the
+   :meth:`repoze.bfg.configuration.Configurator.make_wsgi_app` method
+   to return a :term:`WSGI` application.
+

definingmodels.rst

+===============
+Defining Models
+===============
+
+The first change we'll make to our stock paster-generated application
+will be to define a :term:`model` constructor representing a wiki
+page.  We'll do this inside our ``models.py`` file.
+
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.3/models>`_.
+
+Making Edits to ``models.py``
+-----------------------------
+
+.. note::
+
+  There is nothing automagically special about the filename
+  ``models.py``.  A project may have many models throughout its
+  codebase in arbitrarily-named files.  Files implementing models
+  often have ``model`` in their filenames (or they may live in a
+  Python subpackage of your application package named ``models``) ,
+  but this is only by convention.
+
+The first thing we want to do is remove the stock ``Model`` class from
+the generated ``models.py`` file.  The ``Model`` class is only a
+sample and we're not going to use it.
+
+Then, we'll add a ``Page`` class.  Because this is a SQLAlchemy
+application, this class should inherit from an instance of
+:class:`sqlalchemy.ext.declarative.declarative_base`.  Declarative
+SQLAlchemy models are easier to use than directly-mapped ones.  The
+code generated by our ``routesalchemy`` paster template does not use
+declarative SQLAlchemy syntax, so we'll need to change various things
+to begin to use declarative syntax.
+
+Our ``Page`` class will have a class level attribute ``__tablename__``
+which equals the string ``pages``.  This means that SQLAlchemy will
+store our wiki data in a SQL table named ``pages``.  Our Page class
+will also have class-level attributes named ``id``, ``pagename`` and
+``data`` (all instances of :class:`sqlalchemy.Column`).  These will
+map to columns in the ``pages`` table.  The ``id`` attribute will be
+the primary key in the table.  The ``name`` attribute will be a text
+attribute, each value of which needs to be unique within the column.
+The ``data`` attribute is a text attribute that will hold the body of
+each page.
+
+We'll also remove our ``populate`` function.  We'll inline the
+populate step into ``initialize_sql``, changing our ``initialize_sql``
+function to add a FrontPage object to our database at startup time.
+We're also going to use slightly different binding syntax.  It will
+will otherwise largely be the same as the ``initialize_sql`` in the
+paster-generated ``models.py``.
+
+Our DBSession assignment stays the same as the original generated
+``models.py``.
+
+Looking at the Result of Our Edits to ``models.py``
+---------------------------------------------------
+
+The result of all of our edits to ``models.py`` will end up looking
+something like this:
+
+.. literalinclude:: src/models/tutorial/models.py
+   :linenos:
+   :language: python
+
+Viewing the Application in a Browser
+------------------------------------
+
+We can't.  At this point, our system is in a "non-runnable" state;
+we'll need to change view-related files in the next chapter to be able
+to start the application successfully.  If you try to start the
+application, you'll wind up with a Python traceback on your console
+that ends with this exception:
+
+.. code-block:: text
+
+   ImportError: cannot import name Model

definingviews.rst

+==============
+Defining Views
+==============
+
+A :term:`view callable` in a :term:`url dispatch` -based
+:mod:`repoze.bfg` application is typically a simple Python function
+that accepts a single parameter named :term:`request`.  A view
+callable is assumed to return a :term:`response` object.
+
+.. note:: A :mod:`repoze.bfg` view can also be defined as callable
+   which accepts *two* arguments: a :term:`context` and a
+   :term:`request`.  You'll see this two-argument pattern used in
+   other :mod:`repoze.bfg` tutorials and applications.  Either calling
+   convention will work in any :mod:`repoze.bfg` application; the
+   calling conventions can be used interchangeably as necessary.  In
+   :term:`url dispatch` based applications, however, the context
+   object is rarely used in the view body itself, so within this
+   tutorial we define views as callables that accept only a request to
+   avoid the visual "noise".  If you do need the ``context`` within a
+   view function that only takes the request as a single argument, you
+   can obtain it via ``request.context``.
+
+The request passed to every view that is called as the result of a
+route match has an attribute named ``matchdict`` that contains the
+elements placed into the URL by the ``path`` of a ``route`` statement.
+For instance, if a route statement in ``configure.zcml`` had the path
+``:one/:two``, and the URL at ``http://example.com/foo/bar`` was
+invoked, matching this path, the matchdict dictionary attached to the
+request passed to the view would have a ``one`` key with the value
+``foo`` and a ``two`` key with the value ``bar``.
+
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.3/views>`_.
+
+Declaring Dependencies in Our ``setup.py`` File
+===============================================
+
+The view code in our application will depend on a package which is not
+a dependency of the original "tutorial" application.  The original
+"tutorial" application was generated by the ``paster create`` command;
+it doesn't know about our custom application requirements.  We need to
+add a dependency on the ``docutils`` package to our ``tutorial``
+package's ``setup.py`` file by assigning this dependency to the
+``install_requires`` parameter in the ``setup`` function.
+
+Our resulting ``setup.py`` should look like so:
+
+.. literalinclude:: src/views/setup.py
+   :linenos:
+   :language: python
+
+.. note:: After these new dependencies are added, you will need to
+   rerun ``python setup.py develop`` inside the root of the
+   ``tutorial`` package to obtain and register the newly added
+   dependency package.
+
+Adding View Functions
+=====================
+
+We'll get rid of our ``my_view`` view function in our ``views.py``
+file.  It's only an example and isn't relevant to our application.
+
+Then we're going to add four :term:`view callable` functions to our
+``views.py`` module.  One view callable (named ``view_wiki``) will
+display the wiki itself (it will answer on the root URL), another
+named ``view_page`` will display an individual page, another named
+``add_page`` will allow a page to be added, and a final view callable
+named ``edit_page`` will allow a page to be edited.  We'll describe
+each one briefly and show the resulting ``views.py`` file afterward.
+
+.. note::
+
+  There is nothing special about the filename ``views.py``.  A project
+  may have many view callables throughout its codebase in
+  arbitrarily-named files.  Files implementing view callables often
+  have ``view`` in their filenames (or may live in a Python subpackage
+  of your application package named ``views``), but this is only by
+  convention.
+
+The ``view_wiki`` view function
+-------------------------------
+
+The ``view_wiki`` function will respond as the :term:`default view` of
+a ``Wiki`` model object.  It always redirects to a URL which
+represents the path to our "FrontPage".  It returns an instance of the
+:class:`webob.exc.HTTPFound` class (instances of which implement the
+WebOb :term:`response` interface), It will use the
+:func:`repoze.bfg.url.route_url` API to construct a URL to the
+``FrontPage`` page (e.g. ``http://localhost:6543/FrontPage``), and
+will use it as the "location" of the HTTPFound response, forming an
+HTTP redirect.
+
+The ``view_page`` view function
+-------------------------------
+
+The ``view_page`` function will respond as the :term:`default view` of
+a ``Page`` object.  The ``view_page`` function renders the
+:term:`ReStructuredText` body of a page (stored as the ``data``
+attribute of a Page object) as HTML.  Then it substitutes an HTML
+anchor for each *WikiWord* reference in the rendered HTML using a
+compiled regular expression.
+
+The curried function named ``check`` is used as the first argument to
+``wikiwords.sub``, indicating that it should be called to provide a
+value for each WikiWord match found in the content.  If the wiki
+already contains a page with the matched WikiWord name, the ``check``
+function generates a view link to be used as the substitution value
+and returns it.  If the wiki does not already contain a page with with
+the matched WikiWord name, the function generates an "add" link as the
+substitution value and returns it.
+
+As a result, the ``content`` variable is now a fully formed bit of
+HTML containing various view and add links for WikiWords based on the
+content of our current page object.
+
+We then generate an edit URL (because it's easier to do here than in
+the template), and we return a dictionary with a number of arguments.
+The fact that this view returns a dictionary (as opposed to a
+:term:`response` object) is a cue to :mod:`repoze.bfg` that it should
+try to use a :term:`renderer` associated with the view configuration
+to render a template.  In our case, the template which will be
+rendered will be the ``templates/view.pt`` template, as per the
+configuration put into effect in ``configure.zcml``.
+
+The ``add_page`` view function
+------------------------------
+
+The ``add_page`` function will be invoked when a user clicks on a
+*WikiWord* which isn't yet represented as a page in the system.  The
+``check`` function within the ``view_page`` view generates URLs to
+this view.  It also acts as a handler for the form that is generated
+when we want to add a page object.  The ``matchdict`` attribute of the
+request passed to the ``add_page`` view will have the values we need
+to construct URLs and find model objects.
+
+The matchdict will have a ``pagename`` key that matches the name of
+the page we'd like to add.  If our add view is invoked via,
+e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename``
+value in the matchdict will be ``SomeName``.
+
+If the view execution is *not* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``False``), the
+view callable renders a template.  To do so, it generates a "save url"
+which the template use as the form post URL during rendering.  We're
+lazy here, so we're trying to use the same template
+(``templates/edit.pt``) for the add view as well as the page edit
+view, so we create a dummy Page object in order to satisfy the edit
+form's desire to have *some* page object exposed as ``page``, and
+:mod:`repoze.bfg` will render the template associated with this view
+to a response.
+
+If the view execution *is* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``True``), we
+scrape the page body from the form data, create a Page object using
+the name in the matchdict ``pagename``, and obtain the page body from
+the request, and save it into the database using ``session.add``.  We
+then redirect back to the ``view_page`` view (the :term:`default view`
+for a Page) for the newly created page.
+
+The ``edit_page`` view function
+-------------------------------
+
+The ``edit_page`` function will be invoked when a user clicks the
+"Edit this Page" button on the view form.  It renders an edit form but
+it also acts as the handler for the form it renders.  The
+``matchdict`` attribute of the request passed to the ``add_page`` view
+will have a ``pagename`` key matching the name of the page the user
+wants to edit.
+
+If the view execution is *not* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``False``), the
+view simply renders the edit form, passing the request, the page
+object, and a save_url which will be used as the action of the
+generated form.
+
+If the view execution *is* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``True``), the
+view grabs the ``body`` element of the request parameter and sets it
+as the ``data`` key in the matchdict.  It then redirects to the
+default view of the wiki page, which will always be the ``view_page``
+view.
+
+Viewing the Result of Our Edits to ``views.py``
+===============================================
+
+The result of all of our edits to ``views.py`` will leave it looking
+like this:
+
+.. literalinclude:: src/views/tutorial/views.py
+   :linenos:
+   :language: python
+
+Adding Templates
+================
+
+The views we've added all reference a :term:`template`.  Each template
+is a :term:`Chameleon` template.  The default templating system in
+:mod:`repoze.bfg` is a variant of :term:`ZPT` provided by
+:term:`Chameleon`.  These templates will live in the ``templates``
+directory of our tutorial package.
+
+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:
+
+.. 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.
+
+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 below:
+
+.. literalinclude:: src/views/tutorial/templates/edit.pt
+   :linenos:
+   :language: xml
+
+Static Resources
+----------------
+
+Our templates name a single static resource named ``style.css``.  We
+need to create this and place it in a file named ``style.css`` within
+our package's ``templates/static`` directory.  This file is a little
+too long to replicate within the body of this guide, however it is
+available `online
+<http://docs.repoze.org/bfgwiki2-1.2/views/tutorial/templates/static/style.css>`_.
+
+This CSS file will be accessed via
+e.g. ``http://localhost:6543/static/style.css`` by virtue of the
+``<static>`` directive we've defined in the ``configure.zcml`` file.
+Any number and type of static resources can be placed in this
+directory (or subdirectories) and are just referred to by URL within
+templates.
+
+Mapping Views to URLs in ``configure.zcml``
+===========================================
+
+The ``configure.zcml`` file contains ``route`` declarations (and a
+lone ``view`` declaration) which serve to map URLs via :term:`url
+dispatch` to view functions.  First, we’ll get rid of the existing
+``route`` created by the template using the name ``home``. It’s only
+an example and isn’t relevant to our application. 
+
+We then need to add four ``route`` declarations to ``configure.zcml``.
+Note that the *ordering* of these declarations is very important.
+``route`` declarations are matched in the order they're found in the
+``configure.zcml`` file.
+
+#. Add a declaration which maps the empty path (signifying the root
+   URL) to the view named ``view_wiki`` in our ``views.py`` file with
+   the name ``view_wiki``.  This is the :term:`default view` for the
+   wiki.
+
+#. Add a declaration which maps the path pattern ``:pagename`` to the
+   view named ``view_page`` in our ``views.py`` file with the view
+   name ``view_page``.  This is the regular view for a page.
+
+#. Add a declaration which maps the path pattern
+   ``:pagename/edit_page`` to the view named ``edit_page`` in our
+   ``views.py`` file with the name ``edit_page``.  This is the edit view
+   for a page.
+
+#. Add a declaration which maps the path pattern
+   ``add_page/:pagename`` to the view named ``add_page`` in our
+   ``views.py`` file with the name ``add_page``.  This is the add view
+   for a new page.
+
+As a result of our edits, the ``configure.zcml`` file should look
+something like so:
+
+.. literalinclude:: src/views/tutorial/configure.zcml
+   :linenos:
+   :language: xml
+
+The WSGI Pipeline
+-----------------
+
+Within ``tutorial.ini``, note the existence of a ``[pipeline:main]``
+section which specifies our WSGI pipeline.  This "pipeline" will be
+served up as our WSGI application.  As far as the WSGI server is
+concerned the pipeline *is* our application.  Simpler configurations
+don't use a pipeline: instead they expose a single WSGI application as
+"main".  Our setup is more complicated, so we use a pipeline.
+
+``egg:repoze.tm2#tm`` is at the "top" of the pipeline.  This is a
+piece of middleware which commits a transaction if no exception
+occurs; if an exception occurs, the transaction will be aborted.  This
+is the piece of software that allows us to forget about needing to do
+manual commits and aborts of our database connection in view code.
+
+Adding an Element to the Pipeline
+---------------------------------
+
+Let's add a piece of middleware to the WSGI pipeline.  We'll add
+``egg:Paste#evalerror`` middleware which displays debuggable errors in
+the browser while you're developing (this is *not* recommended for
+deployment as it is a security risk).  Let's insert evalerror into the
+pipeline right above ``egg:repoze.tm2#tm``, making our resulting
+``tutorial.ini`` file look like so:
+
+.. literalinclude:: src/views/tutorial.ini
+   :linenos:
+   :language: ini
+
+Viewing the Application in a Browser
+====================================
+
+Once we've set up the WSGI pipeline properly, we can finally examine
+our application in a browser.  The views we'll try are as follows:
+
+- Visiting ``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
+  the ``view_page`` view of the front page page object.
+
+- Visiting ``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
+  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.
+
+Adding Tests
+============
+
+Since we've added a good bit of imperative code here, it's useful to
+define tests for the views we've created.  We'll change our tests.py
+module to look like this:
+
+.. literalinclude:: src/views/tutorial/tests.py
+   :linenos:
+   :language: python
+
+We can then run the tests using something like:
+
+.. code-block:: text
+   :linenos:
+
+    $ python setup.py test -q
+
+The expected output is something like:
+
+.. code-block:: text
+   :linenos:
+
+   running test
+   running egg_info
+   writing requirements to tutorial.egg-info/requires.txt
+   writing tutorial.egg-info/PKG-INFO
+   writing top-level names to tutorial.egg-info/top_level.txt
+   writing dependency_links to tutorial.egg-info/dependency_links.txt
+   writing entry points to tutorial.egg-info/entry_points.txt
+   unrecognized .svn/entries format in 
+   reading manifest file 'tutorial.egg-info/SOURCES.txt'
+   writing manifest file 'tutorial.egg-info/SOURCES.txt'
+   running build_ext
+   ......
+   ----------------------------------------------------------------------
+   Ran 6 tests in 0.181s
+
+   OK
+
+
+
+   
+=============================
+Distributing Your Application
+=============================
+
+Once your application works properly, you can create a "tarball" from
+it by using the ``setup.py sdist`` command.  The following commands
+assume your current working directory is the ``tutorial`` package
+we've created and that the parent directory of the ``tutorial``
+package is a virtualenv representing a :mod:`repoze.bfg` environment.
+
+On UNIX:
+
+.. code-block:: text
+
+   $ ../bin/python setup.py sdist
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bigfntut> ..\Scripts\python setup.py sdist
+
+.. warning:: If your project files are not checked in to a version
+   control repository (such as Subversion), the dist tarball will
+   *not* contain all the files it needs to.  In particular, it will
+   not contain non-Python-source files (such as templates and static
+   files).  To ensure that these are included, check your files into a
+   version control repository before running ``setup.py sdist``.
+
+The output of such a command will be something like:
+
+.. code-block:: text
+
+   running sdist
+   # ... more output ...
+   creating dist
+   tar -cf dist/tutorial-0.1.tar tutorial-0.1
+   gzip -f9 dist/tutorial-0.1.tar
+   removing 'tutorial-0.1' (and everything under it)
+
+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
+to show them your cool new application.  They should be able to
+install it by pointing the ``easy_install`` command directly at it.
+Or you can upload it to `PyPI <http://pypi.python.org>`_ and share it
+with the rest of the world, where it can be downloaded via
+``easy_install`` remotely like any other package people download from
+PyPI.
+
+.. _bfg_sql_wiki_tutorial:
+
+SQLAlchemy + URL Dispatch Wiki Tutorial
+=======================================
+
+This tutorial introduces a :term:`SQLAlchemy` and :term:`url dispatch`
+-based :mod:`repoze.bfg` application to a developer familiar with
+Python.  When the tutorial is finished, the developer will have
+created a basic Wiki application with authentication.
+
+For cut and paste purposes, the source code for all stages of this
+tutorial can be browsed at `docs.repoze.org
+<http://docs.repoze.org/bfgwiki2-1.3>`_.
+
+.. toctree::
+   :maxdepth: 2
+
+   background
+   installation
+   basiclayout
+   definingmodels
+   definingviews
+   authorization
+   distributing
+
+
+============
+Installation
+============
+
+For the most part, the installation process for this tutorial
+duplicates the steps described in :ref:`installing_chapter` and
+:ref:`project_narr`, however it also explains how to install
+additional libraries for tutorial purposes.
+
+Preparation
+===========
+
+Please take the following steps to prepare for the tutorial.  The
+steps are slightly different depending on whether you're using UNIX or
+Windows.
+
+Preparation, UNIX
+-----------------
+
+#. Install SQLite3 and its development packages if you don't already
+   have them installed.  Usually this is via your system's package
+   manager.  For example, on a Debian Linux system, do ``sudo apt-get
+   install libsqlite3-dev``.
+
+#. If you don't already have a Python 2.5 interpreter installed on
+   your system, obtain, install, or find `Python 2.5
+   <http://python.org/download/releases/2.5.4/>`_ for your system.
+
+#. Install the latest `setuptools` into the Python you
+   obtained/installed/found in the step above: download `ez_setup.py
+   <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using
+   the ``python`` interpreter of your Python 2.5 installation:
+
+   .. code-block:: text
+
+      $ /path/to/my/Python-2.5/bin/python ez_setup.py
+
+#. Use that Python's `bin/easy_install` to install `virtualenv`:
+
+   .. code-block:: text
+
+      $ /path/to/my/Python-2.5/bin/easy_install virtualenv
+
+#. Use that Python's virtualenv to make a workspace:
+
+   .. code-block:: text
+
+      $ path/to/my/Python-25/bin/virtualenv --no-site-packages bigfntut
+
+#. Switch to the ``bigfntut`` directory:
+
+   .. code-block:: text
+
+      $ cd bigfntut
+
+#. (Optional) Consider using ``source bin/activate`` to make your
+   shell environment wired to use the virtualenv.
+
+#. Use ``easy_install`` and point to the BFG "current" index to get
+   :mod:`repoze.bfg` and its direct dependencies installed:
+
+   .. code-block:: text
+
+      $ bin/easy_install -i http://dist.repoze.org/bfg/current/simple \
+              repoze.bfg
+
+#. Use ``easy_install`` to install various packages from PyPI.
+
+   .. code-block:: text
+
+      $ bin/easy_install docutils nose coverage zope.sqlalchemy SQLAlchemy \
+                repoze.tm2
+
+Preparation, Windows
+--------------------
+
+#. Install, or find `Python 2.5
+   <http://python.org/download/releases/2.5.4/>`_ for your system.
+
+#. Install the latest `setuptools` into the Python you
+   obtained/installed/found in the step above: download `ez_setup.py
+   <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using
+   the ``python`` interpreter of your Python 2.5 installation using a
+   command prompt:
+
+   .. code-block:: text
+
+      c:\> c:\Python25\python ez_setup.py
+
+#. Use that Python's `bin/easy_install` to install `virtualenv`:
+
+   .. code-block:: text
+
+      c:\> c:\Python25\Scripts\easy_install virtualenv
+
+#. Use that Python's virtualenv to make a workspace:
+
+   .. code-block:: text
+
+      c:\> c:\Python25\Scripts\virtualenv --no-site-packages bigfntut
+
+#. Switch to the ``bigfntut`` directory:
+
+   .. code-block:: text
+
+      c:\> cd bigfntut
+
+#. (Optional) Consider using ``bin\activate.bat`` to make your shell
+   environment wired to use the virtualenv.
+
+#. Use ``easy_install`` and point to the BFG "current" index to get
+   :mod:`repoze.bfg` and its direct dependencies installed:
+
+   .. code-block:: text
+
+      c:\bigfntut> Scripts\easy_install -i \
+                http://dist.repoze.org/bfg/current/simple repoze.bfg
+
+#. Use ``easy_install`` to install various packages from PyPI.
+
+   .. code-block:: text
+
+      c:\bigfntut> Scripts\easy_install -i \
+               http://dist.repoze.org/bfg/current/simple docutils \
+               nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2
+
+
+.. _sql_making_a_project:
+
+Making a Project
+================
+
+Your next step is to create a project.  :mod:`repoze.bfg` supplies a
+variety of templates to generate sample projects.  We will use the
+``bfg_routesalchemy`` template, which generates an application that
+uses :term:`SQLAlchemy` and :term:`URL dispatch`.
+
+The below instructions assume your current working directory is the
+"virtualenv" named "bigfntut".
+
+On UNIX:
+
+.. code-block:: text
+
+   $ bin/paster create -t bfg_routesalchemy tutorial
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bigfntut> Scripts\paster create -t bfg_routesalchemy tutorial
+
+.. note:: If you are using Windows, the ``bfg_routesalchemy`` Paster
+   template may not deal gracefully with installation into a location
+   that contains spaces in the path.  If you experience startup
+   problems, try putting both the virtualenv and the project into
+   directories that do not contain spaces in their paths.
+
+Installing the Project in "Development Mode"
+============================================
+
+In order to do development on the project easily, you must "register"
+the project as a development egg in your workspace using the
+``setup.py develop`` command.  In order to do so, cd to the "tutorial"
+directory you created in :ref:`sql_making_a_project`, and run the
+"setup.py develop" command using virtualenv Python interpreter.
+
+On UNIX:
+
+.. code-block:: text
+
+   $ cd tutorial
+   $ ../bin/python setup.py develop
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bigfntut> cd tutorial
+   c:\bigfntut\tutorial> ..\Scripts\python setup.py develop
+
+.. _sql_running_tests:
+
+Running the Tests
+=================
+
+After you've installed the project in development mode, you may run
+the tests for the project.
+
+On UNIX:
+
+.. code-block:: text
+
+   $ ../bin/python setup.py test -q
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q
+
+Starting the Application
+========================
+
+Start the application.
+
+On UNIX:
+
+.. code-block:: text
+
+   $ ../bin/paster serve tutorial.ini --reload
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bifgfntut\tutorial> ..\Scripts\paster serve tutorial.ini --reload
+
+Exposing Test Coverage Information
+==================================
+
+You can run the ``nosetests`` command to see test coverage
+information.  This runs the tests in the same way that ``setup.py
+test`` does but provides additional "coverage" information, exposing
+which lines of your project are "covered" (or not covered) by the
+tests.
+
+To get this functionality working, we'll need to install a couple of
+other packages into our ``virtualenv``: ``nose`` and ``coverage``:
+
+On UNIX:
+
+.. code-block:: text
+
+   $ ../bin/easy_install nose coverage
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bfgfntut\tutorial> ..\Scripts\easy_install nose coverage
+
+Once ``nose`` and ``coverage`` are installed, we can actually run the
+coverage tests.
+
+On UNIX:
+
+.. code-block:: text
+
+   $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage
+
+On Windows:
+
+.. code-block:: text
+
+   c:\bigfntut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
+         --cover-erase --with-coverage
+
+Looks like our package's ``models`` module doesn't quite have 100%
+test coverage.
+
+Visit the Application in a Browser
+==================================
+
+In a browser, visit ``http://localhost:6543/``.  You will see the
+generated application's default page.
+
+Decisions the ``bfg_routesalchemy`` Template Has Made For You
+=============================================================
+
+Creating a project using the ``bfg_routesalchemy`` template makes the
+assumption that you are willing to use :term:`SQLAlchemy` as a
+database access tool and :term:`url dispatch` to map URLs to code.
+:mod:`repoze.bfg` supports any persistent storage mechanism
+(e.g. object database or filesystem files, etc).  It also supports an
+additional mechanism to map URLs to code (:term:`traversal`).
+However, for the purposes of this tutorial, we'll only be using url
+dispatch and SQLAlchemy.
+

src/authorization/CHANGES.txt

+0.0
+---
+
+-  Initial version

src/authorization/README.txt

+tutorial README
+
+
+

src/authorization/development.ini

+[app:wiki]
+use = egg:tutorial#main
+reload_templates = true
+mako.directories = tutorial:templates
+debug_authorization = false
+debug_notfound = false
+debug_templates = true
+default_locale_name = en
+db_string = sqlite:///%(here)s/tutorial.db
+
+[pipeline:main]
+pipeline =
+    egg:Paste#evalerror
+    egg:repoze.tm2#tm
+    wiki
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000

src/authorization/setup.cfg

+[nosetests]
+match=^test
+nocapture=1
+cover-package=tutorial
+with-coverage=1
+cover-erase=1
+
+[compile_catalog]
+directory = tutorial/locale
+domain = tutorial
+statistics = true
+
+[extract_messages]
+add_comments = TRANSLATORS:
+output_file = tutorial/locale/tutorial.pot
+width = 80
+
+[init_catalog]
+domain = tutorial
+input_file = tutorial/locale/tutorial.pot
+output_dir = tutorial/locale
+
+[update_catalog]
+domain = tutorial
+input_file = tutorial/locale/tutorial.pot
+output_dir = tutorial/locale
+previous = true

src/authorization/setup.py

+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+README = open(os.path.join(here, 'README.txt')).read()
+CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+
+requires = [
+    'Pylons',
+    'SQLAlchemy',
+    'transaction',
+    'repoze.tm2',
+    'zope.sqlalchemy'
+]
+
+if sys.version_info[:3] < (2,5,0):
+    requires.append('pysqlite')
+    
+setup(name='tutorial',
+      version='0.0',
+      description='tutorial',
+      long_description=README + '\n\n' +  CHANGES,
+      classifiers=[
+        "Programming Language :: Python",
+        "Framework :: Pylons",
+        "Topic :: Internet :: WWW/HTTP",
+        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+        ],
+      author='',
+      author_email='',
+      url='',
+      keywords='web pylons',
+      packages=find_packages(),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=requires,
+      tests_require=requires,
+      test_suite="tutorial",
+      entry_points = """\
+      [paste.app_factory]
+      main = tutorial:main
+      """
+      )
+
Add a comment to this file

src/authorization/tutorial.db

Binary file added.

src/authorization/tutorial/__init__.py

+def main(global_config, **settings):
+    """ This function returns a Pylons WSGI application.
+    """
+    from pylons.configuration import Configurator
+    from tutorial.models import initialize_sql
+    from repoze.bfg.authentication import AuthTktAuthenticationPolicy
+    from repoze.bfg.authorization import ACLAuthorizationPolicy
+    from tutorial.security import groupfinder
+    from tutorial.views import MyController
+    db_string = settings.get('db_string')
+    if db_string is None:
+        raise ValueError("No 'db_string' value in application "
+                         "configuration.")
+    initialize_sql(db_string)
+    config = Configurator(
+        settings=settings,
+        authentication_policy=AuthTktAuthenticationPolicy('sosecret', callback=groupfinder),
+        authorization_policy=ACLAuthorizationPolicy()
+    )
+    config.begin()
+    config.add_static_view('static', 'tutorial:templates/static')
+    config.add_handler('tutorial.views:MyController', pattern='/login',
+                       route_name='login', action='login', factory=MyController)
+    config.add_handler('tutorial.views:MyController', pattern='/logout',
+                       route_name='logout', action='logout', factory=MyController)
+    config.add_handler('tutorial.views:MyController', pattern='/',
+                       route_name='view_wiki', action='view_wiki', factory=MyController)
+    config.add_handler('tutorial.views:MyController', pattern='/{pagename}',
+                       route_name='view_page', action='view_page', factory=MyController)
+    config.add_handler('tutorial.views:MyController', pattern='/add_page/{pagename}',
+                       route_name='add_page', action='add_page', factory=MyController)
+    config.add_handler('tutorial.views:MyController', pattern='/{pagename}/edit_page',
+                       route_name='edit_page', action='edit_page', factory=MyController)
+    config.end()
+    return config.make_wsgi_app()
+

src/authorization/tutorial/models.py

+import transaction
+
+from sqlalchemy import create_engine
+from sqlalchemy import Column
+from sqlalchemy import Integer
+from sqlalchemy import Text
+
+from sqlalchemy.exc import IntegrityError
+
+from sqlalchemy.orm import scoped_session
+from sqlalchemy.orm import sessionmaker
+
+from sqlalchemy.ext.declarative import declarative_base
+
+from zope.sqlalchemy import ZopeTransactionExtension
+
+DBSession = scoped_session(
+    sessionmaker(extension=ZopeTransactionExtension()))
+Base = declarative_base()
+
+class Page(Base):
+    """ The SQLAlchemy declarative model class for a Page object. """
+    __tablename__ = 'pages'
+    id = Column(Integer, primary_key=True)
+    name = Column(Text, unique=True)
+    data = Column(Text)
+
+    def __init__(self, name, data):
+       self.name = name
+       self.data = data
+
+def initialize_sql(db, echo=False):
+    engine = create_engine(db, echo=echo)
+    DBSession.configure(bind=engine)
+    Base.metadata.bind = engine
+    Base.metadata.create_all(engine)
+    try:
+        session = DBSession()
+        page = Page('FrontPage', 'initial data')
+        session.add(page)
+        transaction.commit()
+    except IntegrityError:
+        # already created
+        pass

src/authorization/tutorial/security.py

+USERS = {'editor':'editor',
+          'viewer':'viewer'}
+GROUPS = {'editor':['group:editors']}
+
+def groupfinder(userid, request):
+    if userid in USERS:
+        return GROUPS.get(userid, [])
+

src/authorization/tutorial/templates/edit.mak

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <title>pylons tutorial wiki Editing: ${page.name}</title>
+        <link rel="stylesheet" type="text/css"
+          href="${request.application_url}/static/style.css" />
+</head>
+
+<body>
+
+<div class="main_content">
+  <div style="float:right; width: 10em;"> Viewing
+    <span>${page.name or 'Page Name Goes Here'}</span> <br/>
+    You can return to the <a href="${request.application_url}">FrontPage</a>.
+	% if logged_in:
+      <a href="${request.application_url}/logout">Logout</a>
+	% endif
+  </div>  
+
+  <div>
+    <form action="${save_url}" method="post">
+      <textarea name="body" rows="10" cols="60">${page.data}</textarea>
+      <input type="submit" name="form.submitted" value="Save"/>
+    </form>
+  </div>
+</div>  
+</body>
+</html>

src/authorization/tutorial/templates/login.mak

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+  <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+  <title>pylons tutorial wiki</title>
+  <link rel="stylesheet" type="text/css"
+        href="${request.application_url}/static/style.css" />
+</head>
+
+<body>
+
+<h1>Log In</h1>
+
+<div>${message}</div>
+
+<div class="main_content">
+  <form action="${url}" method="post">
+    <input type="hidden" name="came_from" value="${came_from}"/>
+    <input type="text" name="login" value="${login}"/>
+    <br/>
+    <input type="password" name="password" value="${password}"/>
+    <br/>
+    <input type="submit" name="form.submitted" value="Log In"/>
+  </form>
+</div>  
+
+</body>
+</html>

src/authorization/tutorial/templates/mytemplate.mak

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:tal="http://xml.zope.org/namespaces/tal">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>${project} Application</title>
+<meta name="keywords" content="python web application" />
+<meta name="description" content="Pylons web application" />
+<link href="${request.application_url}/static/default.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+<!-- start header -->
+<div id="logo">
+  <h2><code>${project}</code>, a <code>Pylons</code> application</h2>
+</div>
+<div id="header">
+  <div id="menu">
+  </div>
+</div>
+<!-- end header -->
+<div id="wrapper">
+  <!-- start page -->
+  <div id="page">
+    <!-- start content -->
+    <div id="content">
+      <div class="post">
+	<h1 class="title">Welcome to <code>${project}</code>, an
+	application generated by the <a
+	href="http://pylonshq.com">Pylons</a> web
+	application framework.</h1>
+      </div>
+    </div>
+    <!-- end content -->
+    <!-- start sidebar -->
+    <div id="sidebar"></div>
+    <!-- end sidebar -->
+    <div style="clear: both;">&nbsp;</div>
+  </div>
+</div>
+<!-- end page -->
+<!-- start footer -->
+<div id="footer">
+  <p id="legal">( c ) 2008. All Rights Reserved. Template design
+  by <a href="http://www.freecsstemplates.org/">Free CSS
+  Templates</a>.</p>
+</div>
+<!-- end footer -->
+</body>
+</html>

src/authorization/tutorial/templates/static/default.css

+/*
+Design by Free CSS Templates
+http://www.freecsstemplates.org
+Released for free under a Creative Commons Attribution 2.5 License
+*/
+
+body {
+	margin: 0;
+	padding: 0;
+	background: url(images/img01.gif) repeat-x left top;
+	font-size: 13px;
+	font-family: "Trebuchet MS", Georgia, "Times New Roman", Times, serif;
+	text-align: justify;
+	color: #FFFFFF;
+}
+
+h1, h2, h3 {
+	margin: 0;
+	text-transform: lowercase;
+	font-weight: normal;
+	color: #FFFFFF;
+}
+
+h1 {
+	letter-spacing: -1px;
+	font-size: 32px;
+}
+
+h2 {
+	font-size: 23px;
+}
+
+p, ul, ol {
+	margin: 0 0 2em 0;
+	text-align: justify;
+	line-height: 26px;
+}
+
+a:link {
+	color: #8BD80E;
+}
+
+a:hover, a:active {
+	text-decoration: none;
+	color: #8BD80E;
+}
+
+a:visited {
+	color: #8BD80E;
+}
+
+img {
+	border: none;
+}
+
+img.left {
+	float: left;
+	margin-right: 15px;
+}
+
+img.right {
+	float: right;
+	margin-left: 15px;
+}
+
+/* Form */
+
+form {
+	margin: 0;
+	padding: 0;
+}
+
+fieldset {
+	margin: 0;
+	padding: 0;
+	border: none;
+}
+
+legend {
+	display: none;
+}
+
+input, textarea, select {
+	font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+	font-size: 13px;
+	color: #333333;
+}
+
+#wrapper {
+	margin: 0;
+	padding: 0;
+	background: #000000;
+}
+
+/* Header */
+
+#header {
+	width: 713px;
+	margin: 0 auto;
+	height: 42px;
+}
+
+/* Menu */
+
+#menu {
+	float: left;
+	width: 713px;
+	height: 50px;
+	background: url(images/img02.gif) no-repeat left top;
+}
+
+#menu ul {
+	margin: 0;
+	padding: 0px 0 0 10px;
+	list-style: none;
+	line-height: normal;
+}
+
+#menu li {
+	display: block;
+	float: left;
+}
+
+#menu a {
+	display: block;
+	float: left;
+	background: url(images/img04.gif) no-repeat right 55%;
+	margin-top: 5px;
+	margin-right: 3px;
+	padding: 8px 17px;
+	text-decoration: none;
+	font-size: 13px;
+	color: #000000;
+}
+
+#menu a:hover { 
+	color: #000000;
+}
+
+#menu .current_page_item a {
+	color: #000000;
+}
+
+/** LOGO */
+
+#logo {
+	width: 713px;
+	height: 80px;
+	margin: 0 auto;
+}
+
+#logo h1, #logo h2 {
+	float: left;
+	margin: 0;
+	padding: 30px 0 0 0px;
+	line-height: normal;
+}
+
+#logo h1 { 
+	font-family: Georgia, "Times New Roman", Times, serif;
+	font-size:40px;
+}
+
+#logo h1 a {
+	text-decoration: none;
+	color: #4C4C4C; 
+}
+
+#logo h1 a:hover { text-decoration: underline; }
+
+#logo h2 {
+	float: left;
+	padding: 45px 0 0 18px;
+	font: 18px Georgia, "Times New Roman", Times, serif;
+	color: #8BD80E; 
+}
+
+#logo p a {
+	text-decoration: none;
+	color: #8BD80E;
+}
+
+#logo p a:hover { text-decoration: underline; }
+
+
+
+/* Page */
+
+#page {
+	width: 663px;
+	margin: 0 auto;
+	background: #4C4C4C url(images/img03.gif) no-repeat left bottom;
+	padding: 0 25px;
+}
+
+/* Content */
+
+#content {
+	float: left;
+	width: 410px;
+	
+}
+
+/* Post */
+
+.post {
+	padding: 15px 0px;
+	margin-bottom: 20px;
+}
+
+.post .title {
+	margin-bottom: 20px;
+	padding-bottom: 5px;
+}
+
+.post h1 {
+	padding: 0px 0 0 0px;
+	background: url(images/img08.jpg) no-repeat left top;
+	font-size: 24px;
+	color: #FFFFFF;
+}
+
+.post h2 {
+	padding: 0px 0 0 0px;
+	font-size: 22px;
+	color: #FFFFFF;
+}
+
+.post .entry {
+}
+
+.post .meta {
+	padding: 15px 15px 30px 0px;
+	font-family: Arial, Helvetica, sans-serif;
+	font-size: 11px;
+}
+
+.post .meta p {
+	margin: 0;
+	padding-top: 15px;
+	line-height: normal;
+	color: #FFFFFF;
+}
+
+.post .meta .byline {
+	float: left;
+}
+
+.post .meta .links {
+	float: right;
+}
+
+.post .meta .more {
+	padding: 0 10px 0 18px;
+}
+
+.post .meta .comments {
+}
+
+.post .meta b {
+	display: none;
+}
+
+
+/* Sidebar */
+
+#sidebar {
+	width: 210px;
+	float: right;
+	margin: 0;
+	padding: 0;
+}
+
+#sidebar ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+#sidebar li {
+	margin-bottom: 40px;
+}
+
+#sidebar li ul {
+}
+
+#sidebar li li {
+	margin: 0;
+}
+
+#sidebar h2 {
+	width: 250px;
+	padding: 8px 0 0 0px;
+	margin-bottom: 10px;
+	background: url(images/img07.jpg) no-repeat left top;
+	font-size: 20px;
+	color: #FFFFFF;
+}
+
+/* Search */
+
+#search {
+
+}
+
+#search h2 {
+	margin-bottom: 20px;
+}
+
+#s {
+	width: 140px;
+	margin-right: 5px;
+	padding: 3px;
+	border: 1px solid #BED99C;
+}
+
+#x {
+	padding: 3px;
+	border: none;
+	background: #8BD80E;
+	text-transform: lowercase;
+	font-size: 11px;
+	color: #FFFFFF;
+}
+
+/* Boxes */
+
+.box1 {
+	padding: 20px;
+}
+
+.box2 {
+	color: #BABABA;
+}
+
+.box2 h2 {
+	margin-bottom: 15px;
+	font-size: 16px;
+	color: #FFFFFF;
+}
+
+.box2 ul {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+.box2 a:link, .box2 a:hover, .box2 a:active, .box2 a:visited  {
+	color: #EDEDED;
+}
+
+/* Footer */
+#footer-wrap {
+}
+
+#footer {
+	margin: 0 auto;
+	padding: 20px 0 10px 0;
+	background: #000000;
+}
+
+html>body #footer {
+	height: auto;
+}
+
+#footer p {
+	font-size: 11px;
+}
+
+#legal {
+	clear: both;
+	padding-top: 17px;
+	text-align: center;
+	color: #FFFFFF;
+}
+
+#legal a {
+	font-weight: normal;
+	color: #FFFFFF;
+}
Add a comment to this file

src/authorization/tutorial/templates/static/images/img01.gif

Added
New image
Add a comment to this file

src/authorization/tutorial/templates/static/images/img02.gif

Added
New image
Add a comment to this file

src/authorization/tutorial/templates/static/images/img03.gif

Added
New image
Add a comment to this file

src/authorization/tutorial/templates/static/images/img04.gif

Added
New image
Add a comment to this file

src/authorization/tutorial/templates/static/images/spacer.gif

Added
New image

src/authorization/tutorial/templates/static/style.css

+html, body {
+  color: black;
+  background-color: #ddd;
+  font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif;
+  margin: 0;
+  padding: 0;
+}
+
+td, th {padding:3px;border:none;}
+tr th {text-align:left;background-color:#f0f0f0;color:#333;}
+tr.odd td {background-color:#edf3fe;}
+tr.even td {background-color:#fff;}