diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-06-01 00:15:14 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-06-01 00:15:14 +0000 |
| commit | 6117c04a79d29b710391d95c5cce63358c5490fe (patch) | |
| tree | 86ada0eee517b2a2ae53c51604d444e1183b30d0 /docs/tutorials | |
| parent | c2dbab3e024be02c4246746f3d7d9dc5da9219d8 (diff) | |
| download | pyramid-6117c04a79d29b710391d95c5cce63358c5490fe.tar.gz pyramid-6117c04a79d29b710391d95c5cce63358c5490fe.tar.bz2 pyramid-6117c04a79d29b710391d95c5cce63358c5490fe.zip | |
Keep useful stuff.
Diffstat (limited to 'docs/tutorials')
| -rw-r--r-- | docs/tutorials/bfgwiki2/authorization.rst | 270 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/basiclayout.rst | 129 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/definingmodels.rst | 147 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/definingviews.rst | 364 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/distributing.rst | 96 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/index.rst | 15 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/installation.rst | 3 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/viewdecorators.rst | 257 |
8 files changed, 90 insertions, 1191 deletions
diff --git a/docs/tutorials/bfgwiki2/authorization.rst b/docs/tutorials/bfgwiki2/authorization.rst deleted file mode 100644 index 437cb9c74..000000000 --- a/docs/tutorials/bfgwiki2/authorization.rst +++ /dev/null @@ -1,270 +0,0 @@ -==================== -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 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. - -Configuring a ``repoze.bfg`` Authentication Policy --------------------------------------------------- - -For any :mod:`repoze.bfg` application to perform authorization, we -need to change our ``run.py`` module to add an :term:`authentication -policy`. Adding an authentication policy actually causes the system -to begin to use :term:`authorization`. - -Changing ``run.py`` -~~~~~~~~~~~~~~~~~~~ - -Change your ``run.py`` module to import the -``AuthTktAuthenticationPolicy`` from ``repoze.bfg.authentication``. -Within the body of the ``make_app`` function, construct an instance of -the policy, and pass it as the ``authentication_policy`` argument to -the ``make_app`` function. The first positional argument of an -``AuthTktAuthenticationPolicy`` is a secret used to encrypt cookie -data. Its second argument ("callback") should be a callable that -accepts a userid. If the userid exists in the system, the callback -should 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 should return ``None``. We'll use -"dummy" data to represent user and groups sources. When we're done, -your application's ``run.py`` will look like this. - -.. literalinclude:: src/authorization/tutorial/run.py - :linenos: - :language: python - -BFG's ``make_app`` callable also can accept an authorization policy -parameter. We don't need to specify one, we'll use the default. - -Adding 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 add a different file (for presentation convenience) to add login -and logout views. 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 opf 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: - -.. code-block:: python - :linenos: - - logged_in = authenticated_user(request) - -We'll then change the return value of ``render_template_to_response`` -to pass the `resulting `logged_in`` value to the template, e.g.: - -.. code-block:: python - :linenos: - - return render_template_to_response('templates/view.pt', - request = request, - 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> - -Changing ``configure.zcml`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Change your application's ``configure.zcml`` to add a slightly -inscrutable ``utility`` stanza. This configures our login view to -show up when BFG detects that a view invocation can not be authorized. -When you're done, your ``configure.zcml`` will look like so: - -.. literalinclude:: src/authorization/tutorial/configure.zcml - :linenos: - :language: xml - -Giving Our Root Model Object an ACL ------------------------------------ - -We need to give our root model object an ACL. This ACL will be -sufficient to provide enough information to the BFG security machinery -to challenge a user who doesn't have appropriate credentials when he -attempts to invoke the ``add_page`` or ``edit_page`` views. - -We need to perform some imports at module scope in our ``models.py`` -file: - -.. code-block:: python - :linenos: - - from repoze.bfg.security import Allow - from repoze.bfg.security import Everyone - -Our root model is a ``Wiki`` object. We'll add the following line at -class scope to our ``Wiki`` class: - -.. code-block:: python - :linenos: - - __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'editor', 'edit') ] - -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 :mod:`repoze.bfg` applications. We -actually only need *one* ACL for the entire system, however, because -our security requirements are simple, so this feature is not -demonstrated. - -Our resulting ``models.py`` file will now look like so: - -.. literalinclude:: src/authorization/tutorial/models.py - :linenos: - :language: python - -Adding ``permission`` Declarations to our ``bfg_view`` Decorators ------------------------------------------------------------------ - -To protect each of our views with a particular permission, we need to -pass a ``permission`` argument to each of our ``bfg_view`` decorators. -To do so, within ``views.py``: - -- We add ``permission='view'`` to the ``bfg_view`` decorator attached - to the ``static_view`` view function. This makes the assertion that - only users who possess the effective ``view`` permission at the time - of the request may invoke this view. We've granted ``Everyone`` the - view permission at the root model via its ACL, so everyone will be - able to invoke the ``static_view`` view. - -- We add ``permission='view'`` to the ``bfg_view`` decorator attached - to the ``view_wiki`` view function. This makes the assertion that - only users who possess the effective ``view`` permission at the time - of the request may invoke this view. We've granted ``Everyone`` the - view permission at the root model via its ACL, so everyone will be - able to invoke the ``view_wiki`` view. - -- We add ``permission='view'`` to the ``bfg_view`` decorator attached - to the ``view_page`` view function. This makes the assertion that - only users who possess the effective ``view`` permission at the time - of the request may invoke this view. We've granted ``Everyone`` the - view permission at the root model via its ACL, so everyone will be - able to invoke the ``view_page`` view. - -- We add ``permission='edit'`` to the ``bfg_view`` decorator attached - to the ``add_page`` view function. This makes the assertion that - only users who possess the effective ``view`` permission at the time - of the request may invoke this view. We've granted ``editor`` the - view permission at the root model via its ACL, so only the user - named ``editor`` will able to invoke the ``add_page`` view. - -- We add ``permission='edit'`` to the ``bfg_view`` decorator attached - to the ``edit_page`` view function. This makes the assertion that - only users who possess the effective ``view`` permission at the time - of the request may invoke this view. We've granted ``editor`` the - view permission at the root model via its ACL, so only the user - named ``editor`` will able to invoke the ``edit_page`` view. - -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/ <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/ - <http://localhost:6543/FrontPage/>`_ in a browser invokes the - ``view_page`` view of the front page page object. This is because - it's the *default view* (a view without a ``name``) for Page - objects. It is executable by any user. - -- Visiting `http://localhost:6543/FrontPage/edit_page - <http://localhost:6543/FrontPage/edit_page>`_ in a browser invokes - the edit view for the front page 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 - show the edit page form being displayed. - -- Visiting `http://localhost:6543/add_page/SomePageName - <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 show the edit page - form being displayed. - -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. - - - diff --git a/docs/tutorials/bfgwiki2/basiclayout.rst b/docs/tutorials/bfgwiki2/basiclayout.rst index 1ec10094e..7cf8274c8 100644 --- a/docs/tutorials/bfgwiki2/basiclayout.rst +++ b/docs/tutorials/bfgwiki2/basiclayout.rst @@ -2,9 +2,9 @@ Basic Layout ============ -The starter files generated by the ``bfg_zodb`` template are basic, -but they provide a good orientation for the high-level patterns common -to most :term:`traversal` -based BFG (and BFG with ZODB) projects. +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 BFG projects. ``__init__.py`` --------------- @@ -16,9 +16,9 @@ directory as a Python package. Configuration With ``configure.zcml`` -------------------------------------- -BFG uses a 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: +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: @@ -28,26 +28,29 @@ namespace. Our sample ZCML file looks like the following: #. *Line 3*. Boilerplate, the comment explains. -#. *Lines 6-9*. Register a ``<view>`` that is bound to a class. - ``.views.my_view`` is a *function* we write (generated by the - ``bfg_zodb`` template) that is given a ``context`` and a - ``request`` and returns a response. +#. *Lines 6-7*. Register a :term:`subscriber` that tears down the + SQLAlchemy connection after a request is finished. - Since this ``<view>`` doesn't have a ``name`` attribute, it is the - "default" view for that class. +#. *Lines 9-12*. Register a ``<route>`` that will be used when the + URL is ``/``. Since this ``<view>`` has a blank ``name`` + attribute, it is the "default" view. ``.views.my_view`` refers to a + *function* we write (generated by the ``bfg_routesalchemy`` + template) that is given a ``context`` and a ``request`` and returns + a response. -#. *Lines 11-15*. Register a view on the ``MyModels`` class that - answers URL segments of ``static``. This is a view that will serve - up static resources for us, in this case, at +#. *Lines 14-17*. Register a ``<route>`` with a path that starts with + ``/static``, capturing the rest of the URL as ``subpath``. This is + a view that will serve up static resources for us, in this case, at ``http://localhost:6543/static/`` and below. Content Models with ``models.py`` --------------------------------- -BFG often uses the word *model* when talking about content resources -arranged in a hierarchical *model graph*. The ``models.py`` file is -where the ``bfg_zodb`` Paster template put the classes that implement -our models. +In the context of a SQLAlchemy-based application, a *model* object is +an object composed by quering the SQL database which backs an +application. SQLAlchemy is an "object relational mapper" (an ORM). +The ``models.py`` file is where the ``bfg_zodb`` Paster template put +the classes that implement our models. Here is the source for ``models.py``: @@ -55,22 +58,36 @@ Here is the source for ``models.py``: :linenos: :language: py -#. *Lines 3-4*. The ``MyModel`` class we referred to in the ZCML is - implemented here. It is persistent (via PersistentMapping). The - ``__parent__`` and ``__name__`` are important parts of the - traversal protocol. By default, have these as ``None`` indicating - that this is the :term:`root` object. - -#. *Lines 6-12*. ``appmaker`` is used to return the *application - root* object. It is called on *every request* to the BFG - application (it is essentially a :term:`root factory`). It also - performs bootstrapping by *creating* an application root (inside - the ZODB root object) if one does not already exist. - - We do so by first seeing if the database has the persistent - application root. If not, we make an instance, store it, and - commit the transaction. We then return the application root - object. +#. *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 20*. Set up a SQLAlchemy metadata object. + +#. *Lines 22-24*. A model class named ``Model``. It has an + ``__init__`` that takes a single argument (``name``). It stores a + single attribute named ``name``. + +#. *Lines 26-31*. A SQLAlchemy ``Table`` declaration named + ``models_table`` which we'll use later to map onto our ``Model`` + class. + +#. *Line 33*. 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 35-40*. A function named ``populate`` which adds a single + model instance into our SQL storage and commits a transaction. + +#. *Lines 42-50*. 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`` --------------------------- @@ -84,21 +101,33 @@ and its ``app`` function: :linenos: :language: py -#. *Line 11*. After importing our application, get the ``appmaker`` - function described above. - -#. *Line 12*. Get the ZODB configuration from the ``tutorial.ini`` - file's ``[app:main]`` section. This will be a URI (something like - ``file:///path/to/Data.fs``). - -#. Line *16*. We create a :term:`root factory` using the - ``PersistentApplicationFinder`` helper class, passing it the - ZODB URI and our appmaker. - -#. Line *17*. We use the ``repoze.bfg.router.make_app`` to return a - :term:`WSGI` application. The ``make_app`` function takes the root - factory (``get_root``), the *package* representing our application, - and the keywords parsed by PasteDeploy. +#. *Lines 1-5*. Imports to support later code. + +#. *Lines 7-11*. We define a ``Cleanup`` class which has a + ``__del__`` method (the method called at Python object + destruction), which calls a function. + +#. *Lines 13-15*. An event :term:`subscriber` which adds a + ``Cleanup`` instance to the WSGI environment as + ``tutorial.sasession``. As a result of registering this event + subscriber, when the WSGI environment is cleaned up, our database + connection will be removed. + +#. *Lines 17-24*. Get the database configuration string from the + ``tutorial.ini`` file's ``[app:sql]`` section. This will be a URI + (something like ``sqlite://``). + +#. Line *25*. We initialize our SQL database using SQLAlchemy, passing + it the db string. + +#. Line *26*. We use the ``repoze.bfg.router.make_app`` to return a + :term:`WSGI` application. The ``make_app`` function's first + parameter is the "root factory". Since this is a URL dispatch + application, the root factory is ``None`` (we don't do any + :term:`traversal` in this app. The second argument is the + *package* representing our application, and the third argument, + ``options`` is passed as a keyword argument. It contains a + dictionary of options parsed by PasteDeploy. We'll later change ``run.py`` when we add :term:`authorization` to our wiki application. diff --git a/docs/tutorials/bfgwiki2/definingmodels.rst b/docs/tutorials/bfgwiki2/definingmodels.rst deleted file mode 100644 index 61eb5f112..000000000 --- a/docs/tutorials/bfgwiki2/definingmodels.rst +++ /dev/null @@ -1,147 +0,0 @@ -=============== -Defining Models -=============== - -The first change we'll make to our bone-stock paster-generated -application will be to define a number of :term:`model` constructors. -For this application, which will be a Wiki, we will need two kinds of -model constructors: a "Wiki" model constructor, and a "Page" model -constructor. Both our Page and Wiki constructors will be class -objects. A single instance of the "Wiki" class will serve as a -container for "Page" objects, which will be instances of the "Page" -class. - -Adding Model Classes --------------------- - -The first thing we want to do is remove the ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample -and we're not going to use it. - -.. 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. - -Then, we'll add a ``Wiki`` class. Because this is a ZODB application, -this class should inherit from -``persistent.mapping.PersistentMapping``. We want it to inherit from -the ``PersistentMapping`` class because our Wiki class will be a -mapping of wiki page names to ``Page`` objects. The -``PersistentMapping`` class provides our class with mapping behavior, -and makes sure that our Wiki page is stored as a "first-class" -persistent object in our ZODB database. - -Our ``Wiki`` class should also have a ``__name__`` attribute set to -``None`` at class scope, and should have a ``__parent__`` attribute -set to None at class scope as well. If a model has a ``__parent__`` -attribute of ``None`` in a traversal-based :mod:`repoze.bfg` -application, it means that it's the :term:`root` model. The -``__name__`` of the root model is always ``None``. - -Then we'll add a ``Page`` class. This class should inherit from -``persistent.Persistent``. We'll also give it an ``__init__`` method -that accepts a single parameter named ``data``. This parameter will -contain the :term:`ReStructuredText` body representing the wiki page -content. Note that ``Page`` objects don't have an initial -``__name__`` or ``__parent__`` attribute. All objects in a traversal -graph must have a ``__name__`` and a ``__parent__`` attribute. We -don't specify these here because both ``__name__`` and ``__parent__`` -will be set by by a :term:`view` function when a Page is added to our -Wiki mapping. - -Add an Appmaker ---------------- - -We're using a mini-framework callable named -``repoze.zodbconn.finder.PersistentApplicationFinder`` in our -application (see "run.py"). A ``PersistentApplicationFinder`` accepts -a ZODB URL as well as an "appmaker" callback. This callback typically -lives in the ``models.py`` file. - -We want to change the appmaker function in our ``models.py`` file so -that our application root is a Wiki instance, and we'll also slot a -single page object (the front page) into the wiki. - -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 - -Testing the Models ------------------- - -To make sure the code we just wrote works, we write tests for the -model classes and the appmaker. Changing ``tests.py``, we'll write a -separate test class for each model class, and we'll write a test class -for the ``appmaker``. - -To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided -as a result of the ``bfg_zodb`` project generator but we'll disuse the -``ViewIntegrationTests`` class. The ``ViewIntegrationTests`` class is -too "heavy-hammer" for our tastes. We'll add three test classes: one -for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` -model named ``WikiModelTests``, and one for the appmaker named -``AppmakerTests``. - -When we're done changing ``tests.py``, it will look something like so: - -.. literalinclude:: src/models/tutorial/tests.py - :linenos: - :language: python - -Running the Tests ------------------ - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: bash - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: bash - - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q - -The expected output is something like this: - -.. code-block:: bash - - ..... - ---------------------------------------------------------------------- - Ran 5 tests in 0.008s - - OK - -Declaring Dependencies in Our ``setup.py`` File ------------------------------------------------ - -Our application depends on packages which are not dependencies of the -original "tutorial" application as it was generated by the ``paster -create`` command. We'll add these dependencies to our ``tutorial`` -package's ``setup.py`` file by assigning these dependencies to both -the ``install_requires`` and the ``tests_require`` parameters to the -``setup`` function. In particular, we require the ``docutils`` -package. - -Our resulting ``setup.py`` should look like so: - -.. literalinclude:: src/models/setup.py - :linenos: - :language: python - diff --git a/docs/tutorials/bfgwiki2/definingviews.rst b/docs/tutorials/bfgwiki2/definingviews.rst deleted file mode 100644 index bf47c37ad..000000000 --- a/docs/tutorials/bfgwiki2/definingviews.rst +++ /dev/null @@ -1,364 +0,0 @@ -============== -Defining Views -============== - -Views in BFG are typically simple Python functions that accept two -parameters: :term:`context`, and :term:`request`. A view is assumed -to return a :term:`response` object. - -Adding View Functions -===================== - -We're going to add four :term:`view` functions to our ``views.py`` -module. One view (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 named ``edit_page`` will allow a -page to be edited. - -.. note:: - - There is nothing automagically special about the filename - ``views.py``. A project may have many views throughout its codebase - in arbitrarily-named files. Files implementing views 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 default view of a -``Wiki`` model object. It always redirects to the ``Page`` object -named "FrontPage". It returns an instance of the -``webob.exc.HTTPFound`` class (instances of which implement the WebOb -:term:`response` interface), and the ``repoze.bfg.model_url`` API. -``model_url`` constructs a URL to the ``FrontPage`` page -(e.g. ``http://localhost:6543/FrontPage``), and uses 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 default view of a -``Page`` object. The ``view_page`` function renders the -:term:`ReStructuredText` body of a page (stored as the ``data`` -attribute of the context, which will be 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 (our -page's ``__parent__``) 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 subsitution 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 call the -``repoze.bfg.chameleon_zpt.render_template_to_response`` function with -a number of arguments. The first argument is the *relative* path to a -:term:`Chameleon` ZPT template. It is relative to the directory of -the file in which we're creating the ``view_page`` function. The -``render_template_to_response`` function also accepts ``request``, -``page``, ``content``, and ``edit_url`` as keyword arguments. As a -result, the template will be able to use these names to perform -various rendering tasks. - -The result of ``render_template_to_response`` is returned to -:mod:`repoze.bfg`. Unsurprisingly, it is a response object. - -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 ``context`` of the -``add_page`` view is always a Wiki object (*not* a Page object). - -The request :term:`subpath` in BFG is the sequence of names that are -found *after* the view name in the URL segments given to BFG as the -result of a request. If our add view is invoked via, -e.g. ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` -will be ``['SomeName']``. - -The add view takes the zeroth element of the subpath (the wiki page -name), and aliases it to the name attribute in order to know the name -of the page we're trying to add. - -If the view rendering is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is False), the view -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 we'll render the template to a -response. - -If the view rendering *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 subpath and the page body, and save it into "our context" (the -wiki) using the ``__setitem__`` method of the context. We then -redirect back to the ``view_page`` view (the 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 ``context`` -of the ``edit_page`` view will *always* be a Page object (never a Wiki -object). - -If the view rendering 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 rendering *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`` attribute of the page context. It then redirects to the -default view of the context (the 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 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 - - -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: - -.. literalinclude:: src/views/tutorial/templates/static/style.css - :linenos: - :language: css - -This CSS file will be accessed via -e.g. ``http://localhost:6543/static/style.css`` by virtue of the -``static_view`` view we've defined in the ``views.py`` 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. - -Testing the Views -================= - -We'll modify our ``tests.py`` file, adding tests for each view -function we added above. As a result, we'll *delete* the -``ViewTests`` test in the file, and add four other test classes: -``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and -``EditPageTests``. These test the ``view_wiki``, ``view_page``, -``add_page``, and ``edit_page`` views respectively. - -Once we're done with the ``tests.py`` module, it will look a lot like -the below: - -.. literalinclude:: src/views/tutorial/tests.py - :linenos: - :language: python - -Running the Tests -================= - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: bash - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: bash - - - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q - -The expected result looks something like: - -.. code-block:: bash - - ......... - ---------------------------------------------------------------------- - Ran 9 tests in 0.203s - - OK - -Mapping Views to URLs in ``configure.zcml`` -=========================================== - -The ``configure.zcml`` file contains ``view`` declarations which serve -to map URLs (via :term:`traversal`) to view functions. You'll need to -add five ``view`` declarations to ``configure.zcml``. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``static_view`` in our ``views.py`` file with - the view name ``static``. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``view_wiki`` in our ``views.py`` file with - no view name. This is the default view for a Wiki. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``view_page`` in our ``views.py`` file with - no view name. This is the default view for a Page. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``add_page`` in our ``views.py`` file with - the view name ``add_page``. This is the add view for a new Page. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``edit_page`` in our ``views.py`` file with - the view name ``edit_page``. This is the edit view for a 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 - -Examining ``tutorial.ini`` -========================== - -Let's take a look at our ``tutorial.ini`` file. The contents of the -file are as follows: - -.. literalinclude:: src/models/tutorial.ini - :linenos: - :language: ini - -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.zodbconn#closer" is at the "top" of the pipeline. This is -a piece of middleware which closes the ZODB connection opened by the -PersistentApplicationFinder at the end of the request. - -"egg:repoze.tm#tm" is the second piece of middleware in the pipeline. -This commits a transaction near the end of the request unless there's -an exception raised. - -Adding an Element to the Pipeline ---------------------------------- - -Let's add a piece of middleware to the WSGI pipeline. -"egg:Paste#evalerror" middleware which displays debuggable errors in -the browser while you're developing (not recommeded for deployment). -Let's insert evalerror into the pipeline right below -"egg:repoze.zodbconn#closer", 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/ <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/ - <http://localhost:6543/FrontPage/>`_ in a browser invokes the - ``view_page`` view of the front page page object. This is because - it's the *default view* (a view without a ``name``) for Page objects. - -- Visiting `http://localhost:6543/FrontPage/edit_page - <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 - <http://localhost:6543/add_page/SomePageName>`_ in a browser invokes - the add view for a page. - -- To generate an error, do `http://localhost:6543/add_page - <http://localhost:6543/add_page>`_. IndexError for - ``request.subpath[0]``. You'll see an interactive traceback - facility provided by evalerror. - - - - - diff --git a/docs/tutorials/bfgwiki2/distributing.rst b/docs/tutorials/bfgwiki2/distributing.rst deleted file mode 100644 index 2b99c9e3a..000000000 --- a/docs/tutorials/bfgwiki2/distributing.rst +++ /dev/null @@ -1,96 +0,0 @@ -============================= -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 BFG environment. - -On UNIX: - -.. code-block:: bash - - $ ../bin/python setup.py sdist - -On Windows: - -.. code-block:: bash - - 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:: bash - - running sdist - 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 - writing manifest file 'tutorial.egg-info/SOURCES.txt' - warning: sdist: missing required meta-data: url - warning: sdist: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied - creating tutorial-0.1 - creating tutorial-0.1/tutorial - creating tutorial-0.1/tutorial.egg-info - creating tutorial-0.1/tutorial/templates - creating tutorial-0.1/tutorial/templates/static - creating tutorial-0.1/tutorial/templates/static/images - making hard links in tutorial-0.1... - hard linking CHANGES.txt -> tutorial-0.1 - hard linking README.txt -> tutorial-0.1 - hard linking ez_setup.py -> tutorial-0.1 - hard linking setup.cfg -> tutorial-0.1 - hard linking setup.py -> tutorial-0.1 - hard linking tutorial.ini -> tutorial-0.1 - hard linking tutorial/__init__.py -> tutorial-0.1/tutorial - hard linking tutorial/configure.zcml -> tutorial-0.1/tutorial - hard linking tutorial/models.py -> tutorial-0.1/tutorial - hard linking tutorial/run.py -> tutorial-0.1/tutorial - hard linking tutorial/tests.py -> tutorial-0.1/tutorial - hard linking tutorial/views.py -> tutorial-0.1/tutorial - hard linking tutorial.egg-info/PKG-INFO -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/SOURCES.txt -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/dependency_links.txt -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/entry_points.txt -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/not-zip-safe -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/requires.txt -> tutorial-0.1/tutorial.egg-info - hard linking tutorial.egg-info/top_level.txt -> tutorial-0.1/tutorial.egg-info - hard linking tutorial/templates/edit.pt -> tutorial-0.1/tutorial/templates - hard linking tutorial/templates/mytemplate.pt -> tutorial-0.1/tutorial/templates - hard linking tutorial/templates/view.pt -> tutorial-0.1/tutorial/templates - hard linking tutorial/templates/static/default.css -> tutorial-0.1/tutorial/templates/static - hard linking tutorial/templates/static/style.css -> tutorial-0.1/tutorial/templates/static - hard linking tutorial/templates/static/templatelicense.txt -> tutorial-0.1/tutorial/templates/static - hard linking tutorial/templates/static/images/img01.gif -> tutorial-0.1/tutorial/templates/static/images - hard linking tutorial/templates/static/images/img02.gif -> tutorial-0.1/tutorial/templates/static/images - hard linking tutorial/templates/static/images/img03.gif -> tutorial-0.1/tutorial/templates/static/images - hard linking tutorial/templates/static/images/img04.gif -> tutorial-0.1/tutorial/templates/static/images - hard linking tutorial/templates/static/images/spacer.gif -> tutorial-0.1/tutorial/templates/static/images - copying setup.cfg -> tutorial-0.1 - Writing tutorial-0.1/setup.cfg - 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. - diff --git a/docs/tutorials/bfgwiki2/index.rst b/docs/tutorials/bfgwiki2/index.rst index 5860dc181..48126b178 100644 --- a/docs/tutorials/bfgwiki2/index.rst +++ b/docs/tutorials/bfgwiki2/index.rst @@ -1,12 +1,13 @@ -.. _bfg_wiki_tutorial: +.. _bfg_sql_wiki_tutorial: -BFG Wiki Tutorial -================= +BFG URL Dispatch + SQLAlchemy Wiki Tutorial +=========================================== -This tutorial introduces a :term:`traversal` -based BFG application to -a developer with at least passing familiarity to Python. When we're -done with the tutorial, the developer will have created a basic Wiki -application with authentication. +This tutorial introduces a :term:`url dispatch` and :term:`SQLAlchemy` +-based BFG application to a developer with at least passing +familiarity to Python. When we're done with the tutorial, the +developer will have created a basic Wiki application with +authentication. Contents: diff --git a/docs/tutorials/bfgwiki2/installation.rst b/docs/tutorials/bfgwiki2/installation.rst index 58a811e8f..fd96211dc 100644 --- a/docs/tutorials/bfgwiki2/installation.rst +++ b/docs/tutorials/bfgwiki2/installation.rst @@ -223,6 +223,9 @@ On Windows: 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 ================================== diff --git a/docs/tutorials/bfgwiki2/viewdecorators.rst b/docs/tutorials/bfgwiki2/viewdecorators.rst deleted file mode 100644 index b9fd3b454..000000000 --- a/docs/tutorials/bfgwiki2/viewdecorators.rst +++ /dev/null @@ -1,257 +0,0 @@ -========================================================== -Using View Decorators Rather than ZCML ``view`` directives -========================================================== - -So far we've been using :term:`ZCML` to map model types to views. -It's often easier to use the "bfg_view" view decorator to do this -mapping. Using view decorators provides better locality of reference -for the mapping, because you can see which model types and view names -the view will serve right next to the view function itself. In this -mode, however, you lose the ability for some views to be overridden -"from the outside" (by someone using your application as a framework). -Since this application is *definitely* not a framework, it makes sense -for us to switch over to using view decorators. - -Adding View Decorators -====================== - -We're going to import the ``bfg_view`` callable from the -``repoze.bfg.view`` module. This callable can be used as a function -decorator. We'll use it to decorate our ``static_view``, -``view_wiki``, ``view_page``, ``add_page`` and ``edit_page`` view -functions. - -The ``bfg_view`` callable accepts a number of arguments: - -``for_`` - - The model type which this view is "for", in our case a class. - -``name`` - - The name of the view. - -There are other arguments which this callable accepts, but these are -the ones we're going to use. - -The ``static_view`` view function ---------------------------------- - -Because our ``bfg_view`` decorator can only decorate view functions -and classes (not instances), we rename our ``static_view`` to -``static_app`` and create a new function named ``static_view`` which -simply calls ``static_app`` with the context and request. We decorate -the resulting ``static_view`` function with the following: - -.. code-block:: python - :linenos: - - @bfg_view(for_=Wiki, name='static') - -This indicates that the view is "for" the Wiki class and has the -view_name ``static``. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - for=".models.Wiki" - view=".views.static_view" - name="static" - /> - -Our decorator takes its place. - -The ``view_wiki`` view function -------------------------------- - -The decorator above the ``view_wiki`` function will be: - -.. code-block:: python - :linenos: - - @bfg_view(for_=Wiki) - -This indicates that the view is "for" the Wiki class and has the -*empty* view_name (indicating the default view). After injecting this -decorator, we can now *remove* the following from our -``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - for=".models.Wiki" - view=".views.view_wiki" - /> - -Our new decorator takes its place. - -The ``view_page`` view function -------------------------------- - -The decorator above the ``view_page`` function will be: - -.. code-block:: python - :linenos: - - @bfg_view(for_=Page) - -This indicates that the view is "for" the Page class and has the -*empty* view_name (indicating the default view). After injecting this -decorator, we can now *remove* the following from our -``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - for=".models.Page" - view=".views.view_page" - /> - - -Our new decorator takes its place. - -The ``add_page`` view function ------------------------------- - -The decorator above the ``add_page`` function will be: - -.. code-block:: python - :linenos: - - @bfg_view(for_=Wiki, name='add_page') - -This indicates that the view is "for" the Wiki class and has the -``add_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - for=".models.Wiki" - name="add_page" - view=".views.add_page" - /> - -Our new decorator takes its place. - -The ``edit_page`` view function -------------------------------- - -The decorator above the ``edit_page`` function will be: - -.. code-block:: python - :linenos: - - @bfg_view(for_=Page, name='edit_page') - -This indicates that the view is "for" the Page class and has the -``edit_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - for=".models.Page" - name="edit_page" - view=".views.edit_page" - /> - -Our new decorator takes its place. - -Adding a Scan Directive -======================= - -In order for our decorators to be recognized, we must add a bit of -boilerplate to our ``configure.zcml`` file. Add the following tag -anywhere beneath the ``<include package="repoze.bfg.includes">`` tag -but before the ending ``</configure>`` tag within ``configure.zcml``: - -.. code-block:: xml - :linenos: - - <include package="repoze.bfg.includes" /> - <scan package="."/> - -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/viewdecorators/tutorial/views.py - :linenos: - :language: python - -Viewing the Results of Our Edits to ``configure.zcml`` -====================================================== - -The result of all of our edits to ``configure.zcml`` will leave it -looking like this: - -.. literalinclude:: src/viewdecorators/tutorial/configure.zcml - :linenos: - :language: xml - -Running the Tests -================= - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: bash - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: bash - - - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q - -Hopefully nothing will have changed. The expected result looks -something like: - -.. code-block:: bash - - ......... - ---------------------------------------------------------------------- - Ran 9 tests in 0.203s - - OK - -Viewing the Application in a Browser -==================================== - -Once we've set up the WSGI pipeline properly, we can finally examine -our application in a browser. We'll make sure that we didn't break -any views by trying each of them. - -- Visiting `http://localhost:6543/ <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/ - <http://localhost:6543/FrontPage/>`_ in a browser invokes the - ``view_page`` view of the front page page object. This is because - it's the *default view* (a view without a ``name``) for Page objects. - -- Visiting `http://localhost:6543/FrontPage/edit_page - <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 - <http://localhost:6543/add_page/SomePageName>`_ in a browser invokes - the add view for a page. - - - |
