diff options
| -rw-r--r-- | docs/tutorials/bfgwiki2/authorization.rst | 92 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/background.rst | 2 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/basiclayout.rst | 61 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/definingmodels.rst | 12 | ||||
| -rw-r--r-- | docs/tutorials/bfgwiki2/definingviews.rst | 106 |
5 files changed, 145 insertions, 128 deletions
diff --git a/docs/tutorials/bfgwiki2/authorization.rst b/docs/tutorials/bfgwiki2/authorization.rst index 5257bbe5f..9aeb98505 100644 --- a/docs/tutorials/bfgwiki2/authorization.rst +++ b/docs/tutorials/bfgwiki2/authorization.rst @@ -6,9 +6,9 @@ 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. +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. @@ -42,15 +42,16 @@ your ``models.py`` file: The ``RootFactory`` class we've just added will be used by :mod:`repoze.bfg` to construct a ``context`` object. The context is -attached to our request as the ``context`` attribute. +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 "Everyone" (a special principal) to view all pages, while -allowing only a user named ``editor`` 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 execution. -See :ref:`assigning_acls` for more information about what an -:term:`ACL` represents. +allows :data:`repoze.bfg.security.Everyone` (a special principal) to +view all pages, while allowing only a user named ``editor`` 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 @@ -65,29 +66,31 @@ your application's ``run.py`` will look like this. :linenos: :language: python -Configuring a ``repoze.bfg`` Authentication Policy --------------------------------------------------- +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 -:term:`application registry` to add an :term:`authentication policy` -and an :term:`authorization policy`. +``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 ``forbidden`` stanza which points at our -login 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`` routes. This indicates that the views -which these routes reference cannot be invoked without the -authenticated user possessing the ``edit`` permission with respect to -the current context. 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 change ``configure.zcml`` +to add a ``forbidden`` 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. When you're done, your ``configure.zcml`` will look like so .. literalinclude:: src/authorization/tutorial/configure.zcml :linenos: @@ -98,14 +101,14 @@ 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 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. +The groupfinder defined here is an :term:`authentication policy` +"callback"; it is a 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. .. literalinclude:: src/authorization/tutorial/security.py :linenos: @@ -114,16 +117,17 @@ will look like this. 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 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 to our application and provide a link -to it. This view will clear the credentials of the logged in user and -redirect back to the front page. +We'll also add a ``logout`` view callable to our application and +provide a link to it. This view will clear the credentials of the +logged in user and redirect back to the front page. We'll add 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: +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: @@ -133,8 +137,8 @@ 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 -into its template. We'll add something like this to each view body: +``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: .. code-block:: python :linenos: @@ -180,8 +184,8 @@ class="main_content">`` div: 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 diff --git a/docs/tutorials/bfgwiki2/background.rst b/docs/tutorials/bfgwiki2/background.rst index 31cecdf96..09dc8de64 100644 --- a/docs/tutorials/bfgwiki2/background.rst +++ b/docs/tutorials/bfgwiki2/background.rst @@ -12,6 +12,6 @@ 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 is targeted at :mod:`repoze.bfg` version 1.2. Have fun! diff --git a/docs/tutorials/bfgwiki2/basiclayout.rst b/docs/tutorials/bfgwiki2/basiclayout.rst index b8d0ddcc2..b7c6b795d 100644 --- a/docs/tutorials/bfgwiki2/basiclayout.rst +++ b/docs/tutorials/bfgwiki2/basiclayout.rst @@ -25,29 +25,30 @@ following: :linenos: :language: xml -#. *Line 1*. The root ``<configure>`` element, in a ``bfg`` - namespace. +#. *Line 1*. The root ``<configure>`` element, using the + ``http://namespaces.repoze.org/bfg`` namespace. #. *Line 4*. Boilerplate, the comment explains. #. *Lines 6-7*. Register a :term:`subscriber` that tears down the SQLAlchemy connection after a request is finished. -#. *Lines 9-13*. Register a ``<route>`` 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 - "renderer" will use this template to create a response. +#. *Lines 9-13*. 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 15-18*. Register a ``<static>`` directive that will match - any URL hat starts with ``/static/``. This will serve up static + 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`` @@ -104,40 +105,46 @@ 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 -#. *Lines 1-5*. Imports to support later code. +#. *Lines 1-4*. Imports to support later code. -#. *Lines 7-11*. We define a ``Cleanup`` class which has a +#. *Lines 6-10*. 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 +#. *Lines 12-14*. 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 +#. *Lines 21-23*. 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 +#. Line *24*. We initialize our SQL database using SQLAlchemy, passing it the db string. -#. Line *26*. We construct a :term:`Configurator`. The first +#. *Line 25*. We construct a :term:`Configurator`. The first argument provided to the configurator is the :term:`root factory`, which is used by the :mod:`repoze.bfg` :term:`traversal` mechanism. Since this is a URL dispatch application, the root factory is ``None``. The second argument ``settings`` is passed as a keyword argument. It contains a dictionary of settings parsed by - PasteDeploy. We then use the ``make_wsgi_app`` method of the - :term:`Configurator` to return a :term:`WSGI` application. + PasteDeploy. + +# *Lines 26-29*. 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. diff --git a/docs/tutorials/bfgwiki2/definingmodels.rst b/docs/tutorials/bfgwiki2/definingmodels.rst index 982865a80..0cae00ede 100644 --- a/docs/tutorials/bfgwiki2/definingmodels.rst +++ b/docs/tutorials/bfgwiki2/definingmodels.rst @@ -24,19 +24,19 @@ 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 -``sqlalchemy.ext.declarative.declarative_base``. Declarative +: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. +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 ``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 +``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. diff --git a/docs/tutorials/bfgwiki2/definingviews.rst b/docs/tutorials/bfgwiki2/definingviews.rst index c4c609ff2..416b431be 100644 --- a/docs/tutorials/bfgwiki2/definingviews.rst +++ b/docs/tutorials/bfgwiki2/definingviews.rst @@ -2,16 +2,17 @@ Defining Views ============== -A :term:`view` 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 is assumed to return a -:term:`response` object. +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. In + 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 @@ -56,31 +57,32 @@ 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` 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'll describe each one -briefly and show the resulting ``views.py`` file afterwards. +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 afterwards. .. note:: There is nothing 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. + 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 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 -``webob.exc.HTTPFound`` class (instances of which implement the WebOb -:term:`response` interface), It will use the -``repoze.bfg.url.route_url`` API to construct a URL to the +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. @@ -88,8 +90,8 @@ 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 +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 @@ -134,22 +136,23 @@ 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 -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 BFG will render the template -associated with this view to a response. +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 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 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 ------------------------------- @@ -162,15 +165,17 @@ 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. +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. +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`` =============================================== @@ -187,9 +192,9 @@ 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. +: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 ------------------------ @@ -270,7 +275,8 @@ Note that the *ordering* of these declarations is very important. #. 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 default view for the wiki. + 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 @@ -316,7 +322,7 @@ 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 +pipeline right above ``egg:repoze.tm2#tm``, making our resulting ``tutorial.ini`` file look like so: .. literalinclude:: src/views/tutorial.ini |
