diff options
Diffstat (limited to 'docs/tutorials')
| -rw-r--r-- | docs/tutorials/bfgwiki/authorization.rst | 93 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki/background.rst | 13 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki/basiclayout.rst | 75 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki/definingmodels.rst | 74 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki/definingviews.rst | 171 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki/viewdecorators.rst | 30 |
6 files changed, 247 insertions, 209 deletions
diff --git a/docs/tutorials/bfgwiki/authorization.rst b/docs/tutorials/bfgwiki/authorization.rst index 9944f69e0..8d3f08d6a 100644 --- a/docs/tutorials/bfgwiki/authorization.rst +++ b/docs/tutorials/bfgwiki/authorization.rst @@ -23,12 +23,13 @@ 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 add a ``forbidden`` -stanza. This configures our login view to show up when -:mod:`repoze.bfg` detects that a view invocation can not be -authorized. When you're done, your ``configure.zcml`` will look like -so: +:class:`repoze.bfg.authentication.AuthTktAuthenticationPolicy` and an +:class:`repoze.bfg.authorization.ACLAuthorizationPolicy` to enable +declarative security checking. We'll also add a ``forbidden`` stanza, +which species a :term:`forbidden view`. This configures our login +view to show up when :mod:`repoze.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: @@ -39,20 +40,21 @@ 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: -The groupfinder defined here is an authorization policy "callback"; it -is a be 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``. We'll use "dummy" data to represent user and -groups sources. When we're done, your application's ``security.py`` -will look like this. +directory as ``run.py``, ``views.py``, etc) with the following +content: .. literalinclude:: src/authorization/tutorial/security.py :linenos: :language: python +The ``groupfinder`` function defined here is an authorization policy +"callback"; it is a a callable that accepts a userid and a request. +If the userid exists in the set of users known by 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``. +We'll use "dummy" data to represent user and groups sources. + Adding Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -123,8 +125,8 @@ class="main_content">`` div: 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 :mod:`repoze.bfg` +We need to give our root model object an :term:`ACL`. This ACL will +be sufficient to provide enough information to the :mod:`repoze.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. @@ -163,29 +165,32 @@ 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 ``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. +pass a ``permission`` argument to each of our +:class:`repoze.bfg.view.bfg_view` decorators. To do so, within +``views.py``: + +- We add ``permission='view'`` to the 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 + :data:`repoze.bfg.security.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 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 + :data:`repoze.bfg.security.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 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 the``editor`` + principal 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 @@ -197,8 +202,8 @@ To do so, within ``views.py``: 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: +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 @@ -208,8 +213,8 @@ our application in a browser. The views we'll try are as follows: - 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. + it's the :term:`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 diff --git a/docs/tutorials/bfgwiki/background.rst b/docs/tutorials/bfgwiki/background.rst index dde516baf..c1d8d7477 100644 --- a/docs/tutorials/bfgwiki/background.rst +++ b/docs/tutorials/bfgwiki/background.rst @@ -2,16 +2,17 @@ Background ========== -This version of the BFG wiki tutorial presents a :mod:`repoze.bfg` -application that uses technologies which will be familiar to someone -with :term:`Zope` experience. It uses :term:`ZODB` as a persistence -mechanism and :term:`traversal` to map URLs to code. It can also be -followed by people without any prior Python web framework experience. +This version of the :mod:`repoze.bfg` wiki tutorial presents a +:mod:`repoze.bfg` application that uses technologies which will be +familiar to someone with :term:`Zope` experience. It uses +:term:`ZODB` as a persistence mechanism and :term:`traversal` 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 known to work under :mod:`repoze.bfg` version 1.1a5. +This tutorial targets :mod:`repoze.bfg` version 1.2. Have fun! diff --git a/docs/tutorials/bfgwiki/basiclayout.rst b/docs/tutorials/bfgwiki/basiclayout.rst index d373624de..84f6e493e 100644 --- a/docs/tutorials/bfgwiki/basiclayout.rst +++ b/docs/tutorials/bfgwiki/basiclayout.rst @@ -17,9 +17,9 @@ directory as a Python package. Configuration With ``configure.zcml`` -------------------------------------- -:mod:`repoze.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: +The ``bfg_zodb`` template uses :term:`ZCML` to perform system +configuration. The ZCML file generated by the template looks like the +following: .. literalinclude:: src/basiclayout/tutorial/configure.zcml :linenos: @@ -56,10 +56,11 @@ XML namespace. Our sample ZCML file looks like the following: Content Models with ``models.py`` --------------------------------- -:mod:`repoze.bfg` often uses the word *model* when talking about -content resources arranged in a hierarchical *object graph*. The -``models.py`` file is where the ``bfg_zodb`` Paster template put the -classes that implement our models. +:mod:`repoze.bfg` often uses the word :term:`model` when talking about +content resources arranged in the hierarchical *object graph* +consulted by :term:`traversal`. The ``models.py`` file is where the +``bfg_zodb`` Paster template put the classes that implement our model +objects. Here is the source for ``models.py``: @@ -67,11 +68,14 @@ 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 +#. *Lines 3-4*. The ``MyModel`` class we referred to in the ZCML file + named ``configure.zcml`` is implemented here. Instances of this + class will be capable of being persisted in :term:`ZODB` because + the class inherits from the + :class:`persistint.mapping.PersistentMapping` class. 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. + :term:`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 @@ -87,31 +91,44 @@ Here is the source for ``models.py``: App Startup with ``run.py`` --------------------------- -How does a :mod:`repoze.bfg` application start up? When you run under -``paster`` using the ``tutorial.ini`` generated config file, the -application area points at an entry point. Our entry point happens to -be in ``run.py`` and its ``app`` function: +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 -#. *Line 11*. After importing our application, get the ``appmaker`` - function described above. +#. *Lines 1-3*. Perform some dependency imports. -#. *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 11*. Get the ZODB configuration from the ``tutorial.ini`` + file's ``[app:main]`` section represented by the ``settings`` + dictionary passed to our ``app`` function. This will be a URI + (something like ``file:///path/to/Data.fs``). -#. Line *16*. We create a "finder" object using the - ``PersistentApplicationFinder`` helper class, passing it the ZODB - URI and our appmaker. +#. Line *14*. We create a "finder" object using the + :class:`repoze.zodbconn.finder.PersistentApplicationFinder` helper + class, passing it the ZODB URI and the "appmaker" we've imported + from ``models.py``. -#. *Lines 17 - 18*. We create a :term:`root factory` using the finder. +#. *Lines 15 - 16*. We create a :term:`root factory` which uses the + finder to return a ZODB root object. -#. Line *19*. We construct a :term:`Configurator` with a - :term:`root factory` and the settings keywords parsed by - PasteDeploy. The root factory is named ``get_root``. We then use - the ``make_wsgi_app`` method of the :term:`Configurator` to return - a :term:`WSGI` application. +#. Line *17*. We construct a :term:`Configurator` with a :term:`root + factory` and the settings keywords parsed by PasteDeploy. The root + factory is named ``get_root``. + +# *Lines 18-20*. Begin configuration using the + :meth:`repoze.bfg.configuration.Configurator.begin` method, load + the ``configure.zcml`` file from our package using the + :meth:`repoze.bfg.configuration.Configurator.load_zcml` method, and + end configuration using the + :meth:`repoze.bfg.configuration.Configurator.end` method. + +# *Line 21*. Use the + :meth:`repoze.bfg.configuration.Configurator.make_wsgi_app` method + to return a :term:`WSGI` application. diff --git a/docs/tutorials/bfgwiki/definingmodels.rst b/docs/tutorials/bfgwiki/definingmodels.rst index 1b6462a7b..3c41e9dbe 100644 --- a/docs/tutorials/bfgwiki/definingmodels.rst +++ b/docs/tutorials/bfgwiki/definingmodels.rst @@ -2,7 +2,7 @@ Defining Models =============== -The first change we'll make to our bone-stock paster-generated +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 @@ -16,12 +16,12 @@ Deleting the Database We're going to remove the ``MyModel`` Python model class from our ``models.py`` file. Since this class is referred to within our -persistent storage (within ``Data.fs``), we'll have strange things -happen the next time we want to visit the application in a browser. -Remove the ``Data.fs`` from the ``tutorial`` directory before -proceeding any further. It's always fine to do this as long as you -don't care about the content of the database; the database itself will -be recreated as necessary. +persistent storage (represented on disk as a file named ``Data.fs``), +we'll have strange things happen the next time we want to visit the +application in a browser. Remove the ``Data.fs`` from the +``tutorial`` directory before proceeding any further. It's always +fine to do this as long as you don't care about the content of the +database; the database itself will be recreated as necessary. Adding Model Classes -------------------- @@ -41,38 +41,40 @@ and we're not going to use it. 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. +:class:`persistent.mapping.PersistentMapping`. We want it to inherit +from the :class:`persistent.mapping.PersistentMapping` class because +our Wiki class will be a mapping of wiki page names to ``Page`` +objects. The :class:`persistent.mapping.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. +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 also always ``None``. + +Then we'll add a ``Page`` class. This class should inherit from the +:class:`persistent.Persistent` class. 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 +:class:`repoze.zodbconn.finder.PersistentApplicationFinder` in our +application (see ``run.py``). A +:class:`repoze.zodbconn.finder.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 @@ -98,12 +100,10 @@ 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``. +as a result of the ``bfg_zodb`` project generator. 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: diff --git a/docs/tutorials/bfgwiki/definingviews.rst b/docs/tutorials/bfgwiki/definingviews.rst index a3b7ccee4..bd9c9928c 100644 --- a/docs/tutorials/bfgwiki/definingviews.rst +++ b/docs/tutorials/bfgwiki/definingviews.rst @@ -2,15 +2,17 @@ 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. +A :term:`view callable` in a traversal-based :mod:`repoze.bfg` +applications is typically a simple Python function that accepts two +parameters: :term:`context`, and :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 *one* arguments: a :term:`request`. You'll see this - two-argument pattern used in other :mod:`repoze.bfg` tutorials and + one-argument pattern used in other :mod:`repoze.bfg` tutorials and applications. Either calling convention will work in any - :mod:`repoze.bfg` application. In :term:`traversal` based + :mod:`repoze.bfg` application; the calling conventions can be used + interchangeably as necessary. In :term:`traversal` based applications, such as this tutorial, the context is used frequently within the body of a view method, so it makes sense to use the two-argument syntax in this application. However, in :term:`url @@ -19,15 +21,19 @@ to return a :term:`response` object. URL-dispatch-only, it's common to define views as callables that accept only a request to avoid the visual "noise". +We're going to define several :term:`view callable` functions then +wire them into :mod:`repoze.bfg` using some :term:`view +configuration` via :term:`ZCML`. + 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. +We're going to add four :term:`view callable` 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:: @@ -38,28 +44,29 @@ page to be edited. 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 +The ``view_wiki`` function will be configured to 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 +:class:`webob.exc.HTTPFound` class (instances of which implement the +WebOb :term:`response` interface), and the +:func:`repoze.bfg.url.model_url` API. +:func:`repoze.bfg.url.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 ``view_page`` function will be configured to 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 passed to the view; the context 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 @@ -81,17 +88,19 @@ and return it. The arguments we wrap into a dictionary include ``page``, ``content``, and ``edit_url``. As a result, the *template* associated with this -view will be able to use these names to perform various rendering -tasks. The template associated with this view will be a template -which lives in ``templates/view.pt``, which we'll associate with this -view via the ``configure.zcml`` file. - -Note the contrast between this view and the ``view_wiki`` view. In -the ``view_wiki`` view, we return a *response* object. In this view, -we return a *dictionary*. It is *always* fine to return a response -object from a :mod:`repoze.bfg` view. Returning a dictionary is -allowed only when there is a ``renderer`` associated with the view in -the view configuration. +view callable will be able to use these names to perform various +rendering tasks. The template associated with this view callable will +be a template which lives in ``templates/view.pt``, which we'll +associate with this view via the :term:`view configuration` which +lives in the ``configure.zcml`` file. + +Note the contrast between this view callable and the ``view_wiki`` +view callable. In the ``view_wiki`` view callable, we return a +:term:`response` object. In the ``view_page`` view callable, we +return a *dictionary*. It is *always* fine to return a +:term:`response` object from a :mod:`repoze.bfg` view. Returning a +dictionary is allowed only when there is a :term:`renderer` associated +with the view callable in the view configuration. The ``add_page`` view function ------------------------------ @@ -103,33 +112,34 @@ 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, +The request :term:`subpath` in :mod:`repoze.bfg` is the sequence of +names that are found *after* the view name in the URL segments given +in the ``PATH_INFO`` of the WSGI request as the result of +:term:`traversal`. If our add view is invoked via, e.g. ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` -will be ``['SomeName']``. +will be a tuple: ``('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. +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. To do so, we create a +dummy Page object in order to satisfy the edit form's desire to have +*some* page object exposed as ``page``, 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. +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 ------------------------------- @@ -141,15 +151,16 @@ of the ``edit_page`` view will *always* be a Page object (never a Wiki object). 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. +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`` attribute of the page context. It then redirects to the -default view of the context (the page), which will always be 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`` @@ -165,11 +176,11 @@ like this: 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. +Most view callables we've added expected to be rendered via 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 ------------------------ @@ -229,9 +240,10 @@ our package's ``templates/static`` directory: 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. +``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. Testing the Views ================= @@ -284,8 +296,9 @@ 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 four ``view`` declarations to ``configure.zcml``. +to map URLs (via :term:`traversal`) to view functions. This is also +known as :term:`view configuration`. You'll need to add four ``view`` +declarations to ``configure.zcml``. #. 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 @@ -347,19 +360,19 @@ 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.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. +``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 +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 recommended for deployment). Let's insert evalerror into the pipeline right below "egg:repoze.zodbconn#closer", making our resulting ``tutorial.ini`` diff --git a/docs/tutorials/bfgwiki/viewdecorators.rst b/docs/tutorials/bfgwiki/viewdecorators.rst index 299e82658..a1b2810d8 100644 --- a/docs/tutorials/bfgwiki/viewdecorators.rst +++ b/docs/tutorials/bfgwiki/viewdecorators.rst @@ -16,12 +16,13 @@ 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 ``view_wiki``, ``view_page``, +We're going to import the :class:`repoze.bfg.view.bfg_view` callable. +This callable can be used as a function, class, or method decorator. +We'll use it to decorate our ``view_wiki``, ``view_page``, ``add_page`` and ``edit_page`` view functions. -The ``bfg_view`` callable accepts a number of arguments: +The :class:`repoze.bfg.view.bfg_view` callable accepts a number of +arguments: ``for_`` @@ -50,9 +51,9 @@ The decorator above the ``view_wiki`` function will be: @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: +*empty* view_name (indicating the :term:`default view` for the Wiki +class). After injecting this decorator, we can now *remove* the +following from our ``configure.zcml`` file: .. code-block:: xml :linenos: @@ -75,9 +76,9 @@ The decorator above the ``view_page`` function will be: @bfg_view(for_=Page, renderer='templates/view.pt') 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: +*empty* view_name (indicating the :term:`default view` for the Page +class). After injecting this decorator, we can now *remove* the +following from our ``configure.zcml`` file: .. code-block:: xml :linenos: @@ -146,14 +147,15 @@ 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``: +boilerplate to our ``configure.zcml`` file which tells +:mod:`repoze.bfg` to kick off a :term:`scan` at startup time. 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`` |
